Skip to content

V2 layout - #4

Merged
Ayymoss merged 9 commits into
mainfrom
v2-layout
May 25, 2026
Merged

V2 layout#4
Ayymoss merged 9 commits into
mainfrom
v2-layout

Conversation

@Ayymoss

@Ayymoss Ayymoss commented May 25, 2026

Copy link
Copy Markdown
Owner

No description provided.

Ayymoss and others added 9 commits May 13, 2026 18:01
…ries

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.
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.
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.
…, 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.
…r 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.
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.
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.
* 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>
…est 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.
@Ayymoss
Ayymoss merged commit 4ce33c6 into main May 25, 2026
0 of 2 checks passed
@Ayymoss
Ayymoss deleted the v2-layout branch May 25, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants