d7d4dc71a0ea
setup wizard: scan for config and flake.nix, smart defaults — firstRunWizard now scans /etc/nixos (scanConfig: flake.nix -> flake, configuration.nix -> channel, else none), reports the detection, and pre-fills the config-path prompt (output.firstRunPromptDefault, [default] enter-to-confirm); flake-ness is re-detected at the chosen path via exec.dirHasFlake and written as 'flake = true' — never asked. Dropped the [1]/[2] manage-type question and the local prompt (defaults local=true); removed now-dead firstRunManageType. exec.pathExists made pub, configHasFlake refactored onto dirHasFlake. Verified both branches: plain dir -> no flake line; dir with flake.nix -> flake=true. build+tests green
- time
- 2026-06-10 01:32 UTC · 4 months ago
- machine
- koh2
- agent
- claude-opus-4-8
- session
- flake + home manager support
- parent
- f41c183586ea
↓ nina-d7d4dc71.tar.gzatlas at this save
files changed3 files
src/commands.zig+31 −14src/exec.zig+9 −6src/output.zig+29 −11src/commands.zig+31 −14
@@ -1467,28 +1467,45 @@14671467 output.hello(ctx.cfg.machines);14681468 }14691469 1470-// Runs when ~/.nina.conf is absent on first invocation (interactive TTY only).1471-// Prompts for machine name, config path, and local flag, then writes the file.1470+const ConfigKind = enum { flake, channel, none };1471+1472+// Scan the conventional /etc/nixos for a flake.nix (flake-based) or a1473+// configuration.nix (channel-based) to pre-fill the wizard's defaults.1474+fn scanConfig(io: std.Io) ConfigKind {1475+ if (exec.pathExists(io, "/etc/nixos/flake.nix")) return .flake;1476+ if (exec.pathExists(io, "/etc/nixos/configuration.nix")) return .channel;1477+ return .none;1478+}1479+1480+// Runs when the config is absent on first invocation (interactive TTY only).1481+// Scans for an existing nixos config, detects flake vs channel, and offers a1482+// smart default path — flake-ness is detected, never asked.14721483 pub fn firstRunWizard(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) !void {14731484 output.firstRunBanner();14741485 output.firstRunNoConfig();14751486 14761487 var name_buf: [256]u8 = undefined;14771488 var path_buf: [256]u8 = undefined;1478- var local_buf: [256]u8 = undefined;14791489 14801490 const machine_name = output.firstRunPrompt("machine name", "my-machine", &name_buf);14811491 1482- // Flake-based systems don't have a traditional /etc/nixos/configuration.nix —1483- // their config lives wherever flake.nix is. Ask, then prompt for the right1484- // path and record `flake = true` so channel commands can warn appropriately.1485- const use_flakes = output.firstRunManageType();1486- const config_path = if (use_flakes)1487- output.firstRunPrompt("flake directory", "~/nixos-config", &path_buf)1488- else1489- output.firstRunPrompt("config path", "/etc/nixos", &path_buf);1490- const local_str = output.firstRunPrompt("local", "true", &local_buf);1492+ output.firstRunScanning();1493+ const kind = scanConfig(io);1494+ switch (kind) {1495+ .flake => output.firstRunDetected("/etc/nixos/flake.nix", true),1496+ .channel => output.firstRunDetected("/etc/nixos/configuration.nix", false),1497+ .none => output.firstRunNoneFound(),1498+ }14911499 1500+ // Detected systems default to /etc/nixos (press enter to confirm); when1501+ // nothing was found, leave the default empty so the user must type a path.1502+ const default_path: []const u8 = if (kind == .none) "" else "/etc/nixos";1503+ const config_path = output.firstRunPromptDefault("config path", default_path, kind != .none, &path_buf);1504+1505+ // Re-detect at the chosen path in case the user pointed elsewhere — flake.nix1506+ // there means a flake system, recorded so channel commands warn appropriately.1507+ const use_flakes = exec.dirHasFlake(gpa, io, config_path);1508+14921509 const conf_path = try config.configPath(gpa, environ);14931510 defer gpa.free(conf_path);14941511 if (std.fs.path.dirname(conf_path)) |dir| exec.ensureDir(gpa, io, dir);@@ -1498,8 +1515,8 @@14981515 var content_buf: [1024]u8 = undefined;14991516 const content = try std.fmt.bufPrint(15001517 &content_buf,1501- "{s}[machine]\nname = {s}\nconfig = {s}\nlocal = {s}\n",1502- .{ flake_line, machine_name, config_path, local_str },1518+ "{s}[machine]\nname = {s}\nconfig = {s}\nlocal = true\n",1519+ .{ flake_line, machine_name, config_path },15031520 );15041521 15051522 const file = std.Io.Dir.createFile(.cwd(), io, conf_path, .{}) catch {src/exec.zig+9 −6
@@ -175,13 +175,16 @@175175 // machine — the file lives on the local filesystem. Remote flake detection would176176 // need an ssh round-trip; a remote flake at the default /etc/nixos needs no flag177177 // anyway, so we don't bother.178+// True if `dir` contains a flake.nix (local filesystem check).179+pub fn dirHasFlake(gpa: std.mem.Allocator, io: std.Io, dir: []const u8) bool {180+ const flake_path = std.fs.path.join(gpa, &.{ dir, "flake.nix" }) catch return false;181+ defer gpa.free(flake_path);182+ return pathExists(io, flake_path);183+}184+178185 pub fn configHasFlake(gpa: std.mem.Allocator, io: std.Io, machine: *const types.Machine) bool {179186 if (!machine.local) return false;180- const flake_path = std.fs.path.join(gpa, &.{ machine.config_path, "flake.nix" }) catch return false;181- defer gpa.free(flake_path);182- const f = std.Io.Dir.openFile(.cwd(), io, flake_path, .{}) catch return false;183- f.close(io);184- return true;187+ return dirHasFlake(gpa, io, machine.config_path);185188 }186189 187190 // nixos-rebuild auto-detects /etc/nixos/flake.nix, so the --flake flag is only@@ -1082,7 +1085,7 @@10821085 return std.fmt.allocPrint(gpa, "{s}/home.nix", .{dir}) catch null;10831086 }10841087 1085-fn pathExists(io: std.Io, path: []const u8) bool {1088+pub fn pathExists(io: std.Io, path: []const u8) bool {10861089 const f = std.Io.Dir.openFile(.cwd(), io, path, .{}) catch return false;10871090 f.close(io);10881091 return true;src/output.zig+29 −11
@@ -1085,15 +1085,30 @@10851085 return input;10861086 }10871087 1088-// Ask whether the system is flake-based or channel-based. Returns true for1089-// flakes. Defaults to channels (Enter or anything but '2'), echoing the choice.1090-pub fn firstRunManageType() bool {1091- p("\n how do you manage your system?\n", .{});1092- p(" {s}[1]{s} nixos channels (classic)\n", .{ c(DIM), c(RESET) });1093- p(" {s}[2]{s} nix flakes\n\n", .{ c(DIM), c(RESET) });1094- p(" {s}> {s}", .{ c(DIM), c(RESET) });1088+pub fn firstRunScanning() void {1089+ p("\n {s}:: scanning for config...{s}\n", .{ c(DIM), c(RESET) });1090+}1091+1092+// Report what the scan found at /etc/nixos, noting flake vs channel.1093+pub fn firstRunDetected(path: []const u8, is_flake: bool) void {1094+ const kind = if (is_flake) "flake-based system" else "channel-based system";1095+ p(" {s}-> found {s}{s} {s}({s}){s}\n\n", .{ c(CYAN), path, c(RESET), c(DIM), kind, c(RESET) });1096+}1097+1098+pub fn firstRunNoneFound() void {1099+ p(" {s}-> nothing found at /etc/nixos{s}\n\n", .{ c(DIM), c(RESET) });1100+}1101+1102+// Prompt with a pre-filled default shown in brackets. Empty input accepts the1103+// default. When `detected` and a default is present, note that enter confirms.1104+pub fn firstRunPromptDefault(label: []const u8, default: []const u8, detected: bool, buf: []u8) []const u8 {1105+ if (default.len > 0) {1106+ p(" {s:<14} {s}[{s}]{s} > ", .{ label, c(DIM), default, c(RESET) });1107+ if (detected) p("{s}enter to confirm{s} ", .{ c(DIM), c(RESET) });1108+ } else {1109+ p(" {s:<14} {s}> {s}", .{ label, c(DIM), c(RESET) });1110+ }10951111 flush();1096- var buf: [64]u8 = undefined;10971112 var i: usize = 0;10981113 while (i < buf.len - 1) {10991114 const n = std.posix.read(std.posix.STDIN_FILENO, buf[i .. i + 1]) catch break;@@ -1102,9 +1117,12 @@11021117 i += 1;11031118 }11041119 const input = std.mem.trim(u8, buf[0..i], " \t\r");1105- const flakes = input.len > 0 and input[0] == '2';1106- p("{s}{s}{s}\n", .{ c(DIM), if (flakes) "nix flakes" else "nixos channels", c(RESET) });1107- return flakes;1120+ if (input.len == 0) {1121+ p("{s}{s}{s}\n", .{ c(DIM), default, c(RESET) });1122+ flush();1123+ return default;1124+ }1125+ return input;11081126 }11091127 11101128 // Shown before channel operations on a flake-managed system, where nix-channel