Lock follows paths are absolute from the root node; nomarchy's nodes move
one level down in the composed downstream lock, so ["nixpkgs"] must
become ["nomarchy","nixpkgs"] — otherwise every follows dangles
("input 'nomarchy/home-manager/nixpkgs' follows a non-existent input").
Verified: the composed lock now resolves the full input tree with
`nix flake metadata --offline`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compose a flake.lock for the freshly installed machine — offline.
|
|
|
|
The generated downstream flake has exactly one input, `nomarchy`. A normal
|
|
`nix flake lock` would fetch it from its forge; on an offline install we
|
|
already KNOW the answer: the live ISO was built from a specific nomarchy
|
|
rev whose source (and all transitive inputs, via the ISO's pinned store
|
|
paths) is on hand. So we write the lock nix would have written:
|
|
|
|
root ─▶ nomarchy (locked to the baked rev+narHash)
|
|
└─▶ every node from nomarchy's own flake.lock, verbatim
|
|
|
|
Nix resolves locked inputs by narHash against the store before touching
|
|
the network, so evaluation on the target works without connectivity.
|
|
|
|
Usage:
|
|
compose-lock.py <nomarchy-flake.lock> <out> <locked-json> <original-json>
|
|
|
|
where <locked-json>/<original-json> are the flake-lock node fields for the
|
|
nomarchy input (any input type — github, git, …), baked at package build.
|
|
"""
|
|
import json
|
|
import sys
|
|
|
|
|
|
def main() -> None:
|
|
src_lock, out, locked_json, original_json = sys.argv[1:5]
|
|
|
|
locked = json.loads(locked_json)
|
|
if not locked.get("narHash"):
|
|
sys.exit("compose-lock: no narHash in locked metadata")
|
|
|
|
with open(src_lock) as f:
|
|
upstream = json.load(f)
|
|
|
|
nodes = dict(upstream["nodes"])
|
|
# nomarchy's root node describes ITS inputs; that mapping becomes the
|
|
# input set of the new `nomarchy` node.
|
|
nomarchy_inputs = nodes.pop("root")["inputs"]
|
|
if "nomarchy" in nodes:
|
|
sys.exit("compose-lock: upstream lock already has a 'nomarchy' node")
|
|
|
|
# Follows paths (list-valued inputs) are absolute from the lock's
|
|
# root; the copied nodes now live one level down, so ["nixpkgs"]
|
|
# must become ["nomarchy", "nixpkgs"]. Plain string values reference
|
|
# node keys directly and stay as they are.
|
|
for node in nodes.values():
|
|
for name, ref in node.get("inputs", {}).items():
|
|
if isinstance(ref, list):
|
|
node["inputs"][name] = ["nomarchy"] + ref
|
|
|
|
nodes["nomarchy"] = {
|
|
"inputs": nomarchy_inputs,
|
|
"locked": locked,
|
|
"original": json.loads(original_json),
|
|
}
|
|
nodes["root"] = {"inputs": {"nomarchy": "nomarchy"}}
|
|
|
|
with open(out, "w") as f:
|
|
json.dump({"nodes": nodes, "root": "root", "version": 7}, f, indent=2)
|
|
f.write("\n")
|
|
pin = locked.get("rev", locked["narHash"])[:12]
|
|
print(f"compose-lock: wrote {out} (nomarchy @ {pin})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|