delegate pin management to tack

This commit is contained in:
atagen 2026-05-29 00:25:36 +10:00
parent 070c3f0a7f
commit c42b5abba8
5 changed files with 35 additions and 163 deletions

16
flake.lock generated
View file

@ -82,9 +82,25 @@
"inputs": {
"nix-systems": "nix-systems",
"nixpkgs": "nixpkgs",
"tack": "tack",
"unf": "unf"
}
},
"tack": {
"locked": {
"lastModified": 1779981755,
"narHash": "sha256-sdHSW55s2NCd3t2ezu5oV98ZQPjXHE2nxn0IixFaxwU=",
"owner": "manic-systems",
"repo": "tack",
"rev": "6e62423ffe85ebe0db1a8e538aa3ff281f690b3f",
"type": "github"
},
"original": {
"owner": "manic-systems",
"repo": "tack",
"type": "github"
}
},
"unf": {
"inputs": {
"ndg": "ndg",

View file

@ -3,6 +3,7 @@
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
nix-systems.url = "github:nix-systems/default-linux";
unf.url = "git+https://git.atagen.co/atagen/unf";
tack.url = "github:manic-systems/tack";
};
outputs =
@ -15,6 +16,7 @@
nixpkgs.lib.genAttrs (import nix-systems) (
system: function nixpkgs.legacyPackages.${system} system
);
tackFor = system: tack.packages.${system}.default;
in
{
devShells = forEachSystem (
@ -28,9 +30,10 @@
);
packages = forEachSystem (
pkgs: _: {
pkgs: system: {
default = pkgs.callPackage ./nix/default.nix {
inherit version;
tack = tackFor system;
};
docs = pkgs.callPackage unf.lib.pak-chooie {
inherit self;
@ -55,9 +58,11 @@
in
{
imports = [ ./nix/module.nix ];
programs.meat.tack = lib.mkDefault (tackFor pkgs.stdenv.hostPlatform.system);
programs.meat.package = self.packages.${pkgs.stdenv.hostPlatform.system}.default.override {
differ = cfg.differ;
monitor = cfg.monitor;
tack = cfg.tack;
};
};

167
meat.nu Normal file → Executable file
View file

@ -121,86 +121,14 @@ def cmd-trade [] {
}
}
def pins-path [] { $"($env.MEATS)/pins/pins.toml" }
def lock-path [] { $"($env.MEATS)/pins/pins.lock.json" }
def load-pins [] {
let p = pins-path
if not ($p | path exists) { error make { msg: $"no pins file at ($p)" } }
open --raw $p | from toml
}
def lock-path [] { $"($env.MEATS)/.tack/pins.lock.json" }
def load-lock [] {
let p = lock-path
if ($p | path exists) { open --raw $p | from json } else { {} }
}
# Sort keys alphabetically and write atomically.
def write-lock [lock: record] {
let p = lock-path
let sorted = $lock | columns | sort | reduce -f {} { |k, acc| $acc | insert $k ($lock | get $k) }
let tmp = $"($p).tmp"
$sorted | to json --indent 2 | save -f $tmp
^mv $tmp $p
}
# scheme:rest → expansion via {path} template, or pass-through.
def expand-shorturl [url: string, shorturls: record] {
let parts = $url | split row ":" -n 2
if (($parts | length) < 2) { return $url }
let scheme = $parts | get 0
if not ($scheme in ($shorturls | columns)) { return $url }
($shorturls | get $scheme) | str replace --regex '\{path\}' ($parts | get 1)
}
# Decompose to {git_url, ref?} for cheap ls-remote queries (used by `look`).
def parse-git-target [url: string] {
if ($url | str starts-with "github:") {
let body = $url | str substring 7..
let path_query = $body | split row "?" -n 2
let segs = ($path_query | get 0) | split row "/"
let owner = $segs | get 0
let repo = $segs | get 1
let ref = if (($segs | length) > 2) { $segs | skip 2 | str join "/" } else { null }
return { git_url: $"https://github.com/($owner)/($repo).git", ref: $ref }
}
if ($url | str starts-with "git+") {
let stripped = $url | str substring 4..
let parts = $stripped | split row "?" -n 2
let base = $parts | get 0
let ref = if (($parts | length) > 1) {
($parts | get 1) | split row "&" | each { |kv|
let kvp = $kv | split row "=" -n 2
{ k: ($kvp | get 0), v: ($kvp | get 1?) }
} | where k == "ref" | get -o 0 | get -o v
} else { null }
return { git_url: $base, ref: $ref }
}
error make { msg: $"unsupported url scheme for ls-remote: ($url)" }
}
# Cheap "is upstream ahead?" check via git ls-remote. Returns the rev string.
def ls-remote-head [url: string] {
let tgt = parse-git-target $url
let target_ref = if ($tgt.ref? | is-not-empty) { $"refs/heads/($tgt.ref)" } else { "HEAD" }
let r = ^git ls-remote $tgt.git_url $target_ref | complete
if $r.exit_code != 0 { error make { msg: $"ls-remote failed: ($r.stderr)" } }
let first = $r.stdout | str trim | lines | get -o 0
if ($first | is-empty) { error make { msg: "no refs returned" } }
$first | split row "\t" | get 0
}
# Real prefetch — caches in /nix/store and returns the full locked attrset.
# Output JSON: { hash: SRI, locked: {...}, original: {...}, storePath: PATH }
# --refresh bypasses the ref→rev cache (otherwise stale within tarball-ttl).
def prefetch-pin [url: string] {
let r = ^nix flake prefetch --refresh --json $url | complete
if $r.exit_code != 0 { error make { msg: $"prefetch failed for ($url): ($r.stderr)" } }
let j = $r.stdout | from json
$j.locked | insert narHash $j.hash
}
# Fetch one locked input into the store, mirroring lib/inputs.nix's
# Fetch one locked input into the store, mirroring the tack resolver's
# `builtins.fetchTree lock.<name>`. Returns the name on failure, else null.
def warm-pin [name: string, node: record] {
let tmp = ^mktemp -t "meat-pin.XXXXXX.json" | str trim
@ -228,97 +156,14 @@ def warm-pins [] {
def cmd-fresh [...names: string] {
with-frame {
meat-print "HUNTING FRESH MEATS.."
let pins = load-pins
let shorturls = $pins.shorturls? | default {}
let inputs = $pins.inputs
let lock = load-lock
let requested = if ($names | is-empty) { $inputs | columns } else { $names }
# Report unknown names up front and keep only real targets.
let targets = $requested | each { |name|
if not ($name in ($inputs | columns)) {
meat-print $"NO MEAT CALLED ($name | str upcase).."
null
} else { $name }
} | compact
# Prefetch every target in parallel — this is the network-bound work.
# No lock writes happen here; results are collected and applied below.
let results = $targets | par-each { |name|
let old_rev = ($lock | get -o $name) | default {} | get -o rev
try {
let pin = $inputs | get $name
let expanded = expand-shorturl $pin.url $shorturls
let entry = prefetch-pin $expanded
{ name: $name, ok: true, entry: $entry, old_rev: $old_rev, new_rev: $entry.rev }
} catch { |e|
{ name: $name, ok: false, err: $e.msg }
}
}
# Apply results sequentially in target order so the lock file is never
# written concurrently and reporting stays deterministic.
mut lock = $lock
for name in $targets {
let r = $results | where name == $name | first
meat-print $"PROCESSING ($name | str upcase).."
if not $r.ok {
meat-print $" NO FIND ($name | str upcase): ($r.err)"
continue
}
if $r.old_rev == $r.new_rev {
meat-print $" ($name | str upcase) STILL FRESH"
continue
}
$lock = $lock | upsert $name $r.entry
write-lock $lock
let from = if ($r.old_rev | is-empty) { "NEW" } else { $r.old_rev | str substring 0..8 }
meat-print $" ($name | str upcase): ($from) -> ($r.new_rev | str substring 0..8)"
}
do { cd $env.MEATS; ^$env.TACK update ...$names }
}
print ""
}
def cmd-look [] {
def cmd-look [...names: string] {
with-frame {
meat-print "LOOK FOR NEW MEATS.."
let pins = load-pins
let shorturls = $pins.shorturls? | default {}
let inputs = $pins.inputs
let lock = load-lock
let rows = $inputs | transpose name pin
# Query every upstream head in parallel; no printing happens here.
let results = $rows | par-each { |row|
let old_rev = ($lock | get -o $row.name) | default {} | get -o rev
try {
let expanded = expand-shorturl $row.pin.url $shorturls
let new_rev = ls-remote-head $expanded
{ name: $row.name, ok: true, stale: ($old_rev != $new_rev), old_rev: $old_rev, new_rev: $new_rev }
} catch {
{ name: $row.name, ok: false }
}
}
# Report in input order so output is deterministic and never races.
mut stale = []
for row in $rows {
let r = $results | where name == $row.name | first
if not $r.ok {
meat-print $" NO FIND ($row.name | str upcase).."
continue
}
if $r.stale {
let from = if ($r.old_rev | is-empty) { "NEW" } else { $r.old_rev | str substring 0..8 }
meat-print $" ($row.name | str upcase): ($from) -> ($r.new_rev | str substring 0..8)"
$stale = ($stale | append $row.name)
}
}
if ($stale | is-empty) {
meat-print "NO MEAT FRESHER"
}
do { cd $env.MEATS; ^$env.TACK look ...$names }
}
}
@ -429,7 +274,7 @@ def main [...args: string] {
"poke" => { cmd-poke ...$rest }
"gut" => { cmd-gut ...$rest }
"trade" => { cmd-trade }
"look" => { cmd-look }
"look" => { cmd-look ...$rest }
"fresh" => { cmd-fresh ...$rest }
"hunt" => { cmd-hunt ...$rest }
"ritual" => { cmd-ritual }

View file

@ -5,6 +5,7 @@
nushell,
makeBinaryWrapper,
version,
tack,
differ ? pkgs.dix,
monitor ? pkgs.nix-output-monitor,
...
@ -26,7 +27,8 @@ stdenvNoCC.mkDerivation {
makeBinaryWrapper ${nushell}/bin/nu $out/bin/meat \
--add-flags "$out/share/meat/meat.nu" \
--set DIFFER ${lib.getExe differ} \
--set MONITOR ${lib.getExe monitor}
--set MONITOR ${lib.getExe monitor} \
--set TACK ${lib.getExe tack}
runHook postInstall
'';
}

View file

@ -29,6 +29,10 @@ in
description = "nix monitoring tool to use";
default = pkgs.nix-output-monitor;
};
tack = mkOption {
type = types.package;
description = "tack pin manager";
};
};
config = lib.mkIf cfg.enable {
environment.sessionVariables.MEATS = cfg.flake;