Files
tekton/wiki/patch_release_workflow.md
T
god 114748a54f experimental: remove Tekton Doors entirely
- Delete portal_mode_manager.gd, portal_door.gd, portal_door.tscn
- Strip all Tekton Doors logic from main.gd, player.gd, lobby.gd,
  lobby_room.gd, lobby_manager.gd, camera_context_manager.gd,
  music_manager.gd, tekton.gd, enhanced_gridmap.gd,
  playerboard_manager.gd, special_tiles_manager.gd
- Remove TK enum (TEKTON_DOORS=2), mode_config schema, arena area
- Update tests: 3 modes instead of 4
- Strip HowToPlay tab from main.tscn
2026-07-06 00:18:59 +08:00

323 lines
13 KiB
Markdown

# Patch & Release Workflow
Complete guide for shipping updates to Tekton players — hot patches (`.pck`) for content changes and full binary releases for engine/platform changes.
---
## Overview
Two automated CI pipelines handle all distribution:
| 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 |
---
## Infrastructure
### 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
### 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`
Old `raw.klud.top` (gitea-pages container) retired — Gitea raw endpoint is faster, simpler, and always available.
### Release page
- **URL:** https://git.klud.top/danchie/tekton/releases
- Assets auto-uploaded by CI on tag push
---
## Part 1: Hot Patch (content-only updates)
Use when: script changes, UI tweaks, balance patches, asset replacements, config changes.
### 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.
```
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.
**2. Commit to `experimental`**
```bash
git add CHANGELOG_DRAFT.md
git commit -m "docs: patch notes for next release"
git push origin experimental
```
**3. Trigger patch deploy workflow**
Navigate to the Actions tab:
```
https://git.klud.top/danchie/tekton/actions
```
Click **Deploy Patch****Run workflow**:
| Field | Example |
|---|---|
| **Patch version** | `2.4.3` |
| **Release notes** | `fix: multiplayer desync, gauntlet balance` |
OR via API:
```bash
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"}}'
```
### 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
# 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
```
### How players receive patches
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`).
---
## 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
```
**IMPORTANT:** Tag must match `v` + version format (e.g. `v2.4.3`). The CI is triggered by `v*` tags.
### What the CI does (ci.yml)
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.
### Verification
Check the release page:
```
https://git.klud.top/danchie/tekton/releases/tag/v2.4.3
```
Expected: 3 assets (Windows, Linux, macOS) with correct sizes, changelog body populated, release marked as published (not draft).
### Cleaning duplicate assets
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=<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).