[Unit] Description=left4me server instance %i After=network-online.target Wants=network-online.target [Service] Type=simple User=left4me Group=left4me EnvironmentFile=/etc/left4me/host.env EnvironmentFile=/var/lib/left4me/instances/%i/instance.env # `-` prefix: chdir failure is non-fatal. systemd applies WorkingDirectory # before every Exec line — including ExecStartPre — but the merged dir only # exists once ExecStartPre's overlay mount succeeds. With `-`, ExecStartPre # runs in the unit's home (cwd doesn't matter for the mount helper); the # ExecStart re-applies WorkingDirectory after the mount and finds the dir. WorkingDirectory=-/var/lib/left4me/runtime/%i/merged/left4dead2 # At boot the kernel-overlayfs mount is gone (mounts are volatile); the # web app's start_instance also pre-mounts but doesn't run on auto-start. # The helper is idempotent — a no-op if already mounted by the web app. ExecStartPre=/usr/bin/sudo -n /usr/local/libexec/left4me/left4me-overlay mount %i ExecStart=/var/lib/left4me/installation/srcds_run -game left4dead2 +hostport ${L4D2_PORT} $L4D2_ARGS Restart=on-failure RestartSec=5 # Bound the restart loop. Without these, a persistent ExecStartPre or # ExecStart failure spins indefinitely (default systemd has no cap when # Restart= is explicitly set without StartLimit*). StartLimitBurst=5 StartLimitIntervalSec=60s # Resource control baseline — see docs/superpowers/specs/2026-05-09-l4d2-server-host-perf-baseline-design.md Slice=l4d2-game.slice Nice=-5 IOSchedulingClass=best-effort IOSchedulingPriority=4 OOMScoreAdjust=-200 MemoryHigh=1.5G MemoryMax=2G TasksMax=256 LimitNOFILE=65536 KillSignal=SIGINT TimeoutStopSec=15s LogRateLimitIntervalSec=0 # Hardening (unchanged from previous baseline). NoNewPrivileges=true PrivateTmp=true PrivateDevices=true ProtectHome=true ProtectSystem=strict ReadOnlyPaths=/var/lib/left4me/installation /var/lib/left4me/overlays ReadWritePaths=/var/lib/left4me/runtime/%i RestrictSUIDSGID=true LockPersonality=true [Install] WantedBy=multi-user.target