Documentation fixes from the final branch review, plus small curl/jq
hardening in the gitea-action script:
- tag was documented as cosmetic ("for logging") but is load-bearing in
swarm mode: handlers.go folds it into the requested image, compose
discovery strips it via NormaliseImage, but SwarmExecutor assigns it
directly to ContainerSpec.Image. Omitting it deploys :latest, silently
diverging from what CI just built. Fixed in action.yml, gitea-action's
README, and added to CLAUDE.md's Gotchas since it's invisible from
either mode's code alone.
- gitea-action/README.md's opening line and root README.md's intro/trigger
flow described compose-only behavior even though both docs' bodies now
cover swarm mode too.
- README.md's defense-in-depth section described a two-factor gate; compose
mode is actually three factors (token, label, STACKS_ROOT prefix), and
swarm mode is genuinely two (no local compose file to path-check against).
- action.yml: curl now has --connect-timeout 10 --max-time 900 so a host
that accepts TCP but never answers can't block the fan-out loop forever;
the jq payload build now fails loudly instead of silently sending an
empty payload to every endpoint.
- CLAUDE.md References section now lists this branch's spec and plan.
Claude-Session: https://claude.ai/code/session_01S3aqJ4tvaPezQhsGNCybut
Deploy via package-updater (composite action)
Notifies one or more package-updater instances to update the matching service(s) after a CI build. Each instance does whatever its own MODE dictates — docker compose pull + up -d, or docker service update for swarm.
Usage
In a consumer repo's .gitea/workflows/deploy.yml:
jobs:
deploy:
runs-on: ubuntu-latest
needs: [build-and-push]
steps:
- uses: gitea.example.com/shcizo/package-updater/gitea-action@v1
with:
endpoint: |
https://updater-swarm.example.com/update
https://updater-web01.example.com/update
https://updater-web02.example.com/update
image: registry.example.com/${{ gitea.repository }}
tag: ${{ gitea.sha }}
token: ${{ secrets.UPDATER_TOKEN }}
UPDATER_TOKEN should be set as an organisation-level secret in Gitea so all repos share it.
All instances in the fleet must share the same bearer token, since one token
input is sent to every endpoint.
Inputs
| Name | Required | Default | Description |
|---|---|---|---|
endpoint |
yes | — | Full URL to /update. Several may be given, one per line, to update a fleet. |
image |
yes | — | Image reference without tag |
tag |
no | "" |
Tag that was just pushed. Compose mode ignores it (the compose file pins the reference); swarm mode sets the service image to it, so omitting it deploys :latest. Always pass it. |
token |
yes | — | Bearer token configured in package-updater |
Failure modes
Every endpoint is attempted, even when an earlier one fails — otherwise one dead server would leave the rest of the fleet un-updated, and the CI log would not show which hosts actually succeeded.
The step exits non-zero if any endpoint returned 4xx/5xx or was unreachable. The log lists each endpoint with its HTTP status and response body, so a partial deploy is visible at a glance.
Endpoints are contacted sequentially, so worst-case wall time is the number of endpoints times
how long one update takes. Each request allows 10s to connect and 15 minutes to complete —
/update is synchronous and waits for the deploy to finish.