From 71b7bdf6571fe36a4c18bf64847f91562eb105fc Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Wed, 11 Mar 2026 15:25:04 +0100 Subject: [PATCH] feat(appd): wrap runtime in systemd-run cgroup scope when user session is active supervise() checks /systemd/private to detect an active user systemd session. When present (and WEFT_DISABLE_CGROUP is unset), the runtime binary is launched via: systemd-run --user --scope --wait --collect --slice=weft-apps.slice -p CPUQuota=200% -p MemoryMax=512M -- ... This places each app in a transient weft-apps.slice scope with default resource limits from the blueprint. The --wait flag keeps systemd-run alive so child.wait()/child.kill() remain correct. When no user session is present the command is built directly as before. WEFT_DISABLE_CGROUP=1 bypasses the wrapping unconditionally. --- crates/weft-appd/src/runtime.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/weft-appd/src/runtime.rs b/crates/weft-appd/src/runtime.rs index cf142fd..6fb626d 100644 --- a/crates/weft-appd/src/runtime.rs +++ b/crates/weft-appd/src/runtime.rs @@ -10,6 +10,18 @@ use crate::ipc::{AppStateKind, Response}; const READY_TIMEOUT: Duration = Duration::from_secs(30); +fn systemd_cgroup_available() -> bool { + if std::env::var("WEFT_DISABLE_CGROUP").is_ok() { + return false; + } + let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") else { + return false; + }; + std::path::Path::new(&runtime_dir) + .join("systemd/private") + .exists() +} + fn resolve_preopens(app_id: &str) -> Vec<(String, String)> { #[derive(serde::Deserialize)] struct Pkg { @@ -92,7 +104,25 @@ pub(crate) async fn supervise( } }; - let mut cmd = tokio::process::Command::new(&bin); + let mut cmd = if systemd_cgroup_available() { + let mut c = tokio::process::Command::new("systemd-run"); + c.args([ + "--user", + "--scope", + "--wait", + "--collect", + "--slice=weft-apps.slice", + "-p", + "CPUQuota=200%", + "-p", + "MemoryMax=512M", + "--", + &bin, + ]); + c + } else { + tokio::process::Command::new(&bin) + }; cmd.arg(app_id) .arg(session_id.to_string()) .stdout(std::process::Stdio::piped())