123const std = @import("std");4const types = @import("types.zig");5const errors = @import("errors.zig");6const config = @import("config.zig");7const output = @import("output.zig");8const exec = @import("exec.zig");9const api = @import("api.zig");10const search_tui = @import("search.zig");1112pub const Ctx = struct {13 gpa: std.mem.Allocator,14 io: std.Io,15 environ: *const std.process.Environ.Map,16 machine: types.Machine,17 cfg: types.NinaConfig,18 args: []const []const u8, 19 sub: []const u8, 20 dry: bool,21 check: bool,22 no_apply: bool,23 all: bool,24 last: u32,25 json: bool,26};2728pub fn apply(ctx: Ctx) !void {29 if (ctx.dry) {30 output.applyDry(ctx.machine.name);31 const code = try exec.nixosRebuildDryActivate(ctx.io, &ctx.machine, ctx.gpa);32 if (code != 0) return error.BuildFailed;33 output.applyDryDone();34 return;35 }36 if (ctx.check) {37 return check(ctx);38 }3940 output.applyStart(ctx.machine.name);41 const start = std.Io.Clock.awake.now(ctx.io);4243 const code = exec.nixosRebuildSwitch(ctx.io, &ctx.machine, ctx.gpa) catch |e| {44 output.applyFailed("");45 return e;46 };4748 if (code != 0) {49 output.applyFailed("");50 return error.BuildFailed;51 }5253 const elapsed_ns = @max(0, start.untilNow(ctx.io, .awake).toNanoseconds());54 const elapsed: u64 = @intCast(@divFloor(elapsed_ns, 1_000_000));55 const gen = exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine) catch 0;56 output.applyDone(gen, elapsed);57 logAction(ctx, "apply", gen);58}5960pub fn check(ctx: Ctx) !void {61 output.checkStart(ctx.machine.name);62 const code = try exec.nixosRebuildBuild(ctx.io, &ctx.machine, ctx.gpa);63 if (code != 0) {64 output.checkFailed("");65 return error.BuildFailed;66 }67 output.checkOk();68}6970pub fn back(ctx: Ctx) !void {71 const gens = try exec.getGenerations(ctx.gpa, ctx.io, &ctx.machine);72 defer ctx.gpa.free(gens);7374 var current: u32 = 0;75 for (gens) |g| if (g.current) { current = g.number; };76 const prev = if (current > 1) current - 1 else current;7778 output.backStart(ctx.machine.name, current, prev);79 if (ctx.cfg.confirm and !output.confirm("")) return;8081 const code = try exec.rollback(ctx.io, &ctx.machine, ctx.gpa);82 if (code != 0) return error.BuildFailed;83 output.generationDone(prev);84}8586pub fn go(ctx: Ctx) !void {87 if (ctx.args.len == 0) return error.Internal;88 const target = std.fmt.parseInt(u32, ctx.args[0], 10) catch return error.Internal;89 const current = try exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine);9091 output.goStart(ctx.machine.name, current, target);92 if (ctx.cfg.confirm and !output.confirm("")) return;9394 const code = try exec.switchGeneration(ctx.io, &ctx.machine, ctx.gpa, target);95 if (code != 0) return error.ProcessFailed;96 output.generationDone(target);97}9899pub fn history(ctx: Ctx) !void {100 const gens = try exec.getGenerations(ctx.gpa, ctx.io, &ctx.machine);101 defer ctx.gpa.free(gens);102 output.historyHeader(ctx.machine.name);103 for (gens) |g| output.historyRow(g);104}105106pub fn genCmd(ctx: Ctx) !void {107 if (std.mem.eql(u8, ctx.sub, "list")) return history(ctx);108 if (std.mem.eql(u8, ctx.sub, "current")) {109 const n = try exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine);110 output.genCurrent(n);111 return;112 }113 if (std.mem.eql(u8, ctx.sub, "delete")) {114 if (ctx.args.len == 0) return error.Internal;115 if (std.mem.eql(u8, ctx.args[0], "old")) {116 if (ctx.cfg.confirm and !output.confirm("")) return;117 _ = try exec.deleteOldGenerations(ctx.io, &ctx.machine, ctx.gpa);118 } else {119 const n = std.fmt.parseInt(u32, ctx.args[0], 10) catch return error.Internal;120 if (ctx.cfg.confirm and !output.confirm("")) return;121 _ = try exec.deleteGeneration(ctx.io, &ctx.machine, ctx.gpa, n);122 }123 output.done();124 return;125 }126 return error.Internal;127}128129pub fn clean(ctx: Ctx) !void {130 const gens = try exec.getGenerations(ctx.gpa, ctx.io, &ctx.machine);131 defer ctx.gpa.free(gens);132 output.cleanStart(ctx.machine.name, ctx.cfg.generations, @intCast(gens.len));133 if (ctx.cfg.confirm and !output.confirm("removing — cannot be undone")) return;134 const all = ctx.all or std.mem.eql(u8, ctx.sub, "all");135 _ = try exec.collectGarbage(ctx.io, &ctx.machine, ctx.gpa, all, ctx.cfg.generations);136 output.cleanDone("?");137}138139pub fn statusCmd(ctx: Ctx) !void {140 output.statusHeader();141 const gen = exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine) catch 0;142 output.statusRow(ctx.machine.name, gen, "", "", true);143}144145pub fn diff(ctx: Ctx) !void {146 const gens = try exec.getGenerations(ctx.gpa, ctx.io, &ctx.machine);147 defer ctx.gpa.free(gens);148 var current: u32 = 0;149 var prev: u32 = 0;150 for (gens) |g| {151 if (g.current) current = g.number;152 if (!g.current and g.number > prev and g.number < current) prev = g.number;153 }154 if (ctx.args.len >= 2) {155 const a = std.fmt.parseInt(u32, ctx.args[0], 10) catch current - 1;156 const b = std.fmt.parseInt(u32, ctx.args[1], 10) catch current;157 output.diffHeader(a, b, ctx.machine.name);158 if (try exec.diffClosures(ctx.io, &ctx.machine, ctx.gpa, a, b) != 0) return error.ProcessFailed;159 } else {160 output.diffHeader(if (prev > 0) prev else current - 1, current, ctx.machine.name);161 if (try exec.diffClosures(ctx.io, &ctx.machine, ctx.gpa, if (prev > 0) prev else current - 1, current) != 0) return error.ProcessFailed;162 }163}164165pub fn doctor(ctx: Ctx) !void {166 output.doctorHeader(ctx.machine.name);167 const checks = try exec.runDoctorChecks(ctx.gpa, ctx.io, &ctx.machine);168 defer ctx.gpa.free(checks);169 var warnings: u32 = 0;170 var failures: u32 = 0;171 for (checks) |ch| {172 output.doctorRow(ch);173 if (ch.status == .warn) warnings += 1;174 if (ch.status == .fail) failures += 1;175 }176 output.doctorSummary(warnings, failures);177}178179pub fn update(ctx: Ctx) !void {180 output.updateStart(ctx.machine.name);181 _ = try exec.channelUpdate(ctx.io, &ctx.machine, ctx.gpa);182 output.updateDone();183}184185pub fn upgrade(ctx: Ctx) !void {186 output.upgradeStart(ctx.machine.name);187 output.printSubstep("updating channels", .{});188 _ = exec.channelUpdate(ctx.io, &ctx.machine, ctx.gpa) catch {};189 output.printSubstep("rebuilding", .{});190 const start = std.Io.Clock.awake.now(ctx.io);191 const code = try exec.nixosRebuildSwitch(ctx.io, &ctx.machine, ctx.gpa);192 if (code != 0) {193 output.applyFailed("");194 return error.BuildFailed;195 }196 const elapsed_ns = @max(0, start.untilNow(ctx.io, .awake).toNanoseconds());197 const elapsed: u64 = @intCast(@divFloor(elapsed_ns, 1_000_000));198 const gen = exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine) catch 0;199 output.applyDone(gen, elapsed);200}201202pub fn log(ctx: Ctx) !void {203 output.logHeader(ctx.machine.name);204 const content = exec.readNinaLog(ctx.gpa, ctx.io, ctx.environ, ctx.last) catch {205 206 return;207 };208 defer ctx.gpa.free(content);209 var lines = std.mem.splitScalar(u8, content, '\n');210 while (lines.next()) |line| {211 if (line.len == 0) continue;212 var parts = std.mem.tokenizeScalar(u8, line, '\t');213 const date = parts.next() orelse continue;214 const time = parts.next() orelse "";215 const action = parts.next() orelse "";216 const detail = parts.next() orelse "";217 output.logRow(date, time, action, detail);218 }219}220221pub fn edit(ctx: Ctx) !void {222 const hardware = ctx.args.len > 0 and std.mem.eql(u8, ctx.args[0], "hardware");223 const filename = if (hardware) "hardware-configuration.nix" else "configuration.nix";224 const path = try std.fmt.allocPrint(ctx.gpa, "{s}/{s}", .{ ctx.machine.config_path, filename });225 defer ctx.gpa.free(path);226 output.editOpen(path, ctx.cfg.editor);227 _ = try exec.openEditor(ctx.io, ctx.gpa, ctx.cfg.editor, path, null);228}229230pub fn fmt(ctx: Ctx) !void {231 const path = try std.fmt.allocPrint(ctx.gpa, "{s}/configuration.nix", .{ctx.machine.config_path});232 defer ctx.gpa.free(path);233 if (ctx.check) {234 const code = try exec.nixpkgsFmt(ctx.io, &ctx.machine, ctx.gpa, path, true);235 if (code != 0) output.fmtNeedsFormat() else output.fmtDone();236 return;237 }238 output.fmtStart(path);239 _ = try exec.nixpkgsFmt(ctx.io, &ctx.machine, ctx.gpa, path, false);240 output.fmtDone();241}242243pub fn repl(ctx: Ctx) !void {244 output.replStart();245 _ = try exec.nixRepl(ctx.io, &ctx.machine, ctx.gpa);246}247248pub fn develop(ctx: Ctx) !void {249 if (std.mem.eql(u8, ctx.sub, "show")) {250 _ = try exec.nixBuild(ctx.io, &ctx.machine, ctx.gpa, null);251 return;252 }253 const run_cmd: ?[]const u8 = if (ctx.args.len > 0 and std.mem.eql(u8, ctx.sub, "run"))254 ctx.args[0]255 else256 null;257 output.developStart(&.{});258 _ = try exec.nixDevelop(ctx.io, &ctx.machine, ctx.gpa, run_cmd);259 output.developDone();260}261262pub fn info(ctx: Ctx) !void {263 output.infoHeader(ctx.machine.name);264 const nixos_ver = exec.systemInfo(ctx.gpa, ctx.io, &ctx.machine) catch try ctx.gpa.dupe(u8, "unknown");265 defer ctx.gpa.free(nixos_ver);266 output.infoRow("nixos", std.mem.trim(u8, nixos_ver, " \t\n\r"));267268 const kernel = exec.kernelInfo(ctx.gpa, ctx.io, &ctx.machine) catch try ctx.gpa.dupe(u8, "unknown");269 defer ctx.gpa.free(kernel);270 output.infoRow("kernel", std.mem.trim(u8, kernel, " \t\n\r"));271272 const uptime = exec.uptimeInfo(ctx.gpa, ctx.io, &ctx.machine) catch try ctx.gpa.dupe(u8, "unknown");273 defer ctx.gpa.free(uptime);274 output.infoRow("uptime", std.mem.trim(u8, uptime, " \t\n\r"));275}276277pub fn boot(ctx: Ctx) !void {278 output.bootHeader(ctx.machine.name);279 const entries = exec.bootEntries(ctx.gpa, ctx.io, &ctx.machine) catch &.{};280 defer ctx.gpa.free(entries);281 for (entries, 0..) |entry, i| output.bootEntry(entry, i);282}283284pub fn searchCmd(ctx: Ctx) !void {285 const query = if (ctx.args.len > 0) ctx.args[0] else "";286 const result = try search_tui.run(ctx.gpa, ctx.io, query, .packages);287 if (result) |r| {288 installFromResult(ctx, r) catch |e| return e;289 }290}291292pub fn optionCmd(ctx: Ctx) !void {293 const query = if (ctx.args.len > 0) ctx.args[0] else "";294 _ = try search_tui.run(ctx.gpa, ctx.io, query, .options);295}296297pub fn install(ctx: Ctx) !void {298 const pkg_name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;299300 301 const pkgs = api.searchPackages(ctx.gpa, ctx.io, pkg_name) catch {302 errors.error_info.setMessage("'{s}' not found in nixpkgs", .{pkg_name});303 errors.error_info.setSuggestion("nina search {s}", .{pkg_name});304 return error.PackageNotFound;305 };306 defer ctx.gpa.free(pkgs);307308 if (pkgs.len == 0) {309 errors.error_info.setMessage("'{s}' not found in nixpkgs", .{pkg_name});310 errors.error_info.setSuggestion("nina search {s}", .{pkg_name});311 return error.PackageNotFound;312 }313314 315 316 var found = pkgs[0];317 for (pkgs) |p| {318 if (std.mem.eql(u8, p.pname, pkg_name)) {319 found = p;320 break;321 }322 }323 output.installSelectPrompt(found.pname, found.version);324325 var choice_buf: [8]u8 = undefined;326 var choice_len: usize = 0;327 while (choice_len < choice_buf.len) {328 const n = std.posix.read(std.posix.STDIN_FILENO, choice_buf[choice_len .. choice_len + 1]) catch break;329 if (n == 0) break;330 if (choice_buf[choice_len] == '\n') break;331 choice_len += 1;332 }333 const choice = std.mem.trim(u8, choice_buf[0..choice_len], " \t");334335 if (choice.len == 0 or choice[0] == 'i' or choice[0] == 'I') {336 try profileInstallPkg(ctx, found);337 } else if (choice[0] == 's' or choice[0] == 'S') {338 try systemInstallPkg(ctx, found);339 } else if (choice[0] == 't' or choice[0] == 'T') {340 try tryPkg(ctx, found);341 }342}343344fn installFromResult(ctx: Ctx, result: types.SearchResult) !void {345 switch (result.action) {346 .profile => try profileInstallPkg(ctx, result.package),347 .system => try systemInstallPkg(ctx, result.package),348 }349}350351fn profileInstallPkg(ctx: Ctx, npkg: types.NixPackage) !void {352 output.installStart(npkg.pname, .profile);353 output.printSubstep("nix profile install nixpkgs#{s}", .{npkg.attr});354 output.printTeach(try std.fmt.allocPrint(ctx.gpa, "nix profile install nixpkgs#{s}", .{npkg.attr}));355 const code = try exec.profileInstall(ctx.io, &ctx.machine, ctx.gpa, npkg.attr);356 if (code != 0) return error.ProcessFailed;357 output.installDone(npkg.pname);358}359360fn systemInstallPkg(ctx: Ctx, npkg: types.NixPackage) !void {361 const config_path = try std.fmt.allocPrint(ctx.gpa, "{s}/configuration.nix", .{ctx.machine.config_path});362 defer ctx.gpa.free(config_path);363 const line = api.findSystemPackagesLine(ctx.io, config_path) orelse 1;364 output.installEditorPrompt(npkg.pname, config_path, line, ctx.cfg.editor);365 _ = try exec.openEditor(ctx.io, ctx.gpa, ctx.cfg.editor, config_path, line);366 if (ctx.cfg.confirm) {367 if (output.confirm("apply now? ")) {368 try apply(ctx);369 }370 }371}372373fn tryPkg(ctx: Ctx, npkg: types.NixPackage) !void {374 output.tryStart(npkg.pname);375 output.printSubstep("nix shell nixpkgs#{s}", .{npkg.attr});376 _ = try exec.nixShell(ctx.io, &ctx.machine, ctx.gpa, npkg.attr);377 output.tryDone();378}379380pub fn remove(ctx: Ctx) !void {381 const pkg_name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;382 output.installStart(pkg_name, .profile); 383 const code = try exec.profileRemove(ctx.io, &ctx.machine, ctx.gpa, pkg_name);384 if (code != 0) return error.ProcessFailed;385 output.done();386}387388pub fn list(ctx: Ctx) !void {389 output.listHeader();390 const profile_out = exec.profileList(ctx.gpa, ctx.io, &ctx.machine) catch null;391 if (profile_out) |out| {392 defer ctx.gpa.free(out);393 output.listSection("profile");394 var lines = std.mem.splitScalar(u8, out, '\n');395 while (lines.next()) |line| {396 const t = std.mem.trim(u8, line, " \t\r");397 if (t.len == 0) continue;398 output.listPkg(t, "");399 }400 }401}402403pub fn tryCmd(ctx: Ctx) !void {404 const pkg_name_arg = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;405 const shell_pkg = types.NixPackage{ .attr = pkg_name_arg, .pname = pkg_name_arg, .version = "", .description = "" };406 try tryPkg(ctx, shell_pkg);407}408409410411pub fn service(ctx: Ctx) !void {412 if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {413 output.serviceListHeader(ctx.machine.name);414 const svcs = exec.serviceList(ctx.gpa, ctx.io, &ctx.machine) catch &.{};415 defer ctx.gpa.free(svcs);416 for (svcs) |s| output.serviceRow(s);417 return;418 }419 if (std.mem.eql(u8, ctx.sub, "logs")) {420 const name = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;421 const follow = ctx.args.len > 1 and std.mem.eql(u8, ctx.args[1], "-f");422 output.serviceLogsHeader(name, ctx.last, ctx.machine.name);423 _ = try exec.serviceLogs(ctx.io, &ctx.machine, ctx.gpa, name, ctx.last, follow);424 return;425 }426 if (std.mem.eql(u8, ctx.sub, "status")) {427 const name = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;428 output.serviceVerb("status", name, ctx.machine.name);429 _ = try exec.serviceCtl(ctx.io, &ctx.machine, ctx.gpa, "status", name);430 return;431 }432 433 const name = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;434 if (ctx.cfg.confirm and (std.mem.eql(u8, ctx.sub, "stop") or std.mem.eql(u8, ctx.sub, "disable"))) {435 if (!output.confirm("")) return;436 }437 output.serviceVerb(ctx.sub, name, ctx.machine.name);438 _ = try exec.serviceCtl(ctx.io, &ctx.machine, ctx.gpa, ctx.sub, name);439 output.serviceDone();440}441442443444pub fn flake(ctx: Ctx) !void {445 if (std.mem.eql(u8, ctx.sub, "update")) {446 output.flakeUpdateStart();447 const input: ?[]const u8 = if (ctx.args.len > 0) ctx.args[0] else null;448 _ = try exec.flakeUpdate(ctx.io, &ctx.machine, ctx.gpa, input);449 output.flakeLockWritten();450 return;451 }452 if (std.mem.eql(u8, ctx.sub, "check")) {453 output.flakeCheckStart();454 const code = try exec.flakeCheck(ctx.io, &ctx.machine, ctx.gpa);455 if (code == 0) output.flakeValid() else output.checkFailed("");456 return;457 }458 if (std.mem.eql(u8, ctx.sub, "show")) {459 const json_out = exec.flakeShow(ctx.gpa, ctx.io, &ctx.machine) catch null;460 if (json_out) |j| {461 defer ctx.gpa.free(j);462 output.flakeShowHeader(".");463 464 const parsed = std.json.parseFromSlice(std.json.Value, ctx.gpa, j, .{}) catch return;465 defer parsed.deinit();466 if (parsed.value == .object) {467 var it = parsed.value.object.iterator();468 while (it.next()) |entry| {469 if (entry.value_ptr.* == .object) {470 var inner = entry.value_ptr.object.iterator();471 while (inner.next()) |e2| {472 const desc = if (e2.value_ptr.* == .object)473 if (e2.value_ptr.object.get("description")) |d|474 if (d == .string) d.string else ""475 else ""476 else "";477 output.flakeShowEntry(entry.key_ptr.*, e2.key_ptr.*, desc);478 }479 }480 }481 }482 }483 return;484 }485 if (std.mem.eql(u8, ctx.sub, "init")) {486 _ = try exec.flakeInit(ctx.io, &ctx.machine, ctx.gpa);487 return;488 }489 if (std.mem.eql(u8, ctx.sub, "lock")) {490 _ = try exec.flakeLock(ctx.io, &ctx.machine, ctx.gpa);491 return;492 }493 if (std.mem.eql(u8, ctx.sub, "clone")) {494 const url = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;495 _ = try exec.flakeClone(ctx.io, &ctx.machine, ctx.gpa, url);496 return;497 }498}499500501502pub fn store(ctx: Ctx) !void {503 if (std.mem.eql(u8, ctx.sub, "gc")) {504 _ = try exec.storeGc(ctx.io, &ctx.machine, ctx.gpa);505 return;506 }507 if (std.mem.eql(u8, ctx.sub, "verify")) {508 output.storeVerifyStart();509 _ = try exec.storeVerify(ctx.io, &ctx.machine, ctx.gpa);510 return;511 }512 if (std.mem.eql(u8, ctx.sub, "repair")) {513 if (ctx.cfg.confirm and !output.confirm("")) return;514 output.storeRepairStart();515 _ = try exec.storeRepair(ctx.io, &ctx.machine, ctx.gpa);516 return;517 }518 if (std.mem.eql(u8, ctx.sub, "info")) {519 const si = try exec.storeInfo(ctx.gpa, ctx.io, &ctx.machine);520 output.storeInfo(si);521 return;522 }523 if (std.mem.eql(u8, ctx.sub, "path")) {524 const attr = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;525 const path = try exec.storePath(ctx.gpa, ctx.io, &ctx.machine, attr);526 defer ctx.gpa.free(path);527 output.storePath(path);528 return;529 }530 531 const si = try exec.storeInfo(ctx.gpa, ctx.io, &ctx.machine);532 output.storeInfo(si);533}534535536537pub fn channel(ctx: Ctx) !void {538 if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {539 output.channelListHeader(ctx.machine.name);540 const chans = try exec.channelList(ctx.gpa, ctx.io, &ctx.machine);541 defer ctx.gpa.free(chans);542 for (chans) |ch| output.channelRow(ch);543 return;544 }545 if (std.mem.eql(u8, ctx.sub, "add")) {546 const url = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;547 const name = if (ctx.args.len > 1) ctx.args[1] else "";548 _ = try exec.channelAdd(ctx.io, &ctx.machine, ctx.gpa, url, name);549 output.channelDone();550 return;551 }552 if (std.mem.eql(u8, ctx.sub, "remove")) {553 const name = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;554 _ = try exec.channelRemove(ctx.io, &ctx.machine, ctx.gpa, name);555 output.channelDone();556 return;557 }558 if (std.mem.eql(u8, ctx.sub, "update")) {559 return update(ctx);560 }561}562563564565pub fn profile(ctx: Ctx) !void {566 if (std.mem.eql(u8, ctx.sub, "list") or ctx.sub.len == 0) {567 output.profileListHeader();568 const out = exec.profileList(ctx.gpa, ctx.io, &ctx.machine) catch null;569 if (out) |o| {570 defer ctx.gpa.free(o);571 var lines = std.mem.splitScalar(u8, o, '\n');572 while (lines.next()) |line| {573 const t = std.mem.trim(u8, line, " \t\r");574 if (t.len == 0) continue;575 output.profileRow(t, "", "");576 }577 }578 return;579 }580 if (std.mem.eql(u8, ctx.sub, "install")) {581 const attr = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;582 _ = try exec.profileInstall(ctx.io, &ctx.machine, ctx.gpa, attr);583 output.done();584 return;585 }586 if (std.mem.eql(u8, ctx.sub, "remove")) {587 const attr = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;588 _ = try exec.profileRemove(ctx.io, &ctx.machine, ctx.gpa, attr);589 output.done();590 return;591 }592 if (std.mem.eql(u8, ctx.sub, "upgrade")) {593 _ = try exec.profileUpgrade(ctx.io, &ctx.machine, ctx.gpa);594 output.done();595 return;596 }597}598599600601pub fn pkg(ctx: Ctx) !void {602 const name = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;603 if (std.mem.eql(u8, ctx.sub, "why")) {604 _ = try exec.pkgWhy(ctx.io, &ctx.machine, ctx.gpa, name);605 return;606 }607 if (std.mem.eql(u8, ctx.sub, "deps")) {608 _ = try exec.pkgDeps(ctx.io, &ctx.machine, ctx.gpa, name);609 return;610 }611 if (std.mem.eql(u8, ctx.sub, "size")) {612 const s = exec.pkgSize(ctx.gpa, ctx.io, &ctx.machine, name) catch null;613 if (s) |sz| {614 defer ctx.gpa.free(sz);615 output.pkgResult("size", sz);616 }617 return;618 }619 if (std.mem.eql(u8, ctx.sub, "path")) {620 const p = try exec.storePath(ctx.gpa, ctx.io, &ctx.machine, name);621 defer ctx.gpa.free(p);622 output.pkgResult("path", p);623 return;624 }625 if (std.mem.eql(u8, ctx.sub, "closure")) {626 _ = try exec.pkgClosure(ctx.io, &ctx.machine, ctx.gpa, name);627 return;628 }629}630631632633pub fn pin(ctx: Ctx) !void {634 const input = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;635 const commit = if (ctx.args.len > 1) ctx.args[1] else return error.Internal;636 exec.editFlakeLock(ctx.gpa, input, commit) catch return error.ProcessFailed;637 output.pinDone(input, commit);638}639640pub fn unpin(ctx: Ctx) !void {641 const input = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;642 _ = try exec.flakeUpdate(ctx.io, &ctx.machine, ctx.gpa, input);643 output.unpinDone(input);644}645646647648pub fn hash(ctx: Ctx) !void {649 const path = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;650 const h = try exec.nixHash(ctx.gpa, ctx.io, &ctx.machine, path);651 defer ctx.gpa.free(h);652 output.hashResult(std.mem.trim(u8, h, " \t\n\r"));653}654655pub fn fetch(ctx: Ctx) !void {656 const url = if (ctx.args.len > 0) ctx.args[0] else return error.Internal;657 const h = try exec.nixFetch(ctx.gpa, ctx.io, &ctx.machine, url);658 defer ctx.gpa.free(h);659 output.fetchResult(std.mem.trim(u8, h, " \t\n\r"), url);660}661662663664pub fn build(ctx: Ctx) !void {665 const attr: ?[]const u8 = if (ctx.args.len > 0) ctx.args[0] else null;666 output.buildStart(attr orelse "default");667 const code = try exec.nixBuild(ctx.io, &ctx.machine, ctx.gpa, attr);668 if (code != 0) return error.BuildFailed;669 output.buildDone();670}671672pub fn runCmd(ctx: Ctx) !void {673 const pkg_arg = if (ctx.args.len > 0) ctx.args[0] else return error.PackageNotFound;674 output.runStart(pkg_arg);675 _ = try exec.nixRun(ctx.io, &ctx.machine, ctx.gpa, pkg_arg);676}677678679680pub fn hello(ctx: Ctx) !void {681 output.hello(ctx.cfg.machines);682}683684pub 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);688689 if (status.allEnabled()) {690 output.setupAllSet();691 return;692 }693694 output.setupEnabling();695 const path = try exec.enableNixFeatures(gpa, io, environ);696 defer gpa.free(path);697698 output.setupDone(path, exec.isNixOS(io));699}700701702703fn logAction(ctx: Ctx, action: []const u8, gen: u32) void {704 var buf: [128]u8 = undefined;705 const entry = std.fmt.bufPrint(&buf, "{s}\t{s}\t{s}\tgen {d}\n", .{706 "2026", "00:00", action, gen,707 }) catch return;708 exec.appendNinaLog(ctx.io, ctx.environ, entry);709}