# Canonical example unit for BaseRunnerPrivExec.py - see docs/BaseRunnerMulti.md's # "Deployment" section for the copy/daemon-reload/enable/start sequence. # Adjust paths if this host's checkout doesn't live at /usr/local/basestation3. [Unit] Description=Privileged exec helper for BaseRunnerMulti After=network.target [Service] User=baserunner Group=baserunner # baserunner has no home directory (--no-create-home), so matplotlib's # default $HOME/.config/matplotlib cache dir isn't writable; it falls # back to a throwaway /tmp dir with a startup warning if left unset. CacheDirectory=baserunner Environment=MPLCONFIGDIR=/var/cache/baserunner AmbientCapabilities=CAP_SETUID CAP_SETGID CapabilityBoundingSet=CAP_SETUID CAP_SETGID # This unit nests under baserunner.slice (see below) rather than being # its own top-level delegated cgroup - Delegate=yes still grants CgroupJoiner # write access to per-site (site-) and per-job # (site-/job-) child cgroups it creates *inside the slice*, # but this unit's own cgroup itself is never touched by CgroupJoiner (see # Slice= below for why that split exists). Delegate=yes # Nests this unit's own cgroup one level under baserunner.slice instead of # directly under system.slice - see the long comment above DelegateSubgroup= # used to sit (removed; ExecStartPre= below is the actual fix now) for the # full "no internal process" rationale. Confirmed on Ubuntu 22.04/systemd # 249 (matches seaglider.pub) that DelegateSubgroup= (systemd >= 254 only) # is silently ignored there ("Unknown key name 'DelegateSubgroup'") and # does NOT protect against the resulting status=219/CGROUP restart failure # - this unit's own cgroup must simply never be the place CgroupJoiner # enables a controller, on any systemd version, full stop. Slice=baserunner.slice # baserunner.slice is auto-created (by the Slice= reference above) the # first time this unit starts, owned by root like any other cgroup - this # bootstraps it to match Delegate=yes's usual per-unit chown, but one level # up: mkdir is a no-op if the slice cgroup already exists (it always will, # after the first boot), chown -R is safe/idempotent to rerun even with # live site-*/job-* children already inside it (metadata-only, no real # I/O - cgroupfs is virtual), and re-enabling an already-enabled controller # is a harmless no-op. Runs as root (the "+" prefix) regardless of this # unit's own User=baserunner, via systemd's per-command privilege override # - PermissionsStartOnly= is deprecated in favor of exactly this. # Deliberately NOT gated on cpu_quota_pct/cpu_weight/memory_high_mb/ # memory_max_mb being configured anywhere in sites.yaml: unlike CgroupJoiner # enabling controllers per-site (only once a site is actually dispatched # to), this unconditional bootstrap must happen before ANY dispatch, since # ExecStart itself needs baserunner.slice to already be write-accessible. ExecStartPre=+/bin/sh -c 'mkdir -p /sys/fs/cgroup/baserunner.slice && chown -R baserunner:baserunner /sys/fs/cgroup/baserunner.slice && echo "+cpu +memory" > /sys/fs/cgroup/baserunner.slice/cgroup.subtree_control' ExecStart=/opt/basestation/bin/python /usr/local/basestation3/BaseRunnerPrivExec.py \ --sites_config /usr/local/basestation3/etc/sites.yaml \ --priv_exec_socket /run/baserunner/priv_exec.sock \ --cgroup_root /sys/fs/cgroup/baserunner.slice \ --base_log /var/log/baserunner/baserunner-privexec.log RuntimeDirectory=baserunner # Creates /var/log/baserunner/ owned baserunner:baserunner (mode 0750) on # every start, recreating it if it's ever missing - no manual mkdir/chown # of the log directory needed. Requires systemd >= 235. LogsDirectory=baserunner # BaseRunnerPrivExec.py calls sd_notify(READY=1) only after its socket is # bound and listening - this makes baserunnermulti.service's # Requires=/After= on this unit (see baserunnermulti.service) an actual # readiness guarantee, not just "the process was forked". Without # Type=notify here, systemd considers this unit started the instant # ExecStart's process exists, so the watcher could start and try to # dispatch through a socket that doesn't exist yet - seen in production # as a PrivExecError connecting to priv_exec.sock right after boot. Type=notify Restart=always # Every dispatched job is moved into its own # cgroup_root/site-/job- leaf cgroup (see CgroupJoiner.join()), # a descendant of baserunner.slice - NOT of this unit's own cgroup (see # Slice= above). Without KillMode=process, systemd's default # KillMode=control-group would SIGTERM/SIGKILL every process in this # unit's own cgroup on any stop of this unit. That no longer includes # dispatched jobs at all now that they live under the slice instead of # under this unit - but KillMode=process is kept anyway as a second, # independent safeguard (and because this unit's own cgroup should only # ever equal "this daemon's own process" regardless). KillMode=process [Install] WantedBy=multi-user.target