From 0ea12008f610cc2425e2671eac44fa8fc643ffd7 Mon Sep 17 00:00:00 2001 From: r0chd Date: Sun, 16 Aug 2026 13:55:25 +0200 Subject: [PATCH] notify s6 --- daemon/src/config.rs | 1 - daemon/src/main.rs | 9 ++---- daemon/src/notify.rs | 68 ++++++++++++++++++++++++++++++++++++++++++- daemon/src/service.rs | 16 +++++----- nix/finix-vm.nix | 23 +++++++++++++++ 5 files changed, 100 insertions(+), 17 deletions(-) diff --git a/daemon/src/config.rs b/daemon/src/config.rs index 45ccade..86e4dd2 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -28,7 +28,6 @@ pub struct Service { pub enum Notify { #[default] None, - Pid, Systemd, S6, } diff --git a/daemon/src/main.rs b/daemon/src/main.rs index e348b7f..8978215 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -377,8 +377,7 @@ fn handle_restart_requested( let notifier: &mut dyn notify::Notify = match notify { config::Notify::None => &mut notify::Dummy, config::Notify::Systemd => &mut *systemd_notify, - config::Notify::Pid => todo!(), - config::Notify::S6 => todo!(), + config::Notify::S6 => &mut notify::S6, }; match spawn_process( &loop_handle, @@ -660,8 +659,7 @@ fn service_modified( let notifier: &mut dyn notify::Notify = match notify { config::Notify::None => &mut notify::Dummy, config::Notify::Systemd => &mut *systemd_notify, - config::Notify::Pid => todo!(), - config::Notify::S6 => todo!(), + config::Notify::S6 => &mut notify::S6, }; match spawn_process( &loop_handle, @@ -747,8 +745,7 @@ fn start_services( let notifier: &mut dyn notify::Notify = match notify { config::Notify::None => &mut notify::Dummy, config::Notify::Systemd => &mut *systemd_notify, - config::Notify::Pid => todo!(), - config::Notify::S6 => todo!(), + config::Notify::S6 => &mut notify::S6, }; (match spawn_process( &loop_handle, diff --git a/daemon/src/notify.rs b/daemon/src/notify.rs index 718838f..7f4b49f 100644 --- a/daemon/src/notify.rs +++ b/daemon/src/notify.rs @@ -1,9 +1,13 @@ use crate::{condition, ecs, service}; use bevy_ecs::prelude::*; use calloop::generic; +use rustix::pipe; +use std::os::fd; +use std::os::fd::FromRawFd; use std::os::linux::net::SocketAddrExt; use std::os::unix::net; -use std::{io, process}; +use std::os::unix::process::CommandExt; +use std::{io, mem, process}; pub trait Notify { fn configure_command( @@ -111,3 +115,65 @@ impl Notify for Dummy { Ok(None) } } + +#[derive(Resource)] +pub struct S6; + +impl Notify for S6 { + fn configure_command( + &mut self, + loop_handle: &ecs::LoopHandle, + entity: Entity, + cmd: &mut process::Command, + ) -> anyhow::Result> { + let (notify_read, notify_write) = pipe::pipe_with(pipe::PipeFlags::CLOEXEC)?; + + unsafe { + cmd.pre_exec(move || { + let mut target = fd::OwnedFd::from_raw_fd(3); + rustix::io::dup2(¬ify_write, &mut target).map_err(io::Error::from)?; + + mem::forget(target); + + Ok(()) + }); + } + + let fd_wrapper = unsafe { generic::FdWrapper::new(notify_read) }; + let source = generic::Generic::new( + fd_wrapper, + calloop::Interest { + readable: true, + writable: false, + }, + calloop::Mode::Level, + ); + let token = loop_handle.0.insert_source(source, move |_, fd, state| { + let mut buf = [0u8; 64]; + + match rustix::io::read(fd, &mut buf) { + Ok(0) => Ok(calloop::PostAction::Remove), + Ok(n) => { + if buf[..n].contains(&b'\n') { + let Some(ready) = state.world.get::(entity) else { + return Ok(calloop::PostAction::Remove); + }; + + state.world.entity_mut(ready.0).insert(condition::Fulfilled); + + Ok(calloop::PostAction::Remove) + } else { + Ok(calloop::PostAction::Continue) + } + } + Err(rustix::io::Errno::AGAIN) => Ok(calloop::PostAction::Continue), + Err(err) => { + log::error!("failed to read notify fd: {err}"); + Ok(calloop::PostAction::Remove) + } + } + })?; + + Ok(Some(token)) + } +} diff --git a/daemon/src/service.rs b/daemon/src/service.rs index 4d3da52..641541d 100644 --- a/daemon/src/service.rs +++ b/daemon/src/service.rs @@ -1,5 +1,5 @@ use crate::apogee_core_v1::apogee_service_v1; -use crate::{apogee_core_v1, config}; +use crate::{apogee_core_v1, config, notify}; use crate::{condition, ecs}; use bevy_ecs::prelude::*; use std::collections::{BTreeMap, HashSet}; @@ -484,11 +484,10 @@ pub fn handle_start( return; } - let notifier: &mut dyn crate::notify::Notify = match notify { - config::Notify::None => &mut crate::notify::Dummy, + let notifier: &mut dyn notify::Notify = match notify { + config::Notify::None => &mut notify::Dummy, config::Notify::Systemd => &mut *systemd_notify, - config::Notify::Pid => todo!(), - config::Notify::S6 => todo!(), + config::Notify::S6 => &mut notify::S6, }; match crate::spawn_process( &loop_handle, @@ -621,11 +620,10 @@ pub fn handle_restart( commands.entity(ready.0).remove::(); - let notifier: &mut dyn crate::notify::Notify = match notify { - config::Notify::None => &mut crate::notify::Dummy, + let notifier: &mut dyn notify::Notify = match notify { + config::Notify::None => &mut notify::Dummy, config::Notify::Systemd => &mut *systemd_notify, - config::Notify::Pid => todo!(), - config::Notify::S6 => todo!(), + config::Notify::S6 => &mut notify::S6, }; match crate::spawn_process( &loop_handle, diff --git a/nix/finix-vm.nix b/nix/finix-vm.nix index 60e622d..43ec053 100644 --- a/nix/finix-vm.nix +++ b/nix/finix-vm.nix @@ -53,6 +53,29 @@ in restart.on = "never"; }; + delayed-s6-ready-test = { + description = "Marks itself ready through an s6 notification after 1 minute"; + command = "${pkgs.writeShellScript "apogee-delayed-s6-ready-test" '' + ${pkgs.coreutils}/bin/sleep 1m + printf '\n' >&3 + exec 3>&- + exec ${pkgs.coreutils}/bin/sleep infinity + ''}"; + notify = "s6"; + restart.on = "never"; + }; + + delayed-ready-dependent-test = { + description = "Starts after both delayed readiness tests are ready"; + command = "${pkgs.coreutils}/bin/sleep"; + args = [ "infinity" ]; + conditions = [ + "delayed-ready-test/ready" + "delayed-s6-ready-test/ready" + ]; + restart.on = "never"; + }; + pipewire.command = "${config.programs.pipewire.package}/bin/pipewire"; wireplumber = { -- 2.51.2