From 08c7677e25e1c1f5f397898d51e83c764e32db64 Mon Sep 17 00:00:00 2001 From: Nikolay Govorov Date: Tue, 20 Jan 2026 08:37:16 +0000 Subject: Integrate with systemd notify --- Cargo.lock | 10 +++++ Cargo.toml | 3 ++ pkg/zorian.service | 6 ++- src/main.rs | 91 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55a7227..9499881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3607,6 +3607,15 @@ dependencies = [ "syn", ] +[[package]] +name = "sd-notify" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b943eadf71d8b69e661330cb0e2656e31040acf21ee7708e2c238a0ec6af2bf4" +dependencies = [ + "libc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -5562,6 +5571,7 @@ dependencies = [ "hyper-util", "minijinja", "rustls", + "sd-notify", "semver", "serde", "sqlx", diff --git a/Cargo.toml b/Cargo.toml index 9b4618f..762b60d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,9 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } url = "2.5" uuid = { version = "1.19", features = ["v4", "v5"] } +[target.'cfg(target_os = "linux")'.dependencies] +sd-notify = { version = "0.4" } + [dev-dependencies] cargo-deny = "0.19" cargo-llvm-cov = "0.6" diff --git a/pkg/zorian.service b/pkg/zorian.service index ac99e25..64f34b5 100644 --- a/pkg/zorian.service +++ b/pkg/zorian.service @@ -8,11 +8,13 @@ After=time-sync.target network-online.target remote-fs.target nss-lookup.target Wants=time-sync.target [Service] -Type=exec +Type=notify User=zorian Group=zorian Restart=always RestartSec=30 +WatchdogSec=30 +NotifyAccess=main ExecPaths=/usr/local/bin/zorian /usr/lib ExecStart=/usr/local/bin/zorian --config=/etc/zorian.toml LimitCORE=infinity @@ -25,6 +27,7 @@ StateDirectory=%p CacheDirectory=%p RuntimeDirectory=%p +UMask=0077 LockPersonality=yes NoNewPrivileges=yes PrivateDevices=yes @@ -42,7 +45,6 @@ ProtectSystem=strict RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX RestrictNamespaces=yes RestrictSUIDSGID=yes -UMask=0077 [Install] # service should not start from the rescue shell (rescue.target). diff --git a/src/main.rs b/src/main.rs index fee8d4f..d6281ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,10 @@ use axum::{ }; use axum_server::Handle; use axum_server::tls_rustls::RustlsConfig; +#[cfg(target_os = "linux")] +use sd_notify::NotifyState; use tokio::signal; -use tracing::{error, info}; +use tracing::{error, info, trace}; use tracing_subscriber::{layer::SubscriberExt, registry::LookupSpan, util::SubscriberInitExt}; use crate::backends::zig::ZigController; @@ -316,41 +318,74 @@ async fn main() { ); } - // Graceful shutdown - let sigint = async { - signal::ctrl_c() - .await - .expect("failed to install Ctrl+C handler"); + let mut watchdog_ticker = tokio::time::interval(std::time::Duration::from_mins(1)); + + #[cfg(target_os = "linux")] + if sd_notify::booted().unwrap_or(false) { + sd_notify::notify(false, &[NotifyState::Ready]).ok(); + + let mut usec = 0u64; + (sd_notify::watchdog_enabled(true, &mut usec) && usec > 0).then(|| { + let interval = std::time::Duration::from_micros(usec) / 2; + info!( + interval_ms = interval.as_millis() as u64, + "watchdog enabled" + ); + watchdog_ticker = tokio::time::interval(interval); + }); }; - let sigterm = { - #[cfg(unix)] - let terminate = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .expect("failed to install signal handler") - .recv() - .await; - }; + #[cfg(unix)] + let mut sigint = signal::unix::signal(signal::unix::SignalKind::interrupt()) + .expect("failed to install signal handler"); + #[cfg(windows)] + let mut sigint = signal::windows::signal(signal::windows::SignalKind::interrupt()) + .expect("failed to install signal handler"); + + #[cfg(unix)] + let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler"); + + loop { + let watchdog = watchdog_ticker.tick(); + + #[cfg(unix)] + let sigterm = sigterm.recv(); #[cfg(not(unix))] - let terminate = std::future::pending::<()>(); + let sigterm = std::future::pending::<()>(); - terminate - }; - tokio::select! { - _ = sigint => info!("received SIGINT, shutting down"), - _ = sigterm => info!("received SIGTERM, shutting down"), - result = tasks.join_next() => { - match result { - Some(Ok(())) => error!("listener exited unexpectedly, shutting down"), - Some(Err(e)) => error!("listener failed: {e}, shutting down"), - None => { - error!("no listeners running"); - return; + tokio::select! { + _ = sigint.recv() => { + info!("received SIGINT, shutting down"); + break; + }, + _ = sigterm => { + info!("received SIGTERM, shutting down"); + break; + }, + _ = watchdog => { + trace!("server is alive"); + + #[cfg(target_os = "linux")] + sd_notify::notify(false, &[NotifyState::Watchdog]).ok(); + }, + result = tasks.join_next() => { + match result { + Some(Ok(())) => error!("listener exited unexpectedly, shutting down"), + Some(Err(e)) => error!("listener failed: {e}, shutting down"), + None => { + error!("no listeners running"); + return; + } } - } + break; + }, } } + #[cfg(target_os = "linux")] + sd_notify::notify(false, &[NotifyState::Stopping]).ok(); + handle.graceful_shutdown(None); // Wait for all listeners to finish with timeout -- Gilti