d5b8315c6ade

self-update builds the new version into the store before touching the profile, so a network drop or eval failure never leaves nina uninstalled (NINA-006)

time
2026-07-02 13:36 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
fable audit fix — commands.zig
parent
74c9cc1a35ab
2 files src/commands.zig+19 −0src/output.zig+13 −3
src/commands.zig+19 −0
@@ -423,6 +423,25 @@423423         const element = nina_element.?;424424         output.updateSelfNixManaged(remote_ver);425425         if (ctx.cfg.confirm and !output.confirm("")) return;426+        // Build the new version into the store BEFORE touching the profile. A427+        // network drop or eval failure here leaves the old element untouched —428+        // this is the only step that can fail for reasons outside our control,429+        // so it must happen first. Once this succeeds, the subsequent430+        // remove+add operates on an already-warm local store and is not431+        // expected to fail for the same reasons, closing the window a raw432+        // remove-then-add would leave open between the two profile edits.433+        const prefetch_r = exec.capture(ctx.gpa, ctx.io, &ctx.machine, &.{ "nix", "build", "--no-link", "--refresh", FLAKE_URL }) catch {434+            output.updateSelfNixPrefetchFailed();435+            return;436+        };437+        defer ctx.gpa.free(prefetch_r.stdout);438+        defer ctx.gpa.free(prefetch_r.stderr);439+        if (prefetch_r.exit_code != 0) {440+            output.raw(prefetch_r.stdout);441+            output.raw(prefetch_r.stderr);442+            output.updateSelfNixPrefetchFailed();443+            return;444+        }426445         // `nix profile upgrade` re-resolves the element's own originalUrl. The427446         // archive install runs with --no-write-lock-file, so nix has nowhere to428447         // persist the lock it takes at install time except baking it into that

view file

src/output.zig+13 −3
@@ -1129,10 +1129,20 @@11291129     p("{s}try manually: nix profile upgrade '.*nina.*'\n", .{step()});11301130 }11311131 1132+// Reached when building the new version into the store fails, before the1133+// profile has been touched at all — the old nina element is still installed1134+// and working.1135+pub fn updateSelfNixPrefetchFailed() void {1136+    p("{s}could not build the new version — check your connection and try again\n", .{if (color_enabled) ERR_C else ERR_P});1137+    p("{s}your current nina install is untouched\n", .{step()});1138+}1139+11321140 // Reached only after `nix profile remove` already succeeded and the1133-// re-add failed — nina is genuinely uninstalled at this point, not just1134-// stuck on the old version, so the recovery command must reinstall from1135-// scratch rather than upgrade.1141+// re-add failed despite the new version having just been built into the1142+// store — nina is genuinely uninstalled at this point, not just stuck on1143+// the old version, so the recovery command must reinstall from scratch1144+// rather than upgrade. Expected to be rare now that the build happens1145+// before the profile is touched.11361146 pub fn updateSelfNixReinstallFailed() void {11371147     p("{s}nix profile add failed — nina was removed and is not reinstalled\n", .{if (color_enabled) ERR_C else ERR_P});11381148     p("{s}reinstall manually: nix profile add --no-write-lock-file 'https://kepr.uk/nina/archive/HEAD.tar.gz#nina'\n", .{step()});

view file