diff --git a/l4d2web/services/overlay_builders.py b/l4d2web/services/overlay_builders.py index da1d95c..753986e 100644 --- a/l4d2web/services/overlay_builders.py +++ b/l4d2web/services/overlay_builders.py @@ -31,6 +31,18 @@ SCRIPT_SANDBOX_HELPER = "/usr/local/libexec/left4me/left4me-script-sandbox" DISK_BUDGET_BYTES = 20 * 1024**3 +def _sandbox_script_dir() -> Path: + """Where script tmpfiles live before being bind-mounted into the sandbox. + + Cannot live in /tmp because the web service unit has PrivateTmp=yes: + its /tmp is a per-instance namespace that PID 1 (which actually performs + the BindReadOnlyPaths during sandbox setup) cannot resolve. /var/lib is + not affected by PrivateTmp and is visible to PID 1, so the bind-mount + succeeds. + """ + return get_left4me_root() / "sandbox-scripts" + + class BuildError(RuntimeError): """Raised by builders when a build fails for a builder-specific reason (e.g. disk-budget exceeded). Distinct from subprocess-level @@ -189,7 +201,11 @@ def run_sandboxed_script( ) -> None: """Write `script_text` to a tmpfile and exec it inside the privileged sandbox helper. Used by ScriptBuilder.build and by the wipe route.""" - with tempfile.NamedTemporaryFile("w", suffix=".sh", delete=False) as f: + script_dir = _sandbox_script_dir() + script_dir.mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile( + "w", suffix=".sh", delete=False, dir=str(script_dir) + ) as f: f.write(script_text or "") script_path = f.name # NamedTemporaryFile creates 0600 owned by the web user; the sandbox runs