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
↓ nina-8aa0bab7.tar.gzatlas at this save
files changed2 files
src/commands.zig+4 −1src/exec.zig+41 −0src/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 {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 }