114748a54f
- 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
323 lines
13 KiB
Markdown
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).
|