From 516f1cfde2c18e1abb21dea572d9d72a0fa02407 Mon Sep 17 00:00:00 2001 From: HRiggs Date: Mon, 5 Oct 2026 18:25:51 -0400 Subject: [PATCH] fix: steamcmd install dir --- Dockerfile | 8 ++++---- compose.yaml | 2 +- deploy/README.md | 12 +++++++----- deploy/compose.yaml | 4 ++-- docker-entrypoint.sh | 2 +- progression_backend/workshop.py | 11 ++++------- tests/test_backend.py | 12 +++++++++--- 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index d3b1ec2..1669c08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,10 @@ RUN dpkg --add-architecture i386 \ ca-certificates curl lib32gcc-s1 python3 python3-pip python3-venv \ && rm -rf /var/lib/apt/lists/* \ && useradd --create-home --shell /bin/bash progression \ - && mkdir -p /opt/steamcmd/steamapps /srv/progression/.data \ + && mkdir -p /opt/steamcmd/steamapps /srv/progression/.data /srv/steam-library/steamapps \ && curl -fsSL https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \ | tar -xz -C /opt/steamcmd \ - && chown -R progression:progression /opt/steamcmd /srv/progression + && chown -R progression:progression /opt/steamcmd /srv/progression /srv/steam-library WORKDIR /srv/progression COPY --chown=progression:progression requirements-web.txt ./ @@ -26,12 +26,12 @@ ENV HOME=/root \ PROGRESSION_PORT=8000 \ PROGRESSION_DB_PATH=.data/progression.db \ STEAMCMD_PATH=/opt/steamcmd/steamcmd.sh \ - STEAMCMD_WORKSHOP_DIR=/opt/steamcmd/steamapps/workshop/content/294100 \ + STEAMCMD_WORKSHOP_DIR=/srv/steam-library/steamapps/workshop/content/294100 \ REFRESH_INTERVAL_SECONDS=3600 \ REFRESH_ON_STARTUP=true \ INITIAL_DOWNLOAD_TIMEOUT_SECONDS=43200 -VOLUME ["/srv/progression/.data", "/opt/steamcmd/steamapps"] +VOLUME ["/srv/progression/.data", "/srv/steam-library/steamapps"] EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ CMD curl -fsS http://127.0.0.1:8000/api/health || exit 1 diff --git a/compose.yaml b/compose.yaml index 7860bf2..ed9e015 100644 --- a/compose.yaml +++ b/compose.yaml @@ -11,7 +11,7 @@ services: INITIAL_DOWNLOAD_TIMEOUT_SECONDS: ${INITIAL_DOWNLOAD_TIMEOUT_SECONDS:-43200} volumes: - progression-data:/srv/progression/.data - - progression-workshop:/opt/steamcmd/steamapps + - progression-workshop:/srv/steam-library/steamapps volumes: progression-data: diff --git a/deploy/README.md b/deploy/README.md index a2a2ec2..38510f8 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -22,11 +22,13 @@ result, and completion percentage in Dockhand or with `docker compose logs -f`. Failed batches also include SteamCMD's output tail so network, subscription, and filesystem errors are visible directly in the container log. -SteamCMD is forced to use `/opt/steamcmd` so Workshop content always lands in -the mounted volume. On the first `I/O Operation Failed` result, transient `.acf`, -download, and temp state is moved into `.progression-recovery`; completed mod -directories are left untouched. Startup also checks that the volume has enough -free space for the remaining Steam metadata estimate. +SteamCMD is installed in `/opt/steamcmd`, while its forced library root is the +separate `/srv/steam-library` path. The existing `progression-workshop` volume +is mounted below that library root, so upgrades keep all completed downloads. +On the first `I/O Operation Failed` result, transient `.acf`, download, and temp +state is moved into `.progression-recovery`; completed mod directories are left +untouched. Startup also checks that the volume has enough free space for the +remaining Steam metadata estimate. The image does not switch Unix users at startup because some rootless and capability-restricted container hosts block `setuid`. The process remains inside diff --git a/deploy/compose.yaml b/deploy/compose.yaml index 00ab6b8..68c129d 100644 --- a/deploy/compose.yaml +++ b/deploy/compose.yaml @@ -18,7 +18,7 @@ services: CONTENT_COLLECTION_URL: ${CONTENT_COLLECTION_URL:-https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712} COSMETICS_COLLECTION_URL: ${COSMETICS_COLLECTION_URL:-https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646} STEAMCMD_PATH: /opt/steamcmd/steamcmd.sh - STEAMCMD_WORKSHOP_DIR: /opt/steamcmd/steamapps/workshop/content/294100 + STEAMCMD_WORKSHOP_DIR: /srv/steam-library/steamapps/workshop/content/294100 STEAMCMD_BATCH_SIZE: ${STEAMCMD_BATCH_SIZE:-25} STEAMCMD_TIMEOUT_SECONDS: ${STEAMCMD_TIMEOUT_SECONDS:-1800} REFRESH_INTERVAL_SECONDS: ${REFRESH_INTERVAL_SECONDS:-3600} @@ -26,7 +26,7 @@ services: INITIAL_DOWNLOAD_TIMEOUT_SECONDS: ${INITIAL_DOWNLOAD_TIMEOUT_SECONDS:-43200} volumes: - progression-data:/srv/progression/.data - - progression-workshop:/opt/steamcmd/steamapps + - progression-workshop:/srv/steam-library/steamapps volumes: progression-data: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 56b22e4..7f6723c 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -13,6 +13,6 @@ prepare_volume() { } prepare_volume /srv/progression/.data -prepare_volume /opt/steamcmd/steamapps +prepare_volume /srv/steam-library/steamapps exec "$@" diff --git a/progression_backend/workshop.py b/progression_backend/workshop.py index f220430..200b926 100644 --- a/progression_backend/workshop.py +++ b/progression_backend/workshop.py @@ -192,13 +192,10 @@ class SteamCmdRunner: raise ValueError(f"Invalid Workshop ids: {', '.join(invalid)}") self.ensure_workshop_writable() - command = [ - executable, - "+force_install_dir", - str(self.install_root), - "+login", - "anonymous", - ] + command = [executable] + if self.install_root.resolve() != Path(executable).resolve().parent: + command.extend(["+force_install_dir", str(self.install_root)]) + command.extend(["+login", "anonymous"]) for workshop_id in workshop_ids: command.extend( [ diff --git a/tests/test_backend.py b/tests/test_backend.py index 98d0936..fc1ce60 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -201,10 +201,16 @@ class BackendTests(unittest.TestCase): def test_steamcmd_runner_forces_mounted_install_root(self, run: Mock) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) - executable = root / "steamcmd" + executable = root / "steamcmd-bin" / "steamcmd" + executable.parent.mkdir() executable.write_text("", encoding="utf-8") content_dir = ( - root / "steamapps" / "workshop" / "content" / "294100" + root + / "steam-library" + / "steamapps" + / "workshop" + / "content" + / "294100" ) run.return_value = Mock(returncode=0, stdout="", stderr="") runner = SteamCmdRunner(str(executable), content_dir, 60) @@ -217,7 +223,7 @@ class BackendTests(unittest.TestCase): [ str(executable.resolve()), "+force_install_dir", - str(root), + str(root / "steam-library"), "+login", "anonymous", ],