fix: container volumes
Build and Publish Container / Publish Linux container (push) Successful in 4m4s

This commit is contained in:
2026-10-05 15:25:09 -04:00
parent 86b8ab9c75
commit b2df81130e
6 changed files with 77 additions and 4 deletions
+7 -3
View File
@@ -4,7 +4,7 @@ ARG DEBIAN_FRONTEND=noninteractive
RUN dpkg --add-architecture i386 \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl lib32gcc-s1 python3 python3-pip python3-venv \
ca-certificates curl gosu 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 \
@@ -19,9 +19,12 @@ RUN python3 -m venv /srv/venv \
COPY --chown=progression:progression progression_backend ./progression_backend
COPY --chown=progression:progression art ./art
USER progression
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/progression-entrypoint
ENV PROGRESSION_HOST=0.0.0.0 \
ENV HOME=/home/progression \
USER=progression \
XDG_DATA_HOME=/home/progression/.local/share \
PROGRESSION_HOST=0.0.0.0 \
PROGRESSION_PORT=8000 \
PROGRESSION_DB_PATH=.data/progression.db \
STEAMCMD_PATH=/opt/steamcmd/steamcmd.sh \
@@ -35,4 +38,5 @@ 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
ENTRYPOINT ["/usr/local/bin/progression-entrypoint"]
CMD ["/srv/venv/bin/python", "-m", "progression_backend"]
+2
View File
@@ -19,4 +19,6 @@ On a new deployment, the hourly refresh timer stays paused while the first full
Workshop download runs. Its hard limit defaults to 12 hours and can be changed
with `INITIAL_DOWNLOAD_TIMEOUT_SECONDS`. Follow the per-mod title, Workshop ID,
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.
+19
View File
@@ -0,0 +1,19 @@
#!/bin/sh
set -eu
prepare_volume() {
path="$1"
marker="$path/.progression-volume-ready"
mkdir -p "$path"
if [ ! -e "$marker" ]; then
echo "Preparing persistent volume permissions: $path"
chown -R progression:progression "$path"
touch "$marker"
chown progression:progression "$marker"
fi
}
prepare_volume /srv/progression/.data
prepare_volume /opt/steamcmd/steamapps
exec gosu progression "$@"
+12
View File
@@ -293,6 +293,18 @@ class RefreshService:
result = self.runner.run_batch(
batch, timeout_seconds=remaining
)
if result.status != "completed":
logger.warning(
"SteamCMD pass %d batch %d/%d finished with "
"status=%s exit_code=%s after %.1fs. Output:\n%s",
pass_number,
pass_batch_number,
len(batches),
result.status,
result.exit_code,
result.elapsed_seconds,
result.output_tail or "(no SteamCMD output)",
)
self.database.add_download_attempt(
{
"run_id": run_id,
+15
View File
@@ -116,6 +116,20 @@ class SteamCmdRunner:
def available(self) -> bool:
return self.executable_path() is not None
def ensure_workshop_writable(self) -> None:
workshop_dir = self.content_dir.parent.parent
try:
workshop_dir.mkdir(parents=True, exist_ok=True)
self.content_dir.mkdir(parents=True, exist_ok=True)
probe = workshop_dir / ".progression-write-test"
probe.write_text("ok", encoding="utf-8")
probe.unlink()
except OSError as error:
raise PermissionError(
f"SteamCMD Workshop directory is not writable: {workshop_dir}. "
"Check the container volume ownership."
) from error
def run_batch(
self,
workshop_ids: list[str],
@@ -129,6 +143,7 @@ class SteamCmdRunner:
invalid = [item for item in workshop_ids if not item.isdigit()]
if invalid:
raise ValueError(f"Invalid Workshop ids: {', '.join(invalid)}")
self.ensure_workshop_writable()
command = [executable, "+login", "anonymous"]
for workshop_id in workshop_ids:
+22 -1
View File
@@ -23,7 +23,12 @@ from progression_backend.modlist import (
)
from progression_backend.refresh import RefreshCoordinator, RefreshService
from progression_backend.steam_api import WorkshopDetails
from progression_backend.workshop import SteamCmdBatchResult, parse_about_xml, utc_now
from progression_backend.workshop import (
SteamCmdBatchResult,
SteamCmdRunner,
parse_about_xml,
utc_now,
)
class FakeSteam:
@@ -176,6 +181,22 @@ class BackendTests(unittest.TestCase):
self.assertEqual(parsed.package_id, "author.mod")
self.assertEqual(parsed.display_name, "A & B")
def test_steamcmd_runner_prepares_writable_workshop_path(self) -> None:
with tempfile.TemporaryDirectory() as directory:
content_dir = (
Path(directory)
/ "steamapps"
/ "workshop"
/ "content"
/ "294100"
)
runner = SteamCmdRunner("steamcmd", content_dir, 60)
runner.ensure_workshop_writable()
self.assertTrue(content_dir.is_dir())
self.assertFalse(
(content_dir.parent.parent / ".progression-write-test").exists()
)
def test_rml_uses_workshop_id_and_escapes_names(self) -> None:
payload = generate_rml(
[