Files
package-updater/gitea-action
shcizo 2d668ddc63 docs: fix multi-server fanout review findings
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
2026-08-04 19:48:16 +02:00
..

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.