8aa0bab7f860

fix status empty disk/time columns: add exec.systemStatus (current-system closure size via path-info -S /run/current-system + system profile symlink age) and render in status row

time
2026-06-04 23:13 UTC · 4 months ago
machine
koh2
agent
claude-opus-4-8
session
status disk and time columns
parent
78c1d70126e3
2 files src/commands.zig+4 −1src/exec.zig+41 −0
src/commands.zig+4 −1
@@ -143,7 +143,10 @@143143 pub fn statusCmd(ctx: Ctx) !void {144144     output.statusHeader();145145     const gen = exec.getCurrentGeneration(ctx.gpa, ctx.io, &ctx.machine) catch 0;146-    output.statusRow(ctx.machine.name, gen, "", "", true);146+    const st = exec.systemStatus(ctx.gpa, ctx.io, &ctx.machine);147+    defer ctx.gpa.free(st.size);148+    defer ctx.gpa.free(st.age);149+    output.statusRow(ctx.machine.name, gen, st.age, st.size, true);147150 }148151 149152 pub fn diff(ctx: Ctx) !void {

view file

src/exec.zig+41 −0
@@ -360,6 +360,47 @@360360     return stream(io, machine, gpa, &.{ "nix", "store", "verify", "--all" });361361 }362362 363+pub const SystemStatus = struct { size: []u8, age: []u8 };364+365+// One read-only pass for `nina status`: the current system's closure size (a366+// single `path-info -S` of /run/current-system — fast, unlike -S over the whole367+// store) and seconds since the system profile was last switched (its symlink368+// mtime). Both are world-readable; no root needed.369+const STATUS_SCRIPT =370+    \\sz=$(nix path-info -S /run/current-system 2>/dev/null | awk '{print $NF}'); mt=$(stat -c %Y /nix/var/nix/profiles/system 2>/dev/null); now=$(date +%s); echo "${sz:-0} $(( ${now:-0} - ${mt:-0} ))"371+;372+373+pub fn systemStatus(gpa: std.mem.Allocator, io: std.Io, machine: *const types.Machine) SystemStatus {374+    const unknown = SystemStatus{375+        .size = gpa.dupe(u8, "") catch return .{ .size = "", .age = "" },376+        .age = gpa.dupe(u8, "") catch return .{ .size = "", .age = "" },377+    };378+    const r = capture(gpa, io, machine, &.{ "sh", "-c", STATUS_SCRIPT }) catch return unknown;379+    defer gpa.free(r.stdout);380+    defer gpa.free(r.stderr);381+    if (r.exit_code != 0) return unknown;382+383+    var it = std.mem.tokenizeAny(u8, r.stdout, " \t\n\r");384+    const sz_str = it.next() orelse return unknown;385+    const age_str = it.next() orelse return unknown;386+    const bytes = std.fmt.parseInt(u64, sz_str, 10) catch 0;387+    const age_secs = std.fmt.parseInt(u64, age_str, 10) catch 0;388+389+    gpa.free(unknown.size);390+    gpa.free(unknown.age);391+    return .{392+        .size = humanSize(gpa, bytes) catch (gpa.dupe(u8, "") catch ""),393+        .age = humanAgo(gpa, age_secs) catch (gpa.dupe(u8, "") catch ""),394+    };395+}396+397+fn humanAgo(gpa: std.mem.Allocator, secs: u64) ![]u8 {398+    if (secs < 60) return std.fmt.allocPrint(gpa, "{d}s ago", .{secs});399+    if (secs < 3600) return std.fmt.allocPrint(gpa, "{d}m ago", .{secs / 60});400+    if (secs < 86400) return std.fmt.allocPrint(gpa, "{d}h ago", .{secs / 3600});401+    return std.fmt.allocPrint(gpa, "{d}d ago", .{secs / 86400});402+}403+363404 pub fn storeRepair(io: std.Io, machine: *const types.Machine, gpa: std.mem.Allocator) !u8 {364405     return stream(io, machine, gpa, &.{ "sudo", "nix", "store", "repair", "--all" });365406 }

view file