4afdbd225c8f

add nina setup: detects and enables nix-command + flakes in ~/.config/nix/nix.conf

time
2026-05-30 10:03 UTC · 4 months ago
machine
koh2
agent
claude-sonnet-4-6
session
nina setup command
parent
e6b790aa94d5
4 files src/commands.zig+17 −0src/exec.zig+79 −0src/main.zig+13 −0src/output.zig+38 −0
src/commands.zig+17 −0
@@ -681,6 +681,23 @@681681     output.hello(ctx.cfg.machines);682682 }683683 684+pub fn setup(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) !void {685+    output.setupCheck();686+    const status = exec.checkNixFeatures(gpa, io, environ);687+    output.setupFeatures(status.nix_command, status.flakes);688+689+    if (status.allEnabled()) {690+        output.setupAllSet();691+        return;692+    }693+694+    output.setupEnabling();695+    const path = try exec.enableNixFeatures(gpa, io, environ);696+    defer gpa.free(path);697+698+    output.setupDone(path, exec.isNixOS(io));699+}700+684701 // --- Log helper ---685702 686703 fn logAction(ctx: Ctx, action: []const u8, gen: u32) void {

view file

src/exec.zig+79 −0
@@ -650,3 +650,82 @@650650     const size = file.length(io) catch 0;651651     file.writePositionalAll(io, entry, size) catch {};652652 }653+654+// --- Nix feature setup ---655+656+pub const NixFeatureStatus = struct {657+    nix_command: bool = false,658+    flakes: bool = false,659+660+    pub fn allEnabled(self: NixFeatureStatus) bool {661+        return self.nix_command and self.flakes;662+    }663+};664+665+fn scanConfFeatures(conf: []const u8, status: *NixFeatureStatus) void {666+    var lines = std.mem.splitScalar(u8, conf, '\n');667+    while (lines.next()) |line| {668+        const trimmed = std.mem.trim(u8, line, " \t");669+        if (!std.mem.startsWith(u8, trimmed, "experimental-features")) continue;670+        if (std.mem.indexOf(u8, trimmed, "nix-command") != null) status.nix_command = true;671+        if (std.mem.indexOf(u8, trimmed, "flakes") != null) status.flakes = true;672+    }673+}674+675+pub fn checkNixFeatures(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) NixFeatureStatus {676+    var status = NixFeatureStatus{};677+678+    if (std.Io.Dir.readFileAlloc(.cwd(), io, "/etc/nix/nix.conf", gpa, .unlimited) catch null) |conf| {679+        defer gpa.free(conf);680+        scanConfFeatures(conf, &status);681+    }682+683+    if (environ.get("HOME")) |home| {684+        var buf: [512]u8 = undefined;685+        const path = std.fmt.bufPrint(&buf, "{s}/.config/nix/nix.conf", .{home}) catch return status;686+        if (std.Io.Dir.readFileAlloc(.cwd(), io, path, gpa, .unlimited) catch null) |conf| {687+            defer gpa.free(conf);688+            scanConfFeatures(conf, &status);689+        }690+    }691+692+    return status;693+}694+695+pub fn isNixOS(io: std.Io) bool {696+    const file = std.Io.Dir.openFile(.cwd(), io, "/etc/NIXOS", .{}) catch return false;697+    file.close(io);698+    return true;699+}700+701+// Appends `experimental-features = nix-command flakes` to ~/.config/nix/nix.conf.702+// Returns the path written (caller must free).703+pub fn enableNixFeatures(gpa: std.mem.Allocator, io: std.Io, environ: *const std.process.Environ.Map) ![]const u8 {704+    const home = environ.get("HOME") orelse {705+        errors.error_info.setMessage("HOME not set", .{});706+        return error.ConfigNotFound;707+    };708+709+    const dir_path = try std.fmt.allocPrint(gpa, "{s}/.config/nix", .{home});710+    defer gpa.free(dir_path);711+    const file_path = try std.fmt.allocPrint(gpa, "{s}/.config/nix/nix.conf", .{home});712+    errdefer gpa.free(file_path);713+714+    _ = captureLocal(gpa, io, &.{ "mkdir", "-p", dir_path }) catch {};715+716+    const line = "experimental-features = nix-command flakes\n";717+    const file = std.Io.Dir.openFile(.cwd(), io, file_path, .{ .mode = .read_write }) catch718+        (std.Io.Dir.createFile(.cwd(), io, file_path, .{}) catch {719+        errors.error_info.setMessage("could not write {s}", .{file_path});720+        return error.FileNotFound;721+    });722+    defer file.close(io);723+724+    const size = file.length(io) catch 0;725+    file.writePositionalAll(io, line, size) catch {726+        errors.error_info.setMessage("could not write {s}", .{file_path});727+        return error.FileNotFound;728+    };729+730+    return file_path;731+}

view file

src/main.zig+13 −0
@@ -42,6 +42,19 @@4242         return;4343     }4444 45+    // setup runs before config so it works as an onboarding step46+    if (std.mem.eql(u8, args.command, "setup")) {47+        commands.setup(gpa, io, startup.environ_map) catch |err| {48+            const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);49+            const detail = errors.error_info.getDetail();50+            const suggestion = errors.error_info.getSuggestion();51+            output.printError(msg, detail, suggestion);52+            output.flush();53+            std.process.exit(1);54+        };55+        return;56+    }57+4558     // Load config and resolve machine4659     const cfg = config.load(arena, io, startup.environ_map) catch |err| {4760         const msg = errors.error_info.getMessage() orelse errors.staticMessage(err);

view file

src/output.zig+38 −0
@@ -608,6 +608,44 @@608608     p("{s}done\n", .{hdr()});609609 }610610 611+// --- Setup ---612+613+pub fn setupCheck() void {614+    p("{s}checking nix features\n", .{hdr()});615+}616+617+pub fn setupFeatures(nix_command: bool, flakes: bool) void {618+    p("{s}nix-command  {s}{s}{s}\n", .{619+        step(),620+        if (nix_command) c(GREEN) else "",621+        if (nix_command) "enabled" else "not enabled",622+        if (nix_command) c(RESET) else "",623+    });624+    p("{s}flakes       {s}{s}{s}\n", .{625+        step(),626+        if (flakes) c(GREEN) else "",627+        if (flakes) "enabled" else "not enabled",628+        if (flakes) c(RESET) else "",629+    });630+}631+632+pub fn setupAllSet() void {633+    p("{s}all set\n", .{hdr()});634+}635+636+pub fn setupEnabling() void {637+    p("{s}enabling experimental features\n", .{hdr()});638+}639+640+pub fn setupDone(path: []const u8, is_nixos: bool) void {641+    p("{s}wrote {s}\n", .{ step(), path });642+    p("{s}done  open a new shell to pick up the change\n", .{hdr()});643+    if (is_nixos) {644+        printSubstep("to persist across nixos-rebuild, add to configuration.nix:", .{});645+        printSubstep("  nix.settings.experimental-features = [ \"nix-command\" \"flakes\" ];", .{});646+    }647+}648+611649 pub fn pinDone(input: []const u8, commit: []const u8) void {612650     p("{s}pinned {s} -> {s}\n", .{ hdr(), input, commit });613651 }

view file