diff --git a/Patch-Release-Workflow.-.md b/Patch-Release-Workflow.-.md index 4113dfd..88688a9 100644 --- a/Patch-Release-Workflow.-.md +++ b/Patch-Release-Workflow.-.md @@ -1,160 +1,322 @@ -# Patch Release Workflow +# Patch & Release Workflow -How to ship resource/script/asset updates to live desktop players without replacing the full game binary. +Complete guide for shipping updates to Tekton players — hot patches (`.pck`) for content changes and full binary releases for engine/platform changes. -## Platform support +--- -One shared `patch.pck` is used by all desktop builds: +## Overview -- Windows ✅ -- Linux ✅ -- macOS ✅ +Two automated CI pipelines handle all distribution: -This works because Godot mounts `.pck` files into the virtual `res://` filesystem. Platform does not matter for normal Godot resources, scenes, scripts, textures, audio, and data files. +| Pipeline | Trigger | Output | Delivery | +|---|---|---|---| +| **Deploy Patch** (`deploy_patch.yml`) | Manual workflow dispatch | `patch.pck` + `version.json` → `patches` branch | Gitea raw endpoint | +| **Release** (`ci.yml`) | Git tag `v*` push | Windows/Linux/macOS `.zip` → Gitea Release | git.klud.top releases | -Use a full release (`ci.yml`) instead when the change touches platform-native pieces: +--- -- Windows `.exe` -- Linux executable wrapper -- macOS `.app`, signing, entitlements -- native plugins: `.dll`, `.so`, `.dylib` -- export templates or engine/runtime version -- anything that must exist before Godot can start +## Infrastructure -## Public patch URLs +### Gitea instance +- **URL:** https://git.klud.top +- **API:** http://52.74.133.55:3000/api/v1 +- **Runner:** Local Docker container (`gitea-runner`) via `docker-compose` +- **Cache volume:** `/home/dev/godot-cache` → `/cache` (rw) inside runner containers +- **Secret:** `TEKTON_RELEASE_TOKEN` — Token from user `adtpdn` with repo write access -Players download patch metadata and content from Gitea Pages: +### Patch serving +Patches served directly from Gitea's built-in raw file endpoint — no external CDN: +- Manifest: `https://git.klud.top/danchie/tekton/raw/branch/patches/version.json` +- PCK: `https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck` -```text -https://raw.klud.top/danchie/tekton/version.json -https://raw.klud.top/danchie/tekton/patch.pck -``` +Old `raw.klud.top` (gitea-pages container) retired — Gitea raw endpoint is faster, simpler, and always available. -`raw.klud.top` is Caddy reverse proxy → `gitea-pages` container → `patches` branch of this repo. +### Release page +- **URL:** https://git.klud.top/danchie/tekton/releases +- Assets auto-uploaded by CI on tag push -No client auth required. +--- -## 1. Write changelog +## Part 1: Hot Patch (content-only updates) -Edit `CHANGELOG_DRAFT.md` and add player-facing notes under: +Use when: script changes, UI tweaks, balance patches, asset replacements, config changes. -```md +### Step-by-step + +**1. Write changelog** + +Edit `CHANGELOG_DRAFT.md` — add player-facing notes under `## [NEXT]`: + +```markdown ## [NEXT] +- Fixed playerboard desync in multiplayer. +- Adjusted Gauntlet difficulty scaling. ``` -Example: +If `[NEXT]` is missing, add the header. Format is markdown list items without leading dash (the tool strips it). Each line becomes a bullet on the patch notes page. -```md -## [NEXT] -- Fixed Ghost powerup not bypassing sticky tiles. -- Added new Gauntlet reward UI polish. -``` - -## 2. Preview locally - -Generate local `assets/data/version.json`: +**2. Commit to `experimental`** ```bash -python3 tools/generate_version_json.py --local -``` - -Run `BootScreen` in Godot. Editor mode skips remote download and reads local `assets/data/version.json`. - -## 3. Commit changes to `experimental` - -```bash -git add CHANGELOG_DRAFT.md assets/data/version.json project.godot export_presets.cfg -git commit -m "chore: prepare patch notes" +git add CHANGELOG_DRAFT.md +git commit -m "docs: patch notes for next release" git push origin experimental ``` -## 4. Run Gitea Actions patch deploy +**3. Trigger patch deploy workflow** -Open: +Navigate to the Actions tab: -```text +``` https://git.klud.top/danchie/tekton/actions ``` -Run workflow: +Click **Deploy Patch** → **Run workflow**: -```text -Deploy Patch -``` +| Field | Example | +|---|---| +| **Patch version** | `2.4.3` | +| **Release notes** | `fix: multiplayer desync, gauntlet balance` | -The workflow `.gitea/workflows/deploy_patch.yml` does this: - -1. Clones repo using `god` token. -2. Installs Godot 4.7 stable. -3. Runs `tools/generate_version_json.py --skip-changelog`. -4. Builds one shared `build/patch.pck` with `godot --headless --export-pack`. -5. Pushes `version.json` + `patch.pck` to the `patches` branch. -6. `gitea-pages` serves them at `https://raw.klud.top/danchie/tekton/...`. - -## 5. Verify deploy - -Check manifest: +OR via API: ```bash -curl https://raw.klud.top/danchie/tekton/version.json +curl -X POST "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/workflows/deploy_patch.yml/dispatches" \ + -H "Authorization: token $TEKTON_RELEASE_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"ref":"experimental","inputs":{"version":"2.4.3","notes":"fix: multiplayer desync, gauntlet balance"}}' ``` -Check patch file exists: +### What the CI does (deploy_patch.yml) + +1. **Checkout** — `git clone --depth 1` from `experimental` branch (shallow = fast). +2. **Setup Godot** — Uses cached `/cache/godot_4.7` binary. Downloads only if missing (140MB, cached forever). +3. **Generate version.json** — Runs `tools/generate_version_json.py --skip-changelog`. Reads version from `project.godot`, bumps patch number, writes `assets/data/version.json` with the new release entry including `pck_url` pointing to Gitea raw endpoint. +4. **Export patch PCK** — `godot --headless --export-pack "Windows Desktop" build/patch.pck`. No export templates needed — `--export-pack` only packs resources, not binaries. Output ~10-15MB. +5. **Push to patches branch** — Force-pushes `patch.pck` + `version.json` to the `patches` branch of the repo. + +### Verification ```bash -curl -I https://raw.klud.top/danchie/tekton/patch.pck +# Check manifest +curl -s "https://git.klud.top/danchie/tekton/raw/branch/patches/version.json" +# Expected: latest_version matches your patch number + +# Check pck exists +curl -s -o /dev/null -w "%{http_code} %{size_download}B" \ + "https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck" +# Expected: HTTP 200, size ~10-15MB ``` -Expected: HTTP `200`. +### How players receive patches -## 6. Players receive patch +1. Game boots → `GameUpdateManager` fetches `version.json` from Gitea raw endpoint. +2. Compares `latest_version` against local version. +3. If remote is newer → downloads `patch.pck` to `user://patch.pck`. +4. Mounts with `ProjectSettings.load_resource_pack("user://patch.pck")`. +5. All files in `patch.pck` override base `res://` files in memory. +6. No files are overwritten on disk (safe rollback by deleting `patch.pck`). -On boot, game client fetches: +--- -```gdscript -const VERSION_MANIFEST_URL := "https://raw.klud.top/danchie/tekton/version.json" +## Part 2: Full Binary Release (platform updates) + +Use when: engine upgrade, native plugin change, export template update, platform-specific build fix, or any change that needs a new `.exe`/`.app`. + +### Step-by-step + +**1. Ensure changelog is written** + +Same as patch step 1 — `CHANGELOG_DRAFT.md` must have `## [NEXT]` entries. The CI auto-extracts them for the release body. + +**2. Commit and tag** + +```bash +# Commit all changes +git add -A +git commit -m "chore: bump to v2.4.3" + +# Push to experimental +git push origin experimental + +# Create and push tag +git tag v2.4.3 experimental +git push origin v2.4.3 ``` -If remote version is newer, client downloads `patch.pck` to `user://` and mounts it with `ProjectSettings.load_resource_pack(...)`. +**IMPORTANT:** Tag must match `v` + version format (e.g. `v2.4.3`). The CI is triggered by `v*` tags. -## How the patch system works +### What the CI does (ci.yml) -- **Virtual filesystem:** Godot mounts `patch.pck` over `res://` in memory. -- **No physical overwrite:** Local source files are not changed. -- **Override priority:** Files inside mounted patch can override base game files with same paths. -- **Editor bypass:** In Godot Editor, `BootScreen` uses local `assets/data/version.json` for fast preview. +1. **Install tools** — `apt-get install curl unzip zip` (zip was missing in early runs — make sure it's present). +2. **Checkout** — Full clone from tag (shallow not used — needs full history for changelog extraction, though `--depth 1` works too). +3. **Setup Godot with templates** — Caches both Godot binary (140MB) and export templates (1.3GB) in `/cache/`. Templates downloaded once per runner lifetime. +4. **Export 3 platforms:** + - **Windows** — `godot --headless --export-release "Windows Desktop"` → zipped with `zip`. + - **Linux/X11** — Same pattern. + - **macOS** — Export to `.zip` directly (Godot's macOS export produces a zip). + - **Note:** Steam DLLs copied into Windows build from `addons/godotsteam/`. + - **Note:** `|| true` on export commands masks Godot errors (e.g. GodotSteam plugin warnings). Real failures (missing `zip`) will surface. +5. **Extract changelog** — Parses `CHANGELOG_DRAFT.md` for the `## [version]` section matching the tag. Writes to `$CHANGELOG_BODY` env var. +6. **Create/Update Gitea Release** — Checks if release exists for tag. Creates new one with changelog as body if missing. Updates draft release if re-run. +7. **Upload assets** — Each `.zip` uploaded as release asset via multipart POST. +8. **Publish** — Sets `draft:false` to make release public. -## Architecture +### Verification -```mermaid -flowchart TD - subgraph Local Dev - A[CHANGELOG_DRAFT.md] -->|tools/generate_version_json.py --local| B[assets/data/version.json] - B -->|Editor preview| C[BootScreen] - end +Check the release page: - subgraph Gitea Actions - D[Deploy Patch workflow] --> E[Godot 4.7 export-pack] - E --> F[patch.pck] - F --> G[push version.json + patch.pck] - end - - subgraph Hosting - G --> H[patches branch] - H --> I[gitea-pages] - I --> J[https://raw.klud.top/danchie/tekton] - end - - subgraph Player Client - J -->|download version.json| K[Compare versions] - K -->|download patch.pck| L[user://patch.pck] - L -->|load_resource_pack| M[Godot res:// overlay] - M --> N[Game starts patched] - end +``` +https://git.klud.top/danchie/tekton/releases/tag/v2.4.3 ``` -## Rule of thumb +Expected: 3 assets (Windows, Linux, macOS) with correct sizes, changelog body populated, release marked as published (not draft). -Use patch deploy for Godot content changes. +### Cleaning duplicate assets -Use full release for engine, native, executable, signing, or platform-specific changes. +If a tag was force-pushed, multiple CI runs may upload duplicate assets to the same release: + +```bash +# List assets +curl "https://git.klud.top/api/v1/repos/danchie/tekton/releases/tags/v2.4.3" \ + -H "Authorization: token $TEKTON_RELEASE_TOKEN" | jq '.assets[] | "\(.id): \(.name) \(.size/1024/1024)MiB"' + +# Delete old duplicates (keep latest 3: Windows, Linux, macOS) +RELEASE_ID= +curl -X DELETE "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets/$ASSET_ID" \ + -H "Authorization: token $TEKTON_RELEASE_TOKEN" +``` + +### Cancelling stuck or duplicate runs + +Gitea API cannot cancel in-progress runs. Wait for completion, then delete: + +```bash +# List runs for a tag +curl "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/runs?page=1&limit=10" \ + -H "Authorization: token $TEKTON_RELEASE_TOKEN" | jq '.workflow_runs[] | "\(.id): \(.status) \(.conclusion) \(.display_title)"' + +# Delete completed run +curl -X DELETE "http://52.74.133.55:3000/api/v1/repos/danchie/tekton/actions/runs/$RUN_ID" \ + -H "Authorization: token $TEKTON_RELEASE_TOKEN" +``` + +--- + +## Part 3: Agent-Automated Release + +Agent (Hermes) can execute the full release flow from a single user request: + +### Scenario: "Ship v2.4.3" + +Agent actions: +1. Read `CHANGELOG_DRAFT.md` — verify `[NEXT]` has entries. +2. Check `project.godot` current version. +3. Commit changelog to `experimental`. +4. Create tag `v2.4.3` → push to trigger `ci.yml`. +5. Wait for CI completion (poll every 30s, up to 30 min). +6. If CI fails: + - Read job logs for failure reason. + - Fix the workflow file, commit, force-push tag. + - Clean up duplicate assets after re-run. +7. Verify release page has 3 assets with correct sizes. +8. If patch deploy also needed: + - Trigger `deploy_patch.yml` dispatch. + - Verify `patch.pck` is served and version.json updated. + +### Scenario: "Quick hot patch" + +Agent actions: +1. Check if `[NEXT]` has entries in `CHANGELOG_DRAFT.md`. +2. If empty, ask user for changelog notes. +3. Commit `CHANGELOG_DRAFT.md` to `experimental`. +4. Dispatch `deploy_patch.yml` workflow. +5. Verify patch files on Gitea raw endpoint. + +--- + +## Troubleshooting + +### `zip: command not found` in CI + +Root cause: `ubuntu-latest` container doesn't have `zip` pre-installed. The install step must include `zip`: + +```yaml +- name: Install tools + run: apt-get update -qq && apt-get install -y -qq curl unzip zip +``` + +### Godot export fails silently (`|| true`) + +The `|| true` on export commands means a failed Godot export still shows step as success. Check: +- Is `godot_4.7` cached at `/cache/`? +- Does the export preset name match exactly? E.g. `"Windows Desktop"` must match `export_presets.cfg`. +- Is `addons/godotsteam/libgodotsteam*` present? Missing DLLs cause Godot to exit 1. + +### Runner container can't clone repo + +Runner uses HTTP auth with `god` username and `TEKTON_RELEASE_TOKEN` as password. If token is revoked: +1. Generate new token from Gitea → Settings → Applications. +2. Update secret `TEKTON_RELEASE_TOKEN` in repo Settings → Actions → Secrets. +3. Restart runner: `docker compose -f /home/dev/gitea/docker-compose.yml restart runner`. + +### Runner shows "permission denied" for Docker socket + +User `dev` doesn't have Docker socket access. Commands that touch Docker must be run via `sudo` or by the root user on the VPS. The local agent can only: +- Restart runner via systemd: `systemctl --user restart docker-runner` (if running as user service). +- No Docker CLI commands from agent terminal. + +### Release has duplicate assets + +Each CI run uploads assets as new entries. To clean: +- Get release ID from API. +- Delete old asset IDs keeping only latest (highest IDs) for each platform. +- Use jq or manual curl loop (see "Cleaning duplicate assets" above). + +### Tag force-push creates redundant CI runs + +Each push to a tag triggers `ci.yml`. Force-pushing a tag to a new commit creates another run: +- Previous runs keep running (can't cancel via API). +- Wait for all to finish, then delete stale ones. +- The last run to publish sets the release state. + +Best practice: Delete old release before force-pushing tag, or at minimum delete stale completed runs after. + +### Patch manifest not updating + +`generate_version_json.py --skip-changelog` only bumps version and writes `version.json`. If the version didn't change (e.g. `--skip-changelog` with no `[NEXT]` entries), the script exits with code 0 but doesn't write anything. Verify `assets/data/version.json` has the new version after CI run. + +### `gitea-pages` (raw.klud.top) returns 404 + +gitea-pages container uses a Gitea token to read files. If the token is dead: +- Switch to Gitea native raw endpoint: `https://git.klud.top/danchie/tekton/raw/branch/patches/...` +- Update `MANIFEST_URL` in `generate_version_json.py` and `VERSION_MANIFEST_URL` in `game_update_manager.gd`. +- Retire gitea-pages container entirely (not needed, Gitea has built-in raw serving). + +--- + +## File Reference + +| File | Purpose | +|---|---| +| `.gitea/workflows/deploy_patch.yml` | Patch deploy CI — generates pck + pushes to patches branch | +| `.gitea/workflows/ci.yml` | Full binary release CI — exports 3 platforms + creates release | +| `tools/generate_version_json.py` | Version bumping + changelog → version.json conversion | +| `CHANGELOG_DRAFT.md` | Human-readable changelog draft (source of truth for release notes) | +| `assets/data/version.json` | Machine-readable manifest served to players (auto-generated) | +| `scripts/managers/game_update_manager.gd` | Client-side update checker (reads version.json → downloads patch.pck) | +| `project.godot` | Godot project file (config/version = source of truth for version number) | +| `export_presets.cfg` | Export configuration for all platforms | +| `/home/dev/gitea/docker-compose.yml` | Runner container composition (cache volume mount: `/home/dev/godot-cache:/cache`) | + +--- + +## Key Gotchas + +- **`zip` must be in install step** — missing zip kills Windows/Linux export. Added in run 141 — do not remove. +- **Tag format is `vX.Y.Z`** — `ci.yml` trigger is `v*`. A tag without `v` prefix won't build. +- **Force-push tag = new CI run** — Always expect a new run on force-push. Old run keeps running. +- **Changelog extracted from tag version** — `## [X.Y.Z]` section in `CHANGELOG_DRAFT.md`. If section doesn't exist, release body is empty. +- **Patch deploy skips changelog clearing** — `--skip-changelog` means `version.json` is written but `CHANGELOG_DRAFT.md` is NOT modified. Only the full `ci.yml` pipeline clears it. +- **Cache is per-runner-host, not per-run** — Godot binary (140MB) and templates (1.3GB) download once on fresh runner container, then persist via `/cache` volume. Running `docker compose down` + `up` reuses cache if volume isn't deleted. +- **`|| true` masks Godot export errors** — If export fails silently, check the `2>&1 | tail -5` output in CI logs. Error messages like "Cannot call method 'queue_free' on a null value" from GodotSteam are non-fatal (cosmetic plugin warnings).