og-cod-plutainer/.github/workflows
Amos 4ce33c637e
V2 layout (#4)
* v2: unify PLUTAINER_* env vars, restructure volume layout, harden entries

Volume layout (breaking — v1 volumes refused on start, see migrate-v1-to-v2.sh):
  app/configs/         user-facing real *.cfg files (flat); SOT
  app/logs/            stable symlinks (unchanged)
  app/runtime/gamefiles/, app/runtime/plutonium/
  app/.plutainer-version=2

Each entry script fans out symlinks from app/configs/*.cfg into the engine's
expected config dir, so games read configs through symlinks back to the
user-facing dir. RCON writeconfig writes back transparently.

Env vars unified: PLUTAINER_GAME/CONFIG_FILE/PORT/SERVER_NAME/MOD/AUTO_UPDATE/
HEALTHCHECK/SKIP_SEED/EXTRA_ARGS. Old PLUTO_/IW4X_/ALTER_ prefixes still work
via shim_env_vars in game-config.sh, emit [DEPRECATED] warning. PLUTO_SERVER_KEY,
PLUTO_MAX_CLIENTS, IW4X_NET_LOG_IP keep original names (single-family vars).

Other fixes folded in:
- Symlink existence guards via new link_files helper (eliminates dangling
  symlinks when host gamefiles are missing — same class as the prior t7x '*'
  bug).
- set -euo pipefail in all entry scripts.
- wget -N for t7x.exe (skip redownload when unchanged).
- HEALTHCHECK start-period: 1m -> 5m for slow first-run downloads.
- migrate-v1-to-v2.sh handles the volume restructure (supports --dry-run).
- .gitattributes pins shell/python/Dockerfile to LF.

* Fix shim_env_vars + detect_game_type under set -u

Indirect expansion ${!var} and bare ${VAR} both error out under set -u when
the referenced variable is unset. Add :- default so the strict mode in entry
scripts works when canonical PLUTAINER_* vars (or any old-prefix ones) are
absent.

* CI: build :v2 tag from v2-layout branch, keep separate from :latest

Workflow now triggers on pushes to main and v2-layout. Tag rules:
- :latest only when is_default_branch (= main).
- :v2    only when github.ref == refs/heads/v2-layout.

These conditions are mutually exclusive, so v2 builds never touch :latest
and main builds never touch :v2. Both branches still get a :sha-<short> tag.

Doc references updated from :main to :v2.

* v2: drop env-var shim; add raw-configs mode, auto-lift, hold/throttle, mod fan-out

Breaking (only on :v2 — :latest unaffected):
* shim_env_vars removed. PLUTO_*/IW4X_*/ALTER_* prefix env vars no longer
  recognised. PLUTAINER_* is the only accepted form (plus the three unique
  vars PLUTO_SERVER_KEY, PLUTO_MAX_CLIENTS, IW4X_NET_LOG_IP).

New:
* PLUTAINER_USE_RAW_CONFIGS=true opts out of the configs/ symlink system;
  engine path becomes the SOT directly. Default off.
* ensure_config_present: auto-lifts CONFIG_FILE from engine path to configs/
  if found as a real file there; refuses startup with case-insensitive
  find-iname hint if absent everywhere.
* resolve_mod_config_dir + link_configs variadic: when PLUTAINER_MOD is set,
  cfg symlinks fan out into BOTH the base engine config dir AND the
  mod-scoped dir (plutonium fs_game / iw4x mod path). Alterware skipped
  (Workshop ID, not a path).
* link_configs refuses to overwrite a real file at engine path; warns
  instead. Prevents the silent-replace failure mode.
* hold_indefinitely: on startup validation failure, exec sleep infinity so
  the container stays Up and docker doesn't loop restart it. User fixes
  the config and runs docker restart.
* launch_game: 30s sleep after wine exits before container exits, so
  restart policies (no min-delay knob in compose) throttle to ~1 restart
  per 30s on real crashes.
* Improved extract_rcon_password: handles single-quoted and unquoted
  forms; on failure emits a structured WARN telling the user the accepted
  forms and not to set rcon_password via PLUTAINER_EXTRA_ARGS. Doesn't
  block startup.

Docs updated (README + CLAUDE.md): removed deprecated-prefix table,
documented PLUTAINER_USE_RAW_CONFIGS, auto-lift, refuse behavior, restart
throttle, and the rcon parser accepted forms.

* Split auto-lift from ensure_config_present so seed can't pre-empt user file

Original order: seed_configs (cp -n) before ensure_config_present meant a
user's real cfg at engine path got bypassed because seed had already
filled configs/ with a default basename, satisfying the SOT check.

Now auto_lift_user_config runs before seed: if user has a real
(non-symlink) cfg at engine path, mv -f it to configs/ unconditionally.
Then seed gap-fills (cp -n preserves the user file). Then link_configs
fans out. Then ensure_config_present only does the presence-or-refuse
check.

Three callers updated: alterentry, plutoentry, iw4xentry.

* log-watcher: strip stray dirs at stable paths; reap orphan temp symlinks

A sidecar that bind-mounts an individual log file (e.g. games_zm.log) before
the file exists causes Docker to auto-create an empty root-owned directory at
that host path. place_symlink's mv -Tf then fails every poll and litters
logs/ with .XXX temp symlinks.

Fix both reactively in place_symlink and on startup heal. Try rmdir first
(empty dir, plutainer owns parent), fall back to rm -rf; if both fail (non-
empty root-owned dir), log a clear error pointing at the sidecar config.

* extract_rcon_password: accept bare 'rcon_password' (no set/seta prefix)

T6/T7 community cfgs commonly write `rcon_password "value"` without the
`set` keyword. Engine accepts both forms; parser only accepted the prefixed
form, so healthcheck and rcon-cli silently failed on stock t6zm seeds.

Make the `set[a]?` prefix optional and adjust the unquoted fallback to pick
the right awk column whether or not the prefix is present.

* Arm64 v2 (#3)

* Add ARM64 image variant with native Wine 11 via Hangover

New Dockerfile.arm64 targets aarch64 (Pi 5+, Ampere, Apple silicon under
Linux). Multi-stage build:

  1. rust-builder    -> native aarch64 plutonium-updater (upstream ships
                       no aarch64-linux release).
  2. cpp-builder     -> native aarch64 iw4x-launcher via build2 0.18.1
                       compiled from source.
  3. runtime         -> Debian 13 trixie + Hangover 11.4 (.deb bundle:
                       Wine 11 + FEX 2603 + box64 as Wine CPU plug-ins).

x86_64 Windows PE binaries (Plutonium/IW4x/T7x server exes) run under
Wine 11 thunked WoW64 with libarm64ecfex.dll as the CPU backend. No
user-space x86 emulator is required because the helper CLIs are native
aarch64.

scripts/ unchanged - the entrypoints call /home/plutainer/.plutainer/
{plutonium-updater,iw4x-launcher} by fixed path, and we drop a native
aarch64 ELF at exactly that path. The amd64 Dockerfile and its
workflow are untouched.

CI: .github/workflows/docker-publish-arm64.yml builds via QEMU on
ubuntu-latest and pushes ghcr.io/<repo>:arm64. Comment in the file
notes the ubuntu-24.04-arm runner swap for a 3-5x speedup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add curl to arm64 cpp-builder stage so build2 installer runs

The build2-install-*.sh script unconditionally `check_cmd curl`s before
fetching the toolchain source tarball; the stage shipped only wget, so
the installer exited 1 on first ARM64 build. Add curl to the apt list
and use it for the script download too for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Switch arm64 CI to native ubuntu-24.04-arm runner

Plutainer is a public repo, so ARM-hosted runners are free. The
Multi-Stage Dockerfile compiles build2 from source and runs full
cargo/build2 builds; QEMU-emulated on ubuntu-latest that runs 60-90 min
and exposes flaky edge cases. Native aarch64 should land at 20-30 min
and removes QEMU as a failure surface.

setup-qemu-action removed (not needed on native arm runner).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Adapt arm64 image to v2-layout: add migrate-v1-to-v2.sh, retag

Two narrow tweaks on top of the v2 scripts:

* Dockerfile.arm64: include migrate-v1-to-v2.sh in the chmod list so the
  v2 entrypoint can find it executable (was missing because the original
  arm64 commit predated v2).
* CI workflow: trigger on `arm64-v2` and `v2-layout` too, and emit a
  clean `:arm64-v2` tag for those branches instead of the v1-style
  `arm64-{{branch}}` pattern which would produce `arm64-arm64-v2`.

scripts/ themselves come unchanged from upstream v2-layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v2: combined v1-deployment refusal + MIGRATION.md + workflow for :latest promotion

Pre-promotion to :latest, the v2 image needs to refuse v1 deployments
cleanly. Previously the volume check printed its own refusal block but the
env-var side was silent — a user pulling :latest (post-promotion) onto
their v1 compose would have hit "PLUTAINER_CONFIG_FILE not set" with no
hint that the rename was the cause.

Changes:

- detect_legacy_env_vars: scans PLUTO_*/IW4X_*/ALTER_* names, populates
  LEGACY_ENVS_FOUND. Returns non-zero if any are set.
- check_volume_version: still validates marker / inits fresh / detects v1,
  but no longer prints the v1 block inline. Sets V1_VOLUME_DETECTED=true
  on v1 detection and returns. Marker-mismatch errors still print.
- print_v1_migration_block: combined refusal block, adapts to what was
  detected (env, volume, or both). Shows both paths side by side —
  pin :v1-final or migrate via the bundled tool + env rename.
- entrypoint.sh: runs both detectors before deciding to hold, so a single
  block surfaces every reason for refusal.
- MIGRATION.md: full env mapping table, step-by-step migration, rollback.
- README.md: top banner rewritten for the post-promotion tag layout.
  :latest = v2, :v1-final = frozen v1.
- docker-publish.yml + docker-publish-arm64.yml: :v2 / :arm64-v2 enable
  predicates now also fire on main, so the post-merge build publishes
  both the canonical (:latest / :arm64) and the alias tags.

---------

Co-authored-by: nellydocs <71311423+nellydocs@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 11:56:13 +01:00
..
docker-publish-arm64.yml V2 layout (#4) 2026-05-25 11:56:13 +01:00
docker-publish.yml V2 layout (#4) 2026-05-25 11:56:13 +01:00