bbbdab1eedc3

setup: flake vs channel awareness in wizard and config — firstRunWizard asks channels(1)/flakes(2) after machine name, prompts for flake directory vs config path accordingly, and writes top-level 'flake = true' for flakes; NinaConfig.flake field + config parser + test; channel command group prints ':: note: using a flake system — channel commands may not apply' when cfg.flake (note: nina update is the self-updater here, so the note lives on the channel group) — build+tests green, both wizard paths and the note verified on temp HOME

time
2026-06-10 01:08 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
nina community improvements
parent
74510ec9f7dd
4 files src/commands.zig+18 −8src/config.zig+23 −0src/output.zig+28 −0src/types.zig+2 −0
src/commands.zig+18 −8
@@ -952,6 +952,8 @@952952 // --- Channel commands ---953953 954954 pub fn channel(ctx: Ctx) !void {955+    // On a flake-managed system, nix-channel operations generally don't apply.956+    if (ctx.cfg.flake) output.flakeChannelNote();955957     if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {956958         output.channelListHeader(ctx.machine.name);957959         const chans = try exec.channelList(ctx.gpa, ctx.io, &ctx.machine);@@ -1109,21 +1111,29 @@11091111     var local_buf: [256]u8 = undefined;11101112 11111113     const machine_name = output.firstRunPrompt("machine name", "my-machine", &name_buf);1112-    const config_path = output.firstRunPrompt("config path", "/etc/nixos", &path_buf);1114+1115+    // Flake-based systems don't have a traditional /etc/nixos/configuration.nix —1116+    // their config lives wherever flake.nix is. Ask, then prompt for the right1117+    // path and record `flake = true` so channel commands can warn appropriately.1118+    const use_flakes = output.firstRunManageType();1119+    const config_path = if (use_flakes)1120+        output.firstRunPrompt("flake directory", "~/nixos-config", &path_buf)1121+    else1122+        output.firstRunPrompt("config path", "/etc/nixos", &path_buf);11131123     const local_str = output.firstRunPrompt("local", "true", &local_buf);11141124 11151125     const conf_path = try config.configPath(gpa, environ);11161126     defer gpa.free(conf_path);11171127     if (std.fs.path.dirname(conf_path)) |dir| exec.ensureDir(gpa, io, dir);11181128 1129+    // `flake` is a top-level config field, so it must precede the [machine] block.1130+    const flake_line = if (use_flakes) "flake = true\n" else "";11191131     var content_buf: [1024]u8 = undefined;1120-    const content = try std.fmt.bufPrint(&content_buf,1121-        \\[machine]1122-        \\name = {s}1123-        \\config = {s}1124-        \\local = {s}1125-        \\1126-    , .{ machine_name, config_path, local_str });1132+    const content = try std.fmt.bufPrint(1133+        &content_buf,1134+        "{s}[machine]\nname = {s}\nconfig = {s}\nlocal = {s}\n",1135+        .{ flake_line, machine_name, config_path, local_str },1136+    );11271137 11281138     const file = std.Io.Dir.createFile(.cwd(), io, conf_path, .{}) catch {11291139         errors.error_info.setMessage("could not write {s}", .{conf_path});

view file

src/config.zig+23 −0
@@ -116,6 +116,8 @@116116         config.teach = parseBool(val, false);117117     } else if (std.mem.eql(u8, key, "color")) {118118         config.color = parseBool(val, true);119+    } else if (std.mem.eql(u8, key, "flake")) {120+        config.flake = parseBool(val, false);119121     }120122 }121123 @@ -158,6 +160,7 @@158160     std.debug.print("confirm={}\n", .{cfg.confirm});159161     std.debug.print("teach={}\n", .{cfg.teach});160162     std.debug.print("color={}\n", .{cfg.color});163+    std.debug.print("flake={}\n", .{cfg.flake});161164     for (cfg.machines, 0..) |m, i| {162165         std.debug.print("machine[{d}].name={s}\n", .{ i, m.name });163166         std.debug.print("machine[{d}].config={s}\n", .{ i, m.config_path });@@ -232,6 +235,26 @@232235     try std.testing.expect(config.color);233236     try std.testing.expect(!config.machines[0].local);234237     try std.testing.expect(config.machines[0].default);238+}239+240+test "parse flake flag" {241+    const with =242+        \\flake = true243+        \\244+        \\[machine]245+        \\name = box246+    ;247+    const cfg_with = try parse(std.testing.allocator, with, "/home/test");248+    defer std.testing.allocator.free(cfg_with.machines);249+    try std.testing.expect(cfg_with.flake);250+251+    const without =252+        \\[machine]253+        \\name = box254+    ;255+    const cfg_without = try parse(std.testing.allocator, without, "/home/test");256+    defer std.testing.allocator.free(cfg_without.machines);257+    try std.testing.expect(!cfg_without.flake);235258 }236259 237260 test "resolve machine by name" {

view file

src/output.zig+28 −0
@@ -985,6 +985,34 @@985985     return input;986986 }987987 988+// Ask whether the system is flake-based or channel-based. Returns true for989+// flakes. Defaults to channels (Enter or anything but '2'), echoing the choice.990+pub fn firstRunManageType() bool {991+    p("\n   how do you manage your system?\n", .{});992+    p("   {s}[1]{s} nixos channels (classic)\n", .{ c(DIM), c(RESET) });993+    p("   {s}[2]{s} nix flakes\n\n", .{ c(DIM), c(RESET) });994+    p("   {s}> {s}", .{ c(DIM), c(RESET) });995+    flush();996+    var buf: [64]u8 = undefined;997+    var i: usize = 0;998+    while (i < buf.len - 1) {999+        const n = std.posix.read(std.posix.STDIN_FILENO, buf[i .. i + 1]) catch break;1000+        if (n == 0) break;1001+        if (buf[i] == '\n') break;1002+        i += 1;1003+    }1004+    const input = std.mem.trim(u8, buf[0..i], " \t\r");1005+    const flakes = input.len > 0 and input[0] == '2';1006+    p("{s}{s}{s}\n", .{ c(DIM), if (flakes) "nix flakes" else "nixos channels", c(RESET) });1007+    return flakes;1008+}1009+1010+// Shown before channel operations on a flake-managed system, where nix-channel1011+// commands generally don't apply.1012+pub fn flakeChannelNote() void {1013+    p("{s}{s}note: using a flake system — channel commands may not apply{s}\n", .{ hdr(), c(DIM), c(RESET) });1014+}1015+9881016 pub fn firstRunWritten(path: []const u8) void {9891017     p("\n   {s}written to {s}{s}\n\n", .{ c(DIM), path, c(RESET) });9901018 }

view file

src/types.zig+2 −0
@@ -23,6 +23,8 @@2323     confirm: bool = true,2424     teach: bool = false,2525     color: bool = true,26+    // Flake-based system (vs nixos channels). Channel commands warn when set.27+    flake: bool = false,2628     machines: []Machine = &.{},2729 };2830 

view file