2f0d457a97ba
home: detect platform-specific attr (user-arch-os) with fallback chain for home-manager flake ref
- time
- 2026-06-15 12:24 UTC · 3 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- murazaki feedback fixes
- parent
- 7acbc7250302
↓ nina-2f0d457a.tar.gzatlas at this save
files changed3 files
src/commands.zig+49 −11src/exec.zig+51 −11src/output.zig+6 −0src/commands.zig+49 −11
@@ -1500,25 +1500,63 @@15001500 }15011501 15021502 output.homeApplyStart(ctx.machine.name);1503- const flake_ref = exec.homeFlakeRef(ctx.gpa, ctx.io, ctx.environ);1504- defer if (flake_ref) |r| ctx.gpa.free(r);1505- if (flake_ref) |ref| {1506- output.printSubstep("home-manager switch --flake {s}", .{ref});1507- } else {1508- output.printSubstep("home-manager switch", .{});1509- }15101503 1504+ // Flake-based home config: switch by attribute with a graceful fallback1505+ // chain. A channel-based config (no flake.nix) uses a plain switch.1506+ const dir = exec.homeFlakeDir(ctx.gpa, ctx.io, ctx.environ);1507+ defer if (dir) |d| ctx.gpa.free(d);1508+ if (dir) |d| return homeApplyFlake(ctx, d);1509+1510+ output.printSubstep("home-manager switch", .{});15111511 const start = std.Io.Clock.awake.now(ctx.io);1512- const code = if (flake_ref) |ref|1513- try exec.homeManagerSwitchFlake(ctx.io, &ctx.machine, ctx.gpa, ref)1514- else1515- try exec.homeManagerSwitch(ctx.io, &ctx.machine, ctx.gpa);1512+ const code = try exec.homeManagerSwitch(ctx.io, &ctx.machine, ctx.gpa);15161513 if (code != 0) return error.BuildFailed;15171514 15181515 const elapsed_ns = @max(0, start.untilNow(ctx.io, .awake).toNanoseconds());15191516 const elapsed: u64 = @intCast(@divFloor(elapsed_ns, 1_000_000));15201517 output.homeApplyDone(homeCurrentGen(ctx), elapsed);15211518 runPostHook(ctx, "post-home");1519+}1520+1521+// Switch a flake-based home config, trying the most specific attribute first:1522+// <dir>#<user>-<arch>-<os> → <dir>#<user> → <dir> (home-manager resolves)1523+// Each fallback fires only on a missing-attribute error, never on a real build1524+// failure. Mirrors how ${USER}-${system} homeConfigurations keys are written.1525+fn homeApplyFlake(ctx: Ctx, dir: []const u8) !void {1526+ const user = ctx.environ.get("USER") orelse "default";1527+1528+ var refs: std.ArrayList([]const u8) = .empty;1529+ defer {1530+ for (refs.items) |r| ctx.gpa.free(r);1531+ refs.deinit(ctx.gpa);1532+ }1533+ if (exec.homeManagerAttr(ctx.gpa, ctx.io, &ctx.machine, ctx.environ) catch null) |attr| {1534+ defer ctx.gpa.free(attr);1535+ try refs.append(ctx.gpa, try std.fmt.allocPrint(ctx.gpa, "{s}#{s}", .{ dir, attr }));1536+ }1537+ try refs.append(ctx.gpa, try std.fmt.allocPrint(ctx.gpa, "{s}#{s}", .{ dir, user }));1538+ try refs.append(ctx.gpa, try ctx.gpa.dupe(u8, dir));1539+1540+ const start = std.Io.Clock.awake.now(ctx.io);1541+ for (refs.items, 0..) |ref, i| {1542+ output.printSubstep("home-manager switch --flake {s}", .{ref});1543+ switch (try exec.homeManagerSwitchFlake(ctx.io, &ctx.machine, ctx.gpa, ref)) {1544+ .ok => {1545+ const elapsed_ns = @max(0, start.untilNow(ctx.io, .awake).toNanoseconds());1546+ const elapsed: u64 = @intCast(@divFloor(elapsed_ns, 1_000_000));1547+ output.homeApplyDone(homeCurrentGen(ctx), elapsed);1548+ runPostHook(ctx, "post-home");1549+ return;1550+ },1551+ // Attr not defined by the flake — drop to the next candidate.1552+ .attr_missing => {1553+ if (i + 1 < refs.items.len) output.homeAttrFallback(ref);1554+ continue;1555+ },1556+ .failed => return error.BuildFailed,1557+ }1558+ }1559+ return error.BuildFailed;15221560 }15231561 15241562 // --- Service commands ---src/exec.zig+51 −11
@@ -1527,19 +1527,48 @@15271527 return .none;15281528 }15291529 1530-// If the home-manager config dir holds a flake.nix, returns the flake ref1531-// "<dir>#<user>" for `home-manager switch --flake`. Username comes from $USER.1532-// Caller frees. null when there's no flake.nix (channel-based home config).1533-pub fn homeFlakeRef(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) ?[]const u8 {1530+// The home-manager config dir when it holds a flake.nix (a flake-based home1531+// config that `home-manager switch --flake` drives). null for a channel-based1532+// config, which uses a plain `home-manager switch`. Caller frees.1533+pub fn homeFlakeDir(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) ?[]const u8 {15341534 const dir = homeConfigDir(gpa, environ) orelse return null;1535- defer gpa.free(dir);1536- const flake = std.fmt.allocPrint(gpa, "{s}/flake.nix", .{dir}) catch return null;1535+ const flake = std.fmt.allocPrint(gpa, "{s}/flake.nix", .{dir}) catch {1536+ gpa.free(dir);1537+ return null;1538+ };15371539 defer gpa.free(flake);1538- if (!pathExists(io, flake)) return null;1539- const user = environ.get("USER") orelse "default";1540- return std.fmt.allocPrint(gpa, "{s}#{s}", .{ dir, user }) catch null;1540+ if (!pathExists(io, flake)) {1541+ gpa.free(dir);1542+ return null;1543+ }1544+ return dir;15411545 }15421546 1547+// Platform-specific home-manager attribute "<user>-<arch>-<os>" (e.g.1548+// murazaki-x86_64-linux) — the homeConfigurations key many flakes derive from1549+// ${USER}-${system}. arch/os come from uname on the local host (home commands1550+// are local-only). Caller frees.1551+pub fn homeManagerAttr(gpa: std.mem.Allocator, io: std.Io, machine: *const types.Machine, environ: *const std.process.Environ.Map) ![]const u8 {1552+ const user = environ.get("USER") orelse "user";1553+1554+ const arch_r = try capture(gpa, io, machine, &.{ "uname", "-m" });1555+ defer gpa.free(arch_r.stdout);1556+ defer gpa.free(arch_r.stderr);1557+ if (arch_r.exit_code != 0) return error.ProcessFailed;1558+ const arch = std.mem.trim(u8, arch_r.stdout, " \t\r\n");1559+1560+ const os_r = try capture(gpa, io, machine, &.{ "uname", "-s" });1561+ defer gpa.free(os_r.stdout);1562+ defer gpa.free(os_r.stderr);1563+ if (os_r.exit_code != 0) return error.ProcessFailed;1564+ const os = try gpa.dupe(u8, std.mem.trim(u8, os_r.stdout, " \t\r\n"));1565+ defer gpa.free(os);1566+ for (os) |*ch| ch.* = std.ascii.toLower(ch.*);1567+1568+ if (arch.len == 0 or os.len == 0) return error.ProcessFailed;1569+ return std.fmt.allocPrint(gpa, "{s}-{s}-{s}", .{ user, arch, os });1570+}1571+15431572 // Map a nixos-version string to the matching home-manager branch: an unstable15441573 // channel ("…pre…"/"unstable") → master; a stable release like15451574 // "25.05.20250115.abcdef (Warbler)" → release-25.05. Anything unparseable@@ -1621,13 +1650,24 @@16211650 return r.exit_code;16221651 }16231652 1624-pub fn homeManagerSwitchFlake(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator, flake_ref: []const u8) !u8 {1653+// Outcome of a flake-based switch, so the caller can fall back to a less1654+// specific attribute on a missing-attribute evaluation error — but never on a1655+// genuine build failure.1656+pub const SwitchOutcome = enum { ok, attr_missing, failed };1657+1658+pub fn homeManagerSwitchFlake(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator, flake_ref: []const u8) !SwitchOutcome {16251659 output.flush();16261660 const r = try capture(gpa, io, machine, &.{ "home-manager", "switch", "--flake", flake_ref });16271661 defer gpa.free(r.stdout);16281662 defer gpa.free(r.stderr);1663+ if (r.exit_code == 0) return .ok;1664+ // A flake that doesn't define this homeConfiguration fails during evaluation1665+ // with "does not provide attribute" — surface that so the caller can retry a1666+ // different attr rather than treating it as a build failure.1667+ if (std.mem.indexOf(u8, r.stderr, "does not provide attribute") != null)1668+ return .attr_missing;16291669 errors.setBuildStderr(r.stderr);1630- return r.exit_code;1670+ return .failed;16311671 }16321672 16331673 // home-manager has no system-profile rollback; it re-activates the previoussrc/output.zig+6 −0
@@ -1389,6 +1389,12 @@13891389 p("{s}home packages {s}\n\n", .{ hdr(), machine });13901390 }13911391 1392+// A flake attribute wasn't defined — note it (dim) before trying the next, less1393+// specific candidate in the home-apply fallback chain.1394+pub fn homeAttrFallback(ref: []const u8) void {1395+ p(" {s}{s} not defined — trying next{s}\n", .{ c(DIM), ref, c(RESET) });1396+}1397+13921398 pub fn homeInitStart(switch_on_init: bool, dir: ?[]const u8) void {13931399 const target = dir orelse "~/.config/home-manager";13941400 if (switch_on_init) {