WordPress plugin rebrand/conventions/remote-CLI patterns, Gitea release workflow, bastille jail provisioning, remote shell quoting safety, server fleet map, delegate brief writing, and verification discipline -- all derived from real incidents this session, plus two skills adapted (MIT license, attributed) from obra/superpowers and andrej-karpathy-skills.
4.3 KiB
name, description
| name | description |
|---|---|
| gitea-release-workflow | Use when tagging a release for a plugin/package on a self-hosted Gitea instance, especially when the release needs to be downloadable by an external system (an auto-updater, a CI job, etc.). |
Gitea Release Workflow
Creating a release
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d '{"tag_name": "v1.0.0", "name": "v1.0.0", "body": "<changelog>", "draft": false, "prerelease": false}' \
"$HOST/api/v1/repos/$OWNER/$REPO/releases"
Verify it landed:
curl -s -H "Authorization: token $TOKEN" "$HOST/api/v1/repos/$OWNER/$REPO/releases/latest"
Note: on at least one real self-hosted instance, /releases/latest 404s
for unauthenticated requests even on a public repo — always send the
token, don't assume public-repo endpoints are auth-free.
KNOWN GOTCHA: release-asset downloads can be unreachable
Confirmed live on a real self-hosted Gitea instance: uploading a zip
as a release asset and then trying to fetch it via the asset's own
browser_download_url field returned an internal address
(http://localhost:3000/...) instead of the instance's real public
hostname — a ROOT_URL misconfiguration server-side. This makes the
uploaded asset completely unreachable from outside the Gitea server
itself, even though the release object and its metadata (version,
changelog) are fine.
Symptom: update-check-style API calls that only read release
metadata work fine; the actual download step 404s or times out.
Workaround that reliably works: don't rely on the release-asset upload/download mechanism at all for the actual file bytes. Instead use Gitea's repo-archive endpoint, which zips the tagged git tree on-the-fly and is served from the same working API path pattern as everything else:
curl -s -H "Authorization: token $TOKEN" -o output.zip \
"$HOST/api/v1/repos/$OWNER/$REPO/archive/$TAG.zip"
This means: the release object still gets created (for its version/
changelog metadata, which downstream consumers do need), but you do
not need to upload a zip as a release asset at all — the actual
distributable content is whatever's in the tagged git tree, fetched via
/archive/{tag}.zip at request time.
Before trusting the asset-download path on a NEW Gitea instance: test
it once (curl the browser_download_url directly) rather than
assuming it works — if it does work on a given instance, the asset-upload
approach is simpler and fine to use; the archive-endpoint workaround is
only needed on instances that have this specific misconfiguration. Don't
apply the workaround blindly without confirming the actual failure mode
exists on the instance you're using.
Archive-endpoint folder-naming gotcha
The /archive/{tag}.zip endpoint names the zip's top-level folder based
on the repo (exact naming varies by Gitea version — sometimes the repo
name, sometimes lowercased). If the repo's own files live in a
subdirectory rather than at repo root, the resulting zip gets a
double-nested top-level folder. Keep single-purpose repos (one
plugin/package per repo) flat at the root to avoid this — see the
wordpress-plugin-rebrand skill's flatten-to-root step.
Forking into a new org
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
-d '{"organization": "TargetOrg", "name": "new-repo-name"}' \
"$HOST/api/v1/repos/$SOURCE_OWNER/$SOURCE_REPO/forks"
The name field renames the fork on creation — you don't have to accept
the source repo's own name. Fork inherits the source repo's full git
history; if you're about to do a wholesale rebrand and don't need that
history preserved, a git init + single clean commit + git push --force
is often cleaner than carrying an unrelated-looking history forward — but
only force-push a repo that's genuinely fresh with nothing else depending
on its prior state.
Verify a release actually works end-to-end
Don't stop at "the release object exists" — actually fetch the archive and inspect it:
curl -s -H "Authorization: token $TOKEN" -o /tmp/verify.zip \
"$HOST/api/v1/repos/$OWNER/$REPO/archive/$TAG.zip"
unzip -l /tmp/verify.zip | head -20
Confirm: single non-double-nested top-level folder, expected files present, no truncation.