Godot 4.6 produces an unsigned APK in headless mode on this runner — verified by apksigner: "DOES NOT VERIFY, Missing META-INF/MANIFEST.MF". The internal sign step seems to bail silently (likely because the apksigner subprocess can't locate java without inheriting JAVA_HOME). The export step still reports success. Sign the APK ourselves after the export with the same debug keystore the workflow provisions. Idempotent if Godot ever starts signing again; mandatory until then. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea Actions CI
Workflow defined in build.yml. Triggered on push / PR to
dev and main, or manually via workflow_dispatch.
Jobs
All jobs run on the default ubuntu-latest runner image (which already
ships Node, Python, git, wget, unzip, etc.). Godot is installed per-job
via the local composite action .gitea/actions/setup-godot,
which downloads the official Linux binary from the godotengine GitHub
release and (optionally) export templates into $HOME.
| Job | Tooling installed by the job | Role |
|---|---|---|
validate |
Godot binary (no templates) | godot --headless --import then grep for SCRIPT ERROR / Parse Error. Uploads .godot/ cache. |
lint |
gdtoolkit==4.* via pip |
gdlint scripts db scenes. Parallel to validate; does not gate exports yet. |
export-desktop |
Godot binary + export templates | Matrix: Windows / Linux / macOS. Reuses the import cache, uploads each binary as artifact. |
export-android |
Godot binary + export templates + JDK 17 + Android SDK (under $GITHUB_WORKSPACE/.android-sdk) |
Provisions keystore, writes editor_settings-4.tres with SDK / JDK paths, exports APK. |
Artifacts are kept 14 days, accessible from the Gitea run page.
Prerequisites before the first successful run
- Godot version —
GODOT_VERSIONis set at the top of the workflow (currently4.6). The setup action expects a stable release on the godotengine GitHub releases page; bump in lockstep with the project. - macOS preset missing — add it in Godot Editor → Project → Export →
Add → macOS, name it exactly
macOS(or change the matrix entry). The.zipwill be unsigned; on Mac it needsxattr -dr com.apple.quarantineto launch. Linux/X11Debugpreset — Godot-3-era name. Reopen the project in Godot 4 once and re-save the preset (the editor may rename it). Update the matrixpreset:field accordingly if it does.- Gitea runner —
act_runnerwith the defaultcatthehacker/ubuntu:act-latestimage is enough; no Docker-in-Docker needed now that no job usescontainer:. The runner must reachgithub.com(for actions + Godot release downloads) anddl.google.com(for the Android SDK). - Optional secret
ANDROID_KEYSTORE_BASE64—base64 -w0 debug.keystore, stored as a Gitea repo secret. Without it, a throwaway keystore is generated per run, so the APK signature changes every build.
Linting
gdlint (from Scony's gdtoolkit) runs in the lint job over scripts/,
db/, and scenes/. addons/ (third-party LOD plugin) and developers/
(sandbox) are intentionally excluded.
The job is non-blocking today — the export jobs only depend on
validate, so a lint failure prints warnings but still produces binaries.
Once the codebase is clean, switch the export jobs' needs: validate to
needs: [validate, lint] to make lint a hard gate.
Suppress specific rules per-line with # gdlint: disable=<rule> or
project-wide with a gdlintrc file at the repo root (see
gdtoolkit docs).
Known issues from first runs
Captured from the first triggered runs on feature/godot-migration
(2026-05-17). Both must be resolved before the workflow can pass.
1. Container jobs failed with node: not found (resolved 2026-05-17)
The first runs used container: barichello/godot-ci:4.6 for the Godot
jobs. That image does not ship Node.js, so actions/checkout@v4 (a JS
action) crashed at startup with
OCI runtime exec failed: exec: "node": executable file not found.
Resolved by removing every container: block. The runner's default
catthehacker/ubuntu:act-latest image already has Node / Python / git /
JDK, and Godot is now installed at the start of each job via the local
composite action .gitea/actions/setup-godot/.
2. actions/checkout clones the wrong URL (resolved 2026-05-17)
The first runs failed at clone time because the runner asked for
https://dev.stilobique.com/darknight/puzzle-quest/info/refs while
Gitea was mounted under /gitea/ behind YunoHost — the request was
intercepted by the YunoHost SSO at the root and redirected before
reaching Gitea.
Resolved by relocating Gitea to the root: it now serves at
https://dev.stilobique.com/ directly (API at /api/v1/...,
clone_url at /<owner>/<repo>.git). The runner-injected
GITHUB_SERVER_URL and the actual Gitea base URL now agree.
If Gitea is ever moved back under a sub-path, the fix is ROOT_URL in
app.ini ([server] ROOT_URL = https://<host>/<prefix>/) or
re-registering act_runner with the full instance URL.
3. Default-branch mismatch
The Gitea API reports default_branch: main for the repo, but
CLAUDE.md describes dev as the default. The workflow listens to both,
so jobs trigger correctly either way, but the "Workflows" sidebar in the
Gitea UI reads from whatever the actual default branch is. If you intend
dev to be the default, update it under repo Settings → Branches.
Differences from the old .drone.yml
- No more Drone, no more Butler — build only, artifacts downloadable from the Gitea UI.
- GDScript validation step before export (didn't exist).
.godot/import cache shared between jobs (faster reruns).- Keystore via Gitea secret instead of a public pCloud link.
- macOS target added (preset still to be created in Godot).
master/ emptyReleaseVersionpipeline → replaced by triggers onmain(release branch perCLAUDE.md).
Future: itch.io deploy via Butler
Not wired. When you want it back, add a deploy-itch job gated on tag
push (v*) that downloads the artifacts and runs
butler push <dir> dev-crea/ahog:<channel> with BUTLER_API_KEY from
secrets. Channels used historically:
windows, linux, android, mac.