The configuration files for all my Nix systems
4

Configure Feed

Select the types of activity you want to include in your feed.

refactor: setup tack to track pinned inputs

hanna (Jun 23, 2026, 5:14 PM EDT) 64f0e2eb b5bab0be

+408
+331
.tack/default.nix
··· 1 + # SPDX-License-Identifier: EUPL-1.2 2 + # tack-managed resolver. delete this line to take ownership; tack will leave it alone afterwards. 3 + 4 + let 5 + inherit (builtins) 6 + attrNames 7 + attrValues 8 + concatMap 9 + elem 10 + elemAt 11 + filter 12 + foldl' 13 + fromJSON 14 + head 15 + intersectAttrs 16 + isList 17 + isString 18 + listToAttrs 19 + mapAttrs 20 + match 21 + pathExists 22 + readFile 23 + substring 24 + tail 25 + trace 26 + ; 27 + 28 + call = 29 + { 30 + overrides ? { }, 31 + }: 32 + let 33 + pins = fromTOML (readFile ./pins.toml); 34 + lock = fromJSON (readFile ./pins.lock.json); 35 + all_follow_raw = pins.all_follow or { }; 36 + 37 + # flatten `target = [aliases]` rows alongside `alias = "target"` rows 38 + all_follow = foldl' ( 39 + acc: key: 40 + let 41 + val = all_follow_raw.${key}; 42 + in 43 + if isList val then 44 + acc 45 + // { 46 + ${key} = key; 47 + } 48 + // listToAttrs ( 49 + map (a: { 50 + name = a; 51 + value = key; 52 + }) val 53 + ) 54 + else if isString val then 55 + acc // { ${key} = val; } 56 + else 57 + acc 58 + ) { } (attrNames all_follow_raw); 59 + 60 + knownTypes = [ 61 + "github" 62 + "gitlab" 63 + "git" 64 + "tarball" 65 + "path" 66 + "indirect" 67 + ]; 68 + 69 + # path nodes are convenience pins, so return the live local path directly 70 + # because fetchTree rejects unlocked paths in pure eval 71 + fetchPin = 72 + name: 73 + if !(lock ? ${name}) then 74 + throw "tack: pin '${name}' has no lock entry; run tack update" 75 + else 76 + let 77 + node = lock.${name}; 78 + in 79 + if (node.type or "") == "path" then 80 + { 81 + outPath = if substring 0 1 node.path == "/" then node.path else ./. + ("/" + node.path); 82 + lastModified = node.lastModified or 0; 83 + } 84 + // (if node ? narHash then { inherit (node) narHash; } else { }) 85 + else if !(elem (node.type or "") knownTypes) then 86 + throw "tack: unknown lock type '${node.type or "?"}' for pin '${name}'" 87 + else 88 + fetchTree node; 89 + 90 + fetchFixed = 91 + name: entry: 92 + let 93 + raw = derivation { 94 + inherit name; 95 + inherit (entry) url; 96 + builder = "builtin:fetchurl"; 97 + system = "builtin"; 98 + outputHash = entry.sha256; 99 + outputHashAlgo = "sha256"; 100 + outputHashMode = "flat"; 101 + }; 102 + unpacked = derivation { 103 + inherit name; 104 + builder = "builtin:unpack-channel"; 105 + system = "builtin"; 106 + src = raw; 107 + channelName = name; 108 + }; 109 + in 110 + if (entry.unpack or "file") == "tarball" then unpacked.outPath + "/" + name else raw.outPath; 111 + 112 + resolveSpec = upLock: spec: if isList spec then walkPath upLock upLock.root spec else spec; 113 + 114 + walkPath = 115 + upLock: nodeName: path: 116 + if path == [ ] then 117 + nodeName 118 + else if !(upLock.nodes ? ${nodeName}) then 119 + throw "tack: follows path dead-end: no node '${nodeName}' in flake.lock" 120 + else 121 + let 122 + key = head path; 123 + inputs = upLock.nodes.${nodeName}.inputs or { }; 124 + in 125 + if !(inputs ? ${key}) then 126 + throw "tack: follows path dead-end: node '${nodeName}' has no input '${key}'" 127 + else 128 + walkPath upLock (resolveSpec upLock inputs.${key}) (tail path); 129 + 130 + followsFor = 131 + pin: 132 + let 133 + rules = removeAttrs all_follow (pin.exclude_follow or [ ]); 134 + in 135 + { 136 + level = rules // (pin.follows or { }); 137 + deep = rules; 138 + }; 139 + 140 + resolveFollows = mapAttrs ( 141 + _: target: self.${target} or (throw "tack: follows target '${target}' is not a pin") 142 + ); 143 + 144 + # follows key is `flake:name`, `tack:name`, or bare `name` 145 + # project onto one side, rekeyed to bare names 146 + followsForSide = 147 + side: follows: 148 + listToAttrs ( 149 + concatMap ( 150 + key: 151 + let 152 + m = match "(flake|tack):(.*)" key; 153 + in 154 + if m == null then 155 + [ 156 + { 157 + name = key; 158 + value = follows.${key}; 159 + } 160 + ] 161 + else if head m == side then 162 + [ 163 + { 164 + name = elemAt m 1; 165 + value = follows.${key}; 166 + } 167 + ] 168 + else 169 + [ ] 170 + ) (attrNames follows) 171 + ); 172 + 173 + mkCallerInputs = 174 + upLock: nodeName: rawInputs: levelFollows: deepFollows: 175 + let 176 + resolved = resolveFollows levelFollows; 177 + in 178 + mapAttrs ( 179 + n: _decl: 180 + resolved.${n} or ( 181 + if upLock != null then 182 + let 183 + ref = 184 + (upLock.nodes.${nodeName}.inputs or { }).${n} 185 + or (throw "tack: input '${n}' declared but not in flake.lock node '${nodeName}'"); 186 + childName = resolveSpec upLock ref; 187 + childNode = upLock.nodes.${childName}; 188 + childSrc = fetchTree childNode.locked; 189 + in 190 + if childNode.flake or true then evalTransitive upLock childName childSrc deepFollows else childSrc 191 + else 192 + throw "tack: no flake.lock; cannot resolve input '${n}'" 193 + ) 194 + ) rawInputs; 195 + 196 + mkFlakeResult = 197 + sourceInfo: flakeDir: callerInputs: outputs: 198 + outputs 199 + // sourceInfo 200 + // { 201 + outPath = flakeDir; 202 + inputs = callerInputs; 203 + inherit outputs sourceInfo; 204 + _type = "flake"; 205 + }; 206 + 207 + evalFlake = 208 + sourceInfo: flakeDir: upLock: nodeName: levelFollows: deepFollows: 209 + let 210 + raw = import (flakeDir + "/flake.nix"); 211 + 212 + tackPinsPath = flakeDir + "/.tack/pins.toml"; 213 + hasTack = pathExists tackPinsPath; 214 + upPins = if hasTack then fromTOML (readFile tackPinsPath) else { }; 215 + 216 + # project follows onto each side, keep only names that side has 217 + # bare follow reaches both; `flake:`/`tack:` reaches just one 218 + tackOverrides = resolveFollows ( 219 + intersectAttrs (upPins.inputs or { }) (followsForSide "tack" levelFollows) 220 + ); 221 + flakeLevel = intersectAttrs (raw.inputs or { }) (followsForSide "flake" levelFollows); 222 + 223 + # deep follows pass down raw, so each descendant re-projects per side 224 + callerInputs = mkCallerInputs upLock nodeName (raw.inputs or { }) flakeLevel deepFollows; 225 + 226 + # upstream declares its outputs forward tackOverrides; a closed `{ self }:` 227 + # would throw on the extra kwarg, so forward only when declared 228 + supportsOverrides = (upPins.tack or { }).recomposable or false; 229 + 230 + extraArgs = if supportsOverrides && tackOverrides != { } then { inherit tackOverrides; } else { }; 231 + 232 + outputs = raw.outputs (callerInputs // extraArgs // { self = result; }); 233 + 234 + result = 235 + let 236 + base = mkFlakeResult sourceInfo flakeDir callerInputs outputs; 237 + in 238 + if hasTack && tackOverrides != { } && !supportsOverrides then 239 + trace "tack: ${flakeDir}: not marked recomposable (set [tack] recomposable = true); overrides will not reach upstream" base 240 + else 241 + base; 242 + in 243 + result; 244 + 245 + evalTransitive = 246 + upLock: nodeName: sourceInfo: follows: 247 + evalFlake sourceInfo sourceInfo.outPath upLock nodeName follows follows; 248 + 249 + evalTopFlake = 250 + sourceInfo: pin: 251 + let 252 + flakeDir = sourceInfo.outPath + (if pin ? dir then "/" + pin.dir else ""); 253 + upLockPath = flakeDir + "/flake.lock"; 254 + upLock = if pathExists upLockPath then fromJSON (readFile upLockPath) else null; 255 + rootNode = if upLock != null then upLock.root else null; 256 + f = followsFor pin; 257 + in 258 + evalFlake sourceInfo flakeDir upLock rootNode f.level f.deep; 259 + 260 + evalFetch = 261 + sourceInfo: pin: subdir: 262 + let 263 + path = sourceInfo.outPath + subdir; 264 + tackPinsPath = path + "/.tack/pins.toml"; 265 + hasTack = pathExists tackPinsPath; 266 + upPins = if hasTack then fromTOML (readFile tackPinsPath) else { }; 267 + f = followsFor pin; 268 + # a fetch drill-in is tack-only 269 + tackOverrides = resolveFollows ( 270 + intersectAttrs (upPins.inputs or { }) (followsForSide "tack" f.level) 271 + ); 272 + in 273 + # a fetch pin is a source tree (path); hand back resolved inputs only when 274 + # there are overrides to push into the upstream's .tack 275 + if hasTack && tackOverrides != { } then 276 + let 277 + upstream = import (path + "/.tack"); 278 + in 279 + # old resolvers return a plain attrset, not a callable functor 280 + if upstream ? __functor then 281 + (upstream { overrides = tackOverrides; }) // { outPath = path; } 282 + else 283 + trace "tack: ${path}: upstream .tack predates override support; overrides will not reach it" path 284 + else 285 + path; 286 + 287 + loadPin = 288 + name: pin: 289 + let 290 + pinType = pin.type or (if pin.flake or true then "flake" else "fetch"); 291 + subdir = if pin ? dir then "/" + pin.dir else ""; 292 + in 293 + if pinType == "fixed" then 294 + fetchFixed name lock.${name} 295 + else 296 + let 297 + sourceInfo = fetchPin name; 298 + in 299 + if pinType == "flake" then evalTopFlake sourceInfo pin else evalFetch sourceInfo pin subdir; 300 + 301 + declared = pins.inputs or { }; 302 + 303 + # undeclared lock entries are auto-dedup synthetics only when referenced as 304 + # [all_follow] targets; stale locks from hand-edits are ignored (tack rm to clean) 305 + autoTargets = listToAttrs ( 306 + map (target: { 307 + name = target; 308 + value = true; 309 + }) (attrValues all_follow) 310 + ); 311 + autoNames = filter (n: !(declared ? ${n}) && autoTargets ? ${n}) (attrNames lock); 312 + autoPin = 313 + name: 314 + let 315 + sourceInfo = fetchPin name; 316 + in 317 + if pathExists (sourceInfo.outPath + "/flake.nix") then evalTopFlake sourceInfo { } else sourceInfo; 318 + 319 + self = 320 + (mapAttrs loadPin declared) 321 + // listToAttrs ( 322 + map (name: { 323 + inherit name; 324 + value = autoPin name; 325 + }) autoNames 326 + ) 327 + // overrides; 328 + in 329 + self // { __functor = _: call; }; 330 + in 331 + call { }
+50
.tack/pins.lock.json
··· 1 + { 2 + "agents": { 3 + "type": "github", 4 + "owner": "numtide", 5 + "repo": "llm-agents.nix", 6 + "rev": "437521e14715e95a167faf1c5b8fbb4382c5ac3d", 7 + "narHash": "sha256-wTlSeXo/CmbZvdO2sla7JRO/WpYCEHawEDko/FJHeMQ=", 8 + "lastModified": 1782243238 9 + }, 10 + "darwin": { 11 + "type": "github", 12 + "owner": "nix-darwin", 13 + "repo": "nix-darwin", 14 + "rev": "a1fa429e945becaf60468600daf649be4ba0350c", 15 + "narHash": "sha256-rCPytmKNjctLloB6UgK5CRrHSwV4b0ygxtJLPPp8R14=", 16 + "lastModified": 1781761792 17 + }, 18 + "hjem": { 19 + "type": "github", 20 + "owner": "feel-co", 21 + "repo": "hjem", 22 + "rev": "3b110d0e7c07a5652019223bd7f7b872b86f06a4", 23 + "narHash": "sha256-1cMArpPnPvjKYNMsMRwtAxQ6ngPnIg/Wt3ohS95fbwY=", 24 + "lastModified": 1782071069 25 + }, 26 + "nixpkgs": { 27 + "type": "github", 28 + "owner": "nixos", 29 + "repo": "nixpkgs", 30 + "rev": "b3c092d3c36d91e2f61f3dfb39a159f180a56659", 31 + "narHash": "sha256-BnbXO5s5EhV89lLXMAGCzPdEN5a6vNqvMk71obeTEUw=", 32 + "lastModified": 1782118813 33 + }, 34 + "tack": { 35 + "type": "github", 36 + "owner": "manic-systems", 37 + "repo": "tack", 38 + "rev": "a834318b7fe7371fce1eaf9c309caeadf57cf9af", 39 + "narHash": "sha256-KhJb0NWLhj8AkD8uWEbXt179YlFLemk0OgOltw4jEk8=", 40 + "lastModified": 1782001225 41 + }, 42 + "wsl": { 43 + "type": "github", 44 + "owner": "nix-community", 45 + "repo": "nixos-wsl", 46 + "rev": "5675822ba756e6e56f8f6a5a76e90e0da2ece94d", 47 + "narHash": "sha256-V5EQQbDnmdiXGQXrEF1PEL7QYsFqfH8N1E89Z5ONwFk=", 48 + "lastModified": 1781182279 49 + } 50 + }
+27
.tack/pins.toml
··· 1 + 2 + [shorturls] 3 + gh = "github:{path}" 4 + 5 + [all_follow] 6 + nixpkgs = "nixpkgs" 7 + 8 + [inputs.nixpkgs] 9 + url = "gh:nixos/nixpkgs/nixpkgs-unstable" 10 + 11 + [inputs.wsl] 12 + url = "gh:nix-community/nixos-wsl/main" 13 + 14 + [inputs.darwin] 15 + url = "gh:nix-darwin/nix-darwin/master" 16 + 17 + [inputs.hjem] 18 + url = "gh:feel-co/hjem/main" 19 + 20 + [inputs.hjem.follows] 21 + nix-darwin = "darwin" 22 + 23 + [inputs.agents] 24 + url = "gh:numtide/llm-agents.nix/main" 25 + 26 + [inputs.tack] 27 + url = "gh:manic-systems/tack/v1.0.0"