feat: multi-server deployment topology and CI fan-out #5
Reference in New Issue
Block a user
Delete Branch "feat/multi-server-fanout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bakgrund
Den ursprungliga frågan var om updater kunde hantera Docker Swarm och Docker Compose samtidigt istället för exklusivt via
MODE.Svaret blev nej — och det visade sig vara rätt svar. En instans kan bara nå den Docker-daemon den är konfigurerad mot. Compose-stackarna ligger på andra servrar, så en instans som klarade båda lägena skulle fortfarande bara nå en daemon. Begränsningen låg aldrig i applikationen utan i deployment-topologin: en instans för en hel flotta.
Lösningen är därför en instans per server, och fan-out från CI. Det gör
MODE-exklusiviteten korrekt snarare än en brist.Konsekvens: noll Go-ändringar i den här PR:en.
Innehåll
Dokumentation
README.md: ny sektion om deployment-topologi, omskrivenMODE-rad och known-gaps-punkt, samt rättad ingress som tidigare motsade den nya texten.CLAUDE.md: designintent-punkten uppdelad — rationalen bakom exklusiviteten och swarm-lägets säkerhetsgrind var två regler som delade en bullet.gitea-action/— fan-outendpointtar nu en newline-separerad lista istället för en enda URL. En ensam URL fungerar oförändrat som en lista med ett element.Newline och inte komma: URL:er får innehålla komma men aldrig radbrytning, så avgränsaren kan inte kollidera med datat.
Det som faktiskt var svårt
Loopen måste fortsätta förbi fel. Actionen hade
set -euo pipefail, vilket gör att första nedsläckta servern avbryter körningen — resten av flottan uppdateras aldrig, och loggen döljer vilka som hann med.set -eär borttaget med en kommentar som förklarar varför, så det inte "städas tillbaka".head -n -1var GNU-specifikt. Den befintliga koden använde det, vilket gjorde fan-out-logiken omöjlig att testa på en utvecklarmaskin (illegal line countpå BSD/macOS). Ersatt med bash parameter expansion, som dessutom tar bort två subprocesser per endpoint.tagvar dokumenterad som kosmetisk men är load-bearing i swarm-läge. Kedjanhandlers.go:59-62→swarm.goJob.Image→swarm_executor.goContainerSpec.Imagegör att en utelämnad tag deployar:latestistället för den byggda committen. Compose-läget är genuint tag-agnostiskt, swarm är det inte. Eftersom det nya exemplet sätter en swarm-endpoint först låg detta nu på den dokumenterade lyckliga vägen. Dokumentationen är rättad i bådeaction.ymlochgitea-action/README.md.Timeouts. En host som accepterar TCP men aldrig svarar blockerade loopen i all evighet — samma delvis-deployade utfall som ändringen finns för, nådd en annan väg.
--connect-timeout 10 --max-time 900; generöst eftersom/updateär synkron och väntar ut jobbet.Verifiering
go build ./...ochgo test ./...gröna med tömd testcache.Fan-out-logiken kördes mot en lokal HTTP-server i tre scenarier:
Att den tredje endpointen försöktes är beviset att loopen fortsatte förbi felet.
Känd begränsning
Actionen är shell inuti composite-YAML och det finns ingen testharness för den i repot. Verifieringen ovan kördes för hand och fångas inte av CI. Regression i fan-out-logiken upptäcks alltså inte automatiskt.
En orelaterad, sedan tidigare befintlig inkonsekvens rättades på vägen:
README.mdbeskrev compose-lägets skydd som två faktorer när koden gör tre (token, opt-in-label ochSTACKS_ROOT-prefix).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_01S3aqJ4tvaPezQhsGNCybutView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.