From 84554d42b8ae54d1cf7bebbffc71364038fcb1b2 Mon Sep 17 00:00:00 2001 From: Baylem Date: Tue, 10 Nov 2026 20:42:02 -0500 Subject: [PATCH 1/2] fix: remove live quiesce hooks from flush-only modules Signed-off-by: Baylem --- .github/workflows/validate.yml | 3 +++ ark-survival-ascended/README.md | 7 +++++++ ark-survival-ascended/module.yaml | 2 +- ark-survival-ascended/template.yaml | 5 +---- project-zomboid/README.md | 11 ++++++----- project-zomboid/module.yaml | 2 +- project-zomboid/template.yaml | 8 +------- test-backup-capabilities.py | 25 +++++++++++++++++++++++++ 8 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 test-backup-capabilities.py diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 5383574..d31dfc3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -36,6 +36,9 @@ jobs: - name: Install PyYAML run: pip install --disable-pip-version-check --user pyyaml + - name: Check flush-only backup capabilities + run: python3 test-backup-capabilities.py + - name: Run module preflight validator run: python3 validate.py diff --git a/ark-survival-ascended/README.md b/ark-survival-ascended/README.md index bff4ec9..981b066 100644 --- a/ark-survival-ascended/README.md +++ b/ark-survival-ascended/README.md @@ -104,6 +104,13 @@ actually holds saves and `GameUserSettings.ini`. Default size is 30 GiB; ASA's base install alone is roughly 11 GiB, so heavy mod/save usage may need more. +## Backups + +**Save world** runs `SaveWorld`. ARK can change world and player files again +while a live backup is copying them. For a stopped copy, shut down the server, +wait for it to stop, then request a backup with quiesce enabled. Keep the +server stopped until the backup completes. + ## Known limitations - `SaveWorld`/`DoExit` and `ServerChat` are corroborated by community diff --git a/ark-survival-ascended/module.yaml b/ark-survival-ascended/module.yaml index 501f535..49721d7 100644 --- a/ark-survival-ascended/module.yaml +++ b/ark-survival-ascended/module.yaml @@ -2,7 +2,7 @@ apiVersion: gameplane.local/module/v1 name: ark-survival-ascended displayName: "ARK: Survival Ascended" -version: 1.0.8 +version: 1.0.9 game: ark-survival-ascended categories: [Survival, PvP, Co-op, Modded] summary: "ARK: Survival Ascended dedicated server (mschnitzer/asa-linux-server) with RCON console/moderation. CurseForge mods install via the game's own -mods= launch parameter." diff --git a/ark-survival-ascended/template.yaml b/ark-survival-ascended/template.yaml index 2bcf339..f02bcc4 100644 --- a/ark-survival-ascended/template.yaml +++ b/ark-survival-ascended/template.yaml @@ -59,7 +59,7 @@ metadata: spec: displayName: "ARK: Survival Ascended" game: ark-survival-ascended - version: 1.0.8 + version: 1.0.9 categories: [Survival, PvP, Co-op, Modded] accentColor: "#00b2e2" description: | @@ -157,9 +157,6 @@ spec: # sufficient. lifecycle: stop: ["SaveWorld", "DoExit"] - quiesce: - quiesce: ["SaveWorld"] - unquiesce: ["SaveWorld"] actions: - id: broadcast displayName: Broadcast diff --git a/project-zomboid/README.md b/project-zomboid/README.md index f66238a..cf2b73c 100644 --- a/project-zomboid/README.md +++ b/project-zomboid/README.md @@ -48,11 +48,12 @@ Default size is 15 GiB — heavy Workshop mod usage may need more. ## Backups -`AUTOSAVE_INTERVAL` controls the game's own periodic save; the **Save -world** action forces one immediately and is also what backup quiesce -runs before a snapshot. The image's own `BACKUPS_PERIOD`/`BACKUPS_COUNT` -env vars are not wired here — use the Gameplane Backup CRD as the -authoritative snapshot path instead. +`AUTOSAVE_INTERVAL` controls periodic saves. **Save world** forces an +immediate save. Project Zomboid can write map chunks and player data during +a live backup. For a stopped copy, shut down the server, wait for it to stop, +then request a backup with quiesce enabled. Keep the server stopped until the +backup completes. Use the Gameplane Backup CRD to request the copy. The +image also supports `BACKUPS_PERIOD` and `BACKUPS_COUNT` outside this template. ## Sample diff --git a/project-zomboid/module.yaml b/project-zomboid/module.yaml index 782581f..e0d856a 100644 --- a/project-zomboid/module.yaml +++ b/project-zomboid/module.yaml @@ -2,7 +2,7 @@ apiVersion: gameplane.local/module/v1 name: project-zomboid displayName: Project Zomboid -version: 1.0.5 +version: 1.0.6 game: project-zomboid categories: [Survival, Horror, Co-op] summary: Project Zomboid dedicated server (sknnr/zomboid-dedicated-server) with RCON console/moderation and Steam Workshop mods via the game's own MOD_IDS/MOD_NAMES sync. diff --git a/project-zomboid/template.yaml b/project-zomboid/template.yaml index 0e5a1dc..a11ad48 100644 --- a/project-zomboid/template.yaml +++ b/project-zomboid/template.yaml @@ -35,7 +35,7 @@ metadata: spec: displayName: Project Zomboid game: project-zomboid - version: 1.0.5 + version: 1.0.6 categories: [Survival, Horror, Co-op] accentColor: "#336600" description: | @@ -132,12 +132,6 @@ spec: # first is the commonly recommended safe sequence. lifecycle: stop: ["save", "quit"] - # `save` is a real, idempotent force-save command; there's no PZ - # equivalent of Minecraft's save-off/save-on write-pause pair, so - # quiesce and unquiesce both just force a flush around the snapshot. - quiesce: - quiesce: ["save"] - unquiesce: ["save"] actions: - id: broadcast displayName: Broadcast diff --git a/test-backup-capabilities.py b/test-backup-capabilities.py new file mode 100644 index 0000000..3f2a2d0 --- /dev/null +++ b/test-backup-capabilities.py @@ -0,0 +1,25 @@ +"""Check the backup and manual save declarations for flush-only engines.""" + +from pathlib import Path +import unittest + +import yaml + + +class BackupCapabilitiesTest(unittest.TestCase): + def test_flush_only_games_offer_manual_save_and_clean_stop(self): + for game, save, stop in ( + ("ark-survival-ascended", "SaveWorld", "DoExit"), + ("project-zomboid", "save", "quit"), + ): + with self.subTest(game=game): + template = Path(__file__).parent / game / "template.yaml" + caps = yaml.safe_load(template.read_text(encoding="utf-8"))["spec"]["capabilities"] + self.assertNotIn("quiesce", caps) + self.assertEqual(caps["lifecycle"]["stop"], [save, stop]) + actions = {action["id"]: action["command"] for action in caps["actions"]} + self.assertEqual(actions["save-world"], save) + + +if __name__ == "__main__": + unittest.main() From dcc11c0064bd8b2504cfef15fa82a2a24eebfb6d Mon Sep 17 00:00:00 2001 From: Baylem Date: Wed, 11 Nov 2026 00:45:11 -0500 Subject: [PATCH 2/2] feat(modules): describe the verified Minecraft checkpoint capability Signed-off-by: Baylem --- .schema/gametemplate.schema.json | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/.schema/gametemplate.schema.json b/.schema/gametemplate.schema.json index 871ae7c..5332336 100644 --- a/.schema/gametemplate.schema.json +++ b/.schema/gametemplate.schema.json @@ -237,6 +237,13 @@ "maxItems": 32, "type": "array" }, + "backupFlush": { + "description": "BackupFlush requests an acknowledged checkpoint without disabling writes.", + "enum": [ + "minecraft-save-all" + ], + "type": "string" + }, "lifecycle": { "description": "Lifecycle configures graceful lifecycle transitions the agent drives\nover RCON \u2014 currently the in-game stop sequence the operator runs\nbefore scaling the server down, so the world is saved and shut down\ncleanly instead of relying on a container SIGTERM. Requires\nrcon.protocol != none.", "properties": { @@ -836,7 +843,13 @@ "type": "object" } }, - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": "choose a flush-only checkpoint or a write-freeze declaration", + "rule": "!(has(self.backupFlush) && has(self.quiesce))" + } + ] }, "categories": { "description": "Categories group this game in the dashboard's catalog and Create\nServer picker (e.g. [\"Survival\", \"Sandbox\"]). A game may belong to\nseveral at once \u2014 Minecraft is reasonably Sandbox, Survival and\nCreative. The dashboard builds its category filter from the distinct\nvalues present across installed templates, so a module introduces a\nnew category simply by naming it here \u2014 no frontend change. Empty\nfalls back to a heuristic on the game slug, and finally to \"Other\".\ndocs/module-authoring.md publishes the canonical vocabulary the\nofficial modules use.", @@ -2174,6 +2187,10 @@ { "message": "capabilities.mods.loaders requires a non-empty spec.versions catalog (a mod loader is selected by versions[].loader)", "rule": "!has(self.capabilities) || !has(self.capabilities.mods) || !has(self.capabilities.mods.loaders) || size(self.capabilities.mods.loaders) == 0 || (has(self.versions) && size(self.versions) > 0)" + }, + { + "message": "backupFlush requires the Minecraft Java adapter", + "rule": "!has(self.capabilities) || !has(self.capabilities.backupFlush) || self.game == 'minecraft-java'" } ] },