@@ -37,6 +37,7 @@ async fn main() -> Result<()> {
3737 unix_bridge:: run( config. clone( ) , streams. clone( ) , pending. clone( ) ) ,
3838 pending_sweeper:: run( config. clone( ) , pending. clone( ) ) ,
3939 registry:: heartbeat_loop( config. clone( ) , streams. clone( ) ) ,
40+ coold_heartbeat:: run( config. clone( ) , streams. clone( ) ) ,
4041 ) ?;
4142
4243 Ok ( ( ) )
@@ -262,6 +263,19 @@ mod grpc_server {
262263 let registry = self . registry . clone ( ) ;
263264 let resource_status = self . resource_status . clone ( ) ;
264265 let host_id_clone = host_id. clone ( ) ;
266+ {
267+ let publisher = resource_status. clone ( ) ;
268+ let host_id = host_id. clone ( ) ;
269+ tokio:: spawn ( async move {
270+ publisher
271+ . publish ( coold_status_update (
272+ & host_id,
273+ "installed" ,
274+ "coold stream connected." ,
275+ ) )
276+ . await ;
277+ } ) ;
278+ }
265279 if let Some ( registry) = registry. clone ( ) {
266280 let host_id = host_id. clone ( ) ;
267281 let capabilities = hello. capabilities . clone ( ) ;
@@ -297,6 +311,23 @@ mod grpc_server {
297311 publisher. publish ( update) . await ;
298312 } ) ;
299313 }
314+ Ok ( ClientMsg {
315+ payload : Some ( client_msg:: Payload :: Pong ( _) ) ,
316+ } ) => {
317+ if streams. touch ( & host_id_clone) {
318+ let publisher = resource_status. clone ( ) ;
319+ let host_id = host_id_clone. clone ( ) ;
320+ tokio:: spawn ( async move {
321+ publisher
322+ . publish ( coold_status_update (
323+ & host_id,
324+ "installed" ,
325+ "coold heartbeat restored." ,
326+ ) )
327+ . await ;
328+ } ) ;
329+ }
330+ }
300331 Ok ( ClientMsg {
301332 payload : Some ( client_msg:: Payload :: Hello ( _) ) ,
302333 } ) => warn ! ( host_id = %host_id_clone, "duplicate Hello ignored" ) ,
@@ -310,6 +341,13 @@ mod grpc_server {
310341 }
311342 info ! ( host_id = %host_id_clone, "coold stream disconnected" ) ;
312343 streams. remove ( & host_id_clone) ;
344+ resource_status
345+ . publish ( coold_status_update (
346+ & host_id_clone,
347+ "unreachable" ,
348+ "coold stream disconnected." ,
349+ ) )
350+ . await ;
313351 if let Some ( registry) = registry {
314352 if let Err ( e) = registry. disconnect ( & host_id_clone, disconnect_reason) . await {
315353 warn ! (
@@ -351,9 +389,24 @@ mod grpc_server {
351389 pending. deliver ( & request_id, data) ;
352390 }
353391
392+ pub ( super ) fn coold_status_update (
393+ host_id : & str ,
394+ status : & str ,
395+ status_message : & str ,
396+ ) -> coolify_proto:: agent:: v1:: ResourceStatusUpdate {
397+ coolify_proto:: agent:: v1:: ResourceStatusUpdate {
398+ resource_type : "server" . into ( ) ,
399+ host_id : host_id. into ( ) ,
400+ container_id : String :: new ( ) ,
401+ container_name : String :: new ( ) ,
402+ status : status. into ( ) ,
403+ status_message : status_message. into ( ) ,
404+ }
405+ }
406+
354407 #[ cfg( test) ]
355408 mod tests {
356- use super :: { advertised_capability_not_granted, validate_bind} ;
409+ use super :: { advertised_capability_not_granted, coold_status_update , validate_bind} ;
357410 use std:: net:: SocketAddr ;
358411
359412 fn parse ( s : & str ) -> SocketAddr {
@@ -424,6 +477,68 @@ mod grpc_server {
424477 Some ( "ingress.apply" )
425478 ) ;
426479 }
480+
481+ #[ test]
482+ fn builds_server_status_update_for_coold_liveness ( ) {
483+ let update =
484+ coold_status_update ( "100.64.0.5" , "unreachable" , "coold heartbeat timed out." ) ;
485+
486+ assert_eq ! ( update. resource_type, "server" ) ;
487+ assert_eq ! ( update. host_id, "100.64.0.5" ) ;
488+ assert_eq ! ( update. status, "unreachable" ) ;
489+ assert_eq ! ( update. status_message, "coold heartbeat timed out." ) ;
490+ }
491+ }
492+ }
493+
494+ mod coold_heartbeat {
495+ use anyhow:: Result ;
496+ use coolify_proto:: agent:: v1:: { server_msg, Ping , ServerMsg } ;
497+ use tracing:: warn;
498+ use uuid:: Uuid ;
499+
500+ use crate :: {
501+ config:: Config , grpc_server:: coold_status_update, resource_status:: ResourceStatusPublisher ,
502+ state:: Streams ,
503+ } ;
504+
505+ pub async fn run ( config : Config , streams : Streams ) -> Result < ( ) > {
506+ let publisher = ResourceStatusPublisher :: new (
507+ config. laravel_api_url . clone ( ) ,
508+ config. laravel_api_token . clone ( ) ,
509+ ) ;
510+ let mut ticker = tokio:: time:: interval ( std:: time:: Duration :: from_secs (
511+ config. coold_ping_interval_secs . max ( 1 ) ,
512+ ) ) ;
513+ let timeout = std:: time:: Duration :: from_secs ( config. coold_pong_timeout_secs . max ( 1 ) ) ;
514+
515+ loop {
516+ ticker. tick ( ) . await ;
517+
518+ for host_id in streams. mark_stale ( timeout) {
519+ publisher
520+ . publish ( coold_status_update (
521+ & host_id,
522+ "unreachable" ,
523+ "coold heartbeat timed out." ,
524+ ) )
525+ . await ;
526+ }
527+
528+ for ( host_id, tx) in streams. ping_targets ( ) {
529+ let request_id = Uuid :: new_v4 ( ) . to_string ( ) ;
530+ if tx
531+ . send ( ServerMsg {
532+ request_id,
533+ command : Some ( server_msg:: Command :: Ping ( Ping { } ) ) ,
534+ } )
535+ . await
536+ . is_err ( )
537+ {
538+ warn ! ( %host_id, "coold heartbeat ping send failed" ) ;
539+ }
540+ }
541+ }
427542 }
428543}
429544
0 commit comments