Merge nucleic/olive-ember-seal-q7vk into dev
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
//! naros-init — narOS PID-1 supervisor for container surfaces (docs/NAROS.md §5).
|
||||
//!
|
||||
//! Supervision and plumbing only, no policy:
|
||||
//! - reaps zombies (the historical job of PID 1);
|
||||
//! - forwards SIGTERM/SIGINT to supervised children;
|
||||
//! - `naros-init -- CMD ARGS…` supervises a primary command and exits with its status
|
||||
//! (129+signum on signal death), replacing `sh -c` wrappers as the container entrypoint;
|
||||
//! - with no primary command it is the keepalive that replaces ContainerEngine's
|
||||
//! sleep loop, staying alive until signalled;
|
||||
//! - NAROS_BRIDGE=1 additionally supervises the control bridge
|
||||
//! (`node /opt/nucleic/control-bridge.js`), restarting it with backoff — the bridge is
|
||||
//! best-effort transport, so its failures never affect the primary command or init.
|
||||
//!
|
||||
//! In the VM desktop flavor systemd stays PID 1 and this binary runs as a role unit.
|
||||
|
||||
use std::env;
|
||||
use std::process::{exit, Command};
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
static PENDING_SIGNAL: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
extern "C" fn on_signal(sig: libc::c_int) {
|
||||
PENDING_SIGNAL.store(sig, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn install_handlers() {
|
||||
unsafe {
|
||||
let handler = on_signal as *const () as usize;
|
||||
for sig in [libc::SIGTERM, libc::SIGINT] {
|
||||
libc::signal(sig, handler);
|
||||
}
|
||||
// SIG_DFL for SIGCHLD keeps children reapable via waitpid below.
|
||||
}
|
||||
}
|
||||
|
||||
/// Reap every exited child; returns the primary's status when it is among them.
|
||||
fn reap(primary: Option<libc::pid_t>, bridge: Option<libc::pid_t>) -> (Option<i32>, bool) {
|
||||
let mut primary_status = None;
|
||||
let mut bridge_died = false;
|
||||
loop {
|
||||
let mut status: libc::c_int = 0;
|
||||
let pid = unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG) };
|
||||
if pid <= 0 {
|
||||
break;
|
||||
}
|
||||
let code = if libc::WIFEXITED(status) {
|
||||
libc::WEXITSTATUS(status)
|
||||
} else if libc::WIFSIGNALED(status) {
|
||||
128 + libc::WTERMSIG(status)
|
||||
} else {
|
||||
1
|
||||
};
|
||||
if Some(pid) == primary {
|
||||
primary_status = Some(code);
|
||||
} else if Some(pid) == bridge {
|
||||
bridge_died = true;
|
||||
}
|
||||
}
|
||||
(primary_status, bridge_died)
|
||||
}
|
||||
|
||||
fn forward(sig: i32, pids: &[Option<libc::pid_t>]) {
|
||||
for pid in pids.iter().flatten() {
|
||||
unsafe {
|
||||
libc::kill(*pid, sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_bridge() -> Option<libc::pid_t> {
|
||||
const BRIDGE: &str = "/opt/nucleic/control-bridge.js";
|
||||
if !std::path::Path::new(BRIDGE).exists() {
|
||||
return None;
|
||||
}
|
||||
match Command::new("node").arg(BRIDGE).spawn() {
|
||||
Ok(child) => Some(child.id() as libc::pid_t),
|
||||
Err(err) => {
|
||||
eprintln!("naros-init: bridge spawn failed: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.get(1).map(String::as_str) == Some("--version") {
|
||||
println!("naros-init {}", env!("CARGO_PKG_VERSION"));
|
||||
return;
|
||||
}
|
||||
|
||||
install_handlers();
|
||||
|
||||
let cmd: Vec<&String> = match args.iter().position(|a| a == "--") {
|
||||
Some(i) => args[i + 1..].iter().collect(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
let mut primary: Option<libc::pid_t> = None;
|
||||
if let Some((prog, rest)) = cmd.split_first() {
|
||||
match Command::new(prog).args(rest).spawn() {
|
||||
Ok(child) => primary = Some(child.id() as libc::pid_t),
|
||||
Err(err) => {
|
||||
eprintln!("naros-init: exec {prog}: {err}");
|
||||
exit(127);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let want_bridge = env::var("NAROS_BRIDGE").ok().as_deref() == Some("1");
|
||||
let mut bridge = if want_bridge { spawn_bridge() } else { None };
|
||||
let mut bridge_backoff = Duration::from_millis(500);
|
||||
let mut bridge_retry_at: Option<Instant> = None;
|
||||
|
||||
loop {
|
||||
let sig = PENDING_SIGNAL.swap(0, Ordering::SeqCst);
|
||||
if sig != 0 {
|
||||
forward(sig, &[primary, bridge]);
|
||||
if primary.is_none() {
|
||||
// Keepalive role: the signal is our own shutdown request.
|
||||
exit(128 + sig);
|
||||
}
|
||||
}
|
||||
|
||||
let (primary_status, bridge_died) = reap(primary, bridge);
|
||||
if let Some(code) = primary_status {
|
||||
forward(libc::SIGTERM, &[bridge]);
|
||||
reap(None, bridge);
|
||||
exit(code);
|
||||
}
|
||||
if bridge_died {
|
||||
bridge = None;
|
||||
bridge_retry_at = Some(Instant::now() + bridge_backoff);
|
||||
bridge_backoff = (bridge_backoff * 2).min(Duration::from_secs(30));
|
||||
}
|
||||
if want_bridge && bridge.is_none() {
|
||||
if let Some(at) = bridge_retry_at {
|
||||
if Instant::now() >= at {
|
||||
bridge = spawn_bridge();
|
||||
bridge_retry_at = None;
|
||||
if bridge.is_some() {
|
||||
bridge_backoff = Duration::from_millis(500);
|
||||
} else {
|
||||
bridge_retry_at = Some(Instant::now() + bridge_backoff);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bridge_retry_at = Some(Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user