@@ -69,6 +69,8 @@ want_lnd_report: bool,
6969lnd_timer : time.Timer ,
7070lnd_report_interval : u64 = 1 * time .ns_per_min ,
7171lnd_tls_reset_count : usize = 0 ,
72+ sysupdates_head : ? []u8 = null ,
73+ next_head_refresh_ns : i128 = 0 ,
7274
7375// TODO: move this to a sys.ServiceList
7476/// system services actively managed by the daemon.
@@ -143,7 +145,7 @@ pub fn init(opt: InitOpt) !Daemon {
143145
144146 logger .debug ("conf = {any}" , .{opt .conf });
145147
146- return .{
148+ var d : Daemon = .{
147149 .allocator = opt .allocator ,
148150 .conf = opt .conf ,
149151 .uireader = opt .uir ,
@@ -164,14 +166,22 @@ pub fn init(opt: InitOpt) !Daemon {
164166 // report lightning status immediately on start
165167 .want_lnd_report = true ,
166168 .lnd_timer = try time .Timer .start (),
169+ .next_head_refresh_ns = 0 ,
170+ .sysupdates_head = null ,
167171 };
172+
173+ d .refreshSysupdatesHead ();
174+ return d ;
168175}
169176
170177/// releases all associated resources.
171178/// the daemon must be stop'ed and wait'ed before deiniting.
172179pub fn deinit (self : * Daemon ) void {
173180 self .wpa_ctrl .close () catch | err | logger .err ("deinit: wpa_ctrl.close: {any}" , .{err });
174181 self .services .deinit (self .allocator );
182+ if (self .sysupdates_head ) | head | {
183+ self .allocator .free (head );
184+ }
175185}
176186
177187/// start launches daemon threads and returns immediately.
@@ -338,16 +348,32 @@ fn mainThreadLoop(self: *Daemon) void {
338348 logger .info ("exiting main thread loop" , .{});
339349}
340350
351+ const HEAD_REFRESH_PERIOD_NS : i128 = 60 * std .time .ns_per_s ;
352+
341353/// runs one cycle of the main thread loop iteration.
342- /// the cycle holds self.mu for the whole duration.
354+ /// briefly locks self.mu to decide on refresh, then holds self.mu
343355fn mainThreadLoopCycle (self : * Daemon ) ! void {
356+ // decide whether to refresh (fast)
357+ var do_refresh = false ;
358+ const now = std .time .nanoTimestamp ();
359+ self .mu .lock ();
360+ if (now >= self .next_head_refresh_ns ) {
361+ self .next_head_refresh_ns = now + HEAD_REFRESH_PERIOD_NS ;
362+ do_refresh = true ;
363+ }
364+ self .mu .unlock ();
365+
366+ if (do_refresh ) self .refreshSysupdatesHead ();
367+
344368 self .mu .lock ();
345369 defer self .mu .unlock ();
346370
347371 if (self .want_settings ) {
348- const ok = self .conf .safeReadOnly (struct {
349- fn f (conf : Config.Data , static : Config.StaticData ) bool {
350- const msg : comm.Message.Settings = .{
372+ const head_override : ? []const u8 = self .sysupdates_head ;
373+
374+ const msg_opt = self .conf .safeReadOnly (struct {
375+ fn f (conf : Config.Data , static : Config.StaticData ) ? comm.Message.Settings {
376+ return .{
351377 .hostname = static .hostname ,
352378 .slock_enabled = conf .slock != null ,
353379 .sysupdates = .{
@@ -357,16 +383,23 @@ fn mainThreadLoopCycle(self: *Daemon) !void {
357383 .master = > .stable ,
358384 .disabled = > .disabled ,
359385 },
386+ .head = conf .head , // temporary; caller may override
360387 },
361388 };
362- comm .pipeWrite (.{ .settings = msg }) catch | err | {
363- logger .err ("{}" , .{err });
364- return false ;
365- };
366- return true ;
367389 }
368390 }.f );
369- self .want_settings = ! ok ;
391+
392+ if (msg_opt ) | msg_const | {
393+ var msg = msg_const ;
394+ msg .sysupdates .head = if (head_override ) | h | h else msg .sysupdates .head ;
395+
396+ var ok = true ;
397+ comm .pipeWrite (.{ .settings = msg }) catch | err | {
398+ logger .err ("{}" , .{err });
399+ ok = false ;
400+ };
401+ self .want_settings = ! ok ;
402+ }
370403 }
371404
372405 // network stats
@@ -539,6 +572,12 @@ fn commThreadLoop(self: *Daemon) void {
539572 .unlock_screen = > | pincode | {
540573 self .unlockScreen (pincode ) catch | err | logger .err ("unlockScreen: {!}" , .{err });
541574 },
575+ .get_settings = > {
576+ self .refreshSysupdatesHead ();
577+ self .mu .lock ();
578+ self .want_settings = true ;
579+ self .mu .unlock ();
580+ },
542581 else = > | v | logger .warn ("unhandled msg tag {s}" , .{@tagName (v )}),
543582 }
544583
@@ -1292,6 +1331,56 @@ fn allocSanitizeNodename(allocator: std.mem.Allocator, name: []const u8) ![]cons
12921331 return allocator .dupe (u8 , trimmed );
12931332}
12941333
1334+ fn readGitHeadOwned (self : * Daemon ) ? []u8 {
1335+ std .fs .accessAbsolute (Config .SYSUPDATES_LOCAL_REPO_PATH , .{}) catch | err | switch (err ) {
1336+ error .FileNotFound = > return null ,
1337+ else = > {
1338+ logger .debug ("readGitHeadOwned: access check failed: {any}" , .{err });
1339+ // Proceed anyway; git command will provide a clearer error if needed
1340+ },
1341+ };
1342+
1343+ const res = std .process .Child .run (.{
1344+ .allocator = self .allocator ,
1345+ .argv = &.{ "git" , "-C" , Config .SYSUPDATES_LOCAL_REPO_PATH , "rev-parse" , "--short" , "HEAD" },
1346+ }) catch | err | {
1347+ // In tests, git may not exist; keep this non-fatal.
1348+ logger .debug ("readGitHeadOwned: git failed: {any}" , .{err });
1349+ return null ;
1350+ };
1351+ defer {
1352+ self .allocator .free (res .stdout );
1353+ self .allocator .free (res .stderr );
1354+ }
1355+
1356+ if (res .term != .Exited or res .term .Exited != 0 ) {
1357+ // If it's missing/not a repo, be quiet (or debug), not error.
1358+ logger .debug ("readGitHeadOwned: git exit {any}; stderr={s}" , .{ res .term , res .stderr });
1359+ return null ;
1360+ }
1361+
1362+ const head = std .mem .trim (u8 , res .stdout , & std .ascii .whitespace );
1363+ return self .allocator .dupe (u8 , head ) catch null ;
1364+ }
1365+
1366+ fn refreshSysupdatesHead (self : * Daemon ) void {
1367+ const new = self .readGitHeadOwned () orelse return ;
1368+
1369+ self .mu .lock ();
1370+ defer self .mu .unlock ();
1371+
1372+ if (self .sysupdates_head ) | old | {
1373+ if (std .mem .eql (u8 , old , new )) {
1374+ self .allocator .free (new );
1375+ return ;
1376+ }
1377+ logger .info ("sysupdates head changed: {s} -> {s}" , .{ old , new });
1378+ self .allocator .free (old );
1379+ }
1380+ self .sysupdates_head = new ;
1381+ self .want_settings = true ;
1382+ }
1383+
12951384test "daemon: start-stop" {
12961385 const t = std .testing ;
12971386
0 commit comments