Modernize and harden dedicated server container (SteamCMD reliability, unprivileged runtime, systemd)#1
Open
johnseth97 wants to merge 7 commits into
Open
Modernize and harden dedicated server container (SteamCMD reliability, unprivileged runtime, systemd)#1johnseth97 wants to merge 7 commits into
johnseth97 wants to merge 7 commits into
Conversation
- Create a dedicated abiotic user (default UID/GID 10000:10000, overridden
at runtime via PUID/PGID) instead of running everything as root.
- Fetch SteamCMD from Valve's official tarball instead of Ubuntu's
steamcmd apt package, which fails outright on this base image
("Steamcmd needs to be online to update" even with working network).
- Add en_US.UTF-8 locale generation (upstream PR Pleut#25) to remove SteamCMD's
setlocale warning.
- Fix execute permissions on the extracted steamcmd tarball, which Valve
ships without group/other execute bits.
- set -Eeuo pipefail, structured logging, no eval, no secrets logged. - Parse booleans/integers strictly; reject the shipped SERVER_PASSWORD placeholder while still allowing an explicitly empty password (fixes the upstream VAR:-default substitution bug behind issues Pleut#28/Pleut#29, where an intentionally empty password was silently replaced with a fixed default). - Verify installs by checking the executable's actual presence and the installed Steam build ID, never by trusting SteamCMD's exit code alone (issues Pleut#27/Pleut#24/Pleut#8, PR Pleut#26 - reimplemented with real exponential backoff). - Stage installs/updates in a directory outside any save-data bind mount and rsync into the live install dir excluding Saved/, so a failed or interrupted update can never touch save data (PR Pleut#23, reimplemented without find -delete against the live tree). - Detect and recover from SteamCMD's own self-update bootstrap getting stuck in a repeating failed state, independent of the game depot. - Forward SIGTERM/SIGINT to the game process with a bounded grace period before escalating to SIGKILL; verified with synthetic signal tests.
Queries the actual query port with a Source-engine style A2S_INFO request instead of just checking that PID 1 exists. Falls back to a process+socket check if the game hasn't finished initializing yet.
Modern compose syntax with no top-level version field, locally built image, init: true, restart: no since systemd supervises the stack, cap_drop: ALL with a minimal cap_add for the entrypoint's root-phase bootstrap, no-new-privileges, Z-labeled bind mounts (required on this SELinux host, see docs/upstream-review.md), a health check, and json-file log rotation. Supersedes docker-compose.yml.example, which pointed at an unrelated image and had none of the above.
Stops the service for a consistent snapshot, since no live-save command exists for this server, archives data/ and config/ only, verifies the resulting archive is readable, and prunes to a configurable retention without ever deleting the newest backup.
abiotic-factor.service runs docker compose up in the foreground so systemd directly supervises it as the main PID, with a separate ExecStop for graceful shutdown. Includes an install script and an optional, not auto-enabled, daily backup timer.
…ting docs/upstream-review.md records every open/closed upstream issue and PR reviewed, whether it was adopted, and why. docs/operations.md and docs/troubleshooting.md cover the confirmed on-disk layout, update strategy, the SELinux Z-label requirement, and known failure modes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Modernizes and hardens this fork of
Pleut/abiotic-factor-linux-dockerfor a private, systemd-supervised deployment: the game server no longer runs as root, SteamCMD installs/updates are retried and verified against the actual installed build ID rather than trusted on exit code alone, updates are staged and never risk save data, there's a real health check, graceful shutdown, backup/restore tooling, and a systemd unit + installer for boot-time supervision.Verified directly against the live Steam depot for App ID
2857200rather than assuming the old entrypoint's executable path/launch arguments still apply (they had drifted - seedocs/upstream-review.md).Upstream issues and PRs addressed
Full accounting with adopt/reject rationale in
docs/upstream-review.md. Highlights:abioticuser, configurablePUID/PGID.${VAR:-default}bug in the old entrypoint that ignored an intentionally-empty password.find -deleteagainst the live install directory.Security changes
abiotic, configurablePUID/PGID, default10000:10000) - never UID 0.CHOWN,SETUID,SETGID,DAC_OVERRIDE- seedocker-compose.ymlfor why), and setsno-new-privileges:true..env(real secrets) is git-ignored;.env.exampleships only placeholders, and the entrypoint refuses to start ifSERVER_PASSWORDis still the placeholder.Update strategy
Staged, verified, never destructive - see
docs/operations.md"Update strategy in detail" for the full reasoning, including why a true atomic directory swap was rejected (saves are bind-mounted at nested paths inside the install directory). Short version: install/update into a disposable staging directory, verify the executable is actually present,rsyncinto the live directory excluding the save tree, re-verify. Failed updates leave the previous working installation untouched.Test evidence
docker compose config- clean.shellcheck entrypoint.sh scripts/*.sh- clean (viakoalaman/shellcheckcontainer image, sincednf install shellcheckwasn't available without an interactive sudo password).docker compose build --no-cache- succeeds; verified no secrets in image layers/history, entrypoint scripts executable, final process model runs unprivileged.systemd-analyze verifyon all three unit files - clean.init: true+ a baked-intiniENTRYPOINT fighting each other (removed the redundant one).cap_drop: ALLalso stripping capabilities from the container's root user, breaking the entrypoint's own privilege-drop bootstrap (fixed with a minimalcap_add).steamcmd.shwithout execute permission for non-owner users (fixed with an explicitchmodin the Dockerfile).runtime/without:Zcauses SteamCMD to report a successful install while writing nothing to the host directory - a silent AVC denial, not a SteamCMD bug. Fixed and verified with a full real install completing end-to-end with:Zin place.Known limitations
.github/workflows/ci.yml) is not included in this PR yet. Theghtoken used to push this branch hasrepo/read:org/gistscopes but notworkflow, which GitHub requires for any push that touches.github/workflows/*. The file is written and ready (shellcheck/compose-config/build-only, actions pinned to SHAs) - either rungh auth refresh -h github.com -s workflowand I'll push it as a follow-up commit, or add it manually via the GitHub web UI from the copy in this fork's working tree.steamcmd_public_all/steamcmd_linuxpackages failing checksum/truncation, independent of the actual game depot), reproduced consistently over roughly 30 minutes of testing from this network, including via manual SteamCMD invocations outside this project's code entirely. The entrypoint's retry/backoff and stuck-cache-clearing logic behave correctly and fail safely and clearly (bounded retries, distinct exit code, no partial/corrupt state left ingamefiles, no impact on save data) - seedocs/upstream-review.mdfor the reproduction details. Re-run the fresh-install test once this clears; exact command indocs/operations.md.docs/operations.md.Deployment instructions
See
README.md"Fedora installation" through "systemd commands" for the full walkthrough. Short version:Rollback instructions
Data rollback: restore from
runtime/backups/*.tar.gzperREADME.md"Backup and restore" (service must be stopped first).