Files
hetzner-api-dyndns-records-…/AGENTS.md
T

9.6 KiB

Arbeitsanweisung für Agenten

Diese Datei gilt für das gesamte Repository. Lies sie vor Änderungen am Projekt vollständig.

Release-Grundsätze

  • Veröffentliche nur, wenn der Benutzer das Release ausdrücklich freigegeben hat.
  • Verwende Semantic Versioning mit führendem v, beispielsweise v1.2.2.
  • Bestehende Tags und veröffentlichte Versions-Images werden niemals verschoben, überschrieben oder erneut verwendet.
  • compose.yml bleibt auf eine konkrete Version gepinnt. Verwende dort nicht stable.
  • Veröffentliche genau zwei Registry-Tags: die konkrete Version und stable.
  • Ein Release muss weiterhin linux/amd64 und linux/arm64 enthalten.
  • Verändere oder lösche beim Release keine Datenbank-, Daten- oder Secret-Volumes.
  • Gib niemals Git-, Registry-, Hetzner- oder Pi-hole-Zugangsdaten in Befehlsausgaben, URLs, Commits oder Release Notes aus.
  • Verwende kein Force-Push und keine destruktiven Git-Befehle.

Ziele

  • Quell-Repository: https://git.j-mahler.de/admin/hetzner-api-dyndns-records-webinterface.git
  • Container-Image: git.j-mahler.de/admin/domain-control-webinterface-prod
  • Release-Workflow: .gitea/workflows/release.yml
  • Deployment: compose.yml, standardmäßig Port 9090
  • Healthcheck: http://127.0.0.1:9090/healthz

1. Release-Kandidaten prüfen

Arbeite nur auf main. Prüfe zuerst den vollständigen Zustand und erhalte alle vorhandenen Benutzeränderungen:

git status --short --branch
git diff --stat
git diff --check
git remote -v
git tag --sort=-v:refname | head

Kontrolliere den tatsächlichen Diff. Stage oder committe keine fremden oder sachfremden Änderungen. Falls Frontend-Dateien geändert wurden, erhöhe in internal/web/templates/layout.html die Cache-Version beider Assets gemeinsam, beispielsweise von features-14 auf features-15.

2. Version anheben

Lege eine noch nicht vorhandene Version fest. Für reine Fehler- und UI-Korrekturen wird normalerweise die Patch-Version erhöht.

Ersetze die bisherige Version mindestens an diesen Stellen:

  • compose.yml
  • Image-Beispiel in README.md

Danach dürfen diese Dateien die vorherige Version nicht mehr enthalten. Setze PREVIOUS auf die tatsächlich vorherige Version:

PREVIOUS=v1.2.1
if grep -n "$PREVIOUS" README.md compose.yml; then
  echo "Alte Version $PREVIOUS ist noch eingetragen." >&2
  exit 1
fi
docker compose -f compose.yml config --quiet
git diff --check

3. Pflichtprüfungen vor dem Tag

JavaScript und Compose:

node --check internal/web/static/app.js
docker compose -f compose.yml config --quiet
git diff --check

Go-Quelltext formatieren, falls Go-Dateien geändert wurden. Formatiere nur bewusst geänderte Dateien, bei Bedarf ebenfalls im Container:

docker run --rm \
  -v "$PWD":/src \
  -w /src golang:1.25.12-alpine \
  gofmt -w PFAD/ZUR/GEAENDERTEN_DATEI.go

Das Projekt wird in einem Go-Container geprüft, weil auf dem Host nicht zwingend Go installiert ist:

docker run --rm \
  -v "$PWD":/src \
  -v /tmp/dyndns-go-mod:/go/pkg/mod \
  -v /tmp/dyndns-go-build:/root/.cache/go-build \
  -w /src golang:1.25.12-alpine \
  sh -c 'apk add --no-cache build-base >/dev/null && go vet ./... && go test -race -count=1 ./...'

Bei Abhängigkeits-, Kryptografie-, Authentifizierungs- oder sonstigen Sicherheitsänderungen zusätzlich:

docker run --rm \
  -v "$PWD":/src \
  -v /tmp/dyndns-go-mod:/go/pkg/mod \
  -v /tmp/dyndns-go-build:/root/.cache/go-build \
  -w /src golang:1.25.12-alpine \
  sh -c 'apk add --no-cache build-base >/dev/null && go run golang.org/x/vuln/cmd/govulncheck@latest ./...'

Bei einem Fehler wird nicht getaggt oder veröffentlicht. Behebe den Fehler und beginne die finalen Prüfungen erneut.

4. Commit, Tag und Git-Push

Stage nur die geprüften Dateien und kontrolliere den Index:

git add DATEI1 DATEI2
git diff --cached --check
git diff --cached --stat
git diff --cached

Erstelle einen sachlichen Commit und einen annotierten Tag:

git commit -m "fix: kurze Beschreibung"
git tag -a vX.Y.Z -m "Release vX.Y.Z"
git show --no-patch --format=fuller vX.Y.Z

Pushe Branch und Tag atomar, damit nicht nur einer von beiden veröffentlicht wird:

git push --atomic origin main refs/tags/vX.Y.Z

Prüfe anschließend, dass Remote-Branch und aufgelöster Tag auf denselben Release-Commit zeigen:

git ls-remote origin refs/heads/main refs/tags/vX.Y.Z 'refs/tags/vX.Y.Z^{}'

5. Container-Image veröffentlichen

Ein v*-Tag startet den Gitea-Workflow. Prüfe zuerst in Gitea, ob der Run tatsächlich läuft und erfolgreich endet. Wenn der Workflow erfolgreich ist, darf nicht zusätzlich manuell in dieselben Tags gepusht werden.

Bekannter Gitea-Runner-Zustand

Stand 13.07.2026 ist kein Gitea-Actions-Runner aktiv. Die Runs bleiben auf queued. Der übliche Cancel-Endpunkt ist in dieser Gitea-Version nicht vorhanden; das Löschen wartender Runs antwortete mit HTTP 400. Verändere deshalb nicht direkt die Gitea-Datenbank. Prüfe den Zustand bei jedem Release neu.

Bevor später ein Runner aktiviert wird, müssen die alten wartenden Release-Runs in Gitea bereinigt werden. Andernfalls könnten sie veraltete Images erneut als stable veröffentlichen.

Manueller Multi-Arch-Fallback

Melde Docker vorab interaktiv an, falls noch keine Registry-Anmeldung besteht:

docker login git.j-mahler.de

Gib den Token nur über die verdeckte Passwortabfrage ein. Der Token benötigt mindestens Paket-Schreibzugriff.

Prüfe den vorhandenen Builder:

docker buildx inspect domain-control-release-builder --bootstrap

Falls er nicht existiert:

docker buildx create --name domain-control-release-builder --driver docker-container --use
docker buildx inspect domain-control-release-builder --bootstrap

Falls linux/arm64 nicht unter Platforms erscheint, registriere einmalig die ARM64-Emulation:

docker run --privileged --rm tonistiigi/binfmt:qemu-v10.0.4-56 --install arm64
docker buildx inspect domain-control-release-builder --bootstrap

Setze Version und Commit bewusst und prüfe beide Werte vor dem Build:

VERSION=vX.Y.Z
REVISION="$(git rev-parse HEAD)"
printf 'Version: %s\nCommit:  %s\n' "$VERSION" "$REVISION"

Baue und pushe anschließend beide Plattformen in einem gemeinsamen Manifest:

docker buildx build \
  --builder domain-control-release-builder \
  --platform linux/amd64,linux/arm64 \
  --tag "git.j-mahler.de/admin/domain-control-webinterface-prod:$VERSION" \
  --tag "git.j-mahler.de/admin/domain-control-webinterface-prod:stable" \
  --label org.opencontainers.image.source=https://git.j-mahler.de/admin/hetzner-api-dyndns-records-webinterface \
  --label "org.opencontainers.image.revision=$REVISION" \
  --label "org.opencontainers.image.version=$VERSION" \
  --provenance=false \
  --push .

Der emulierte ARM64-Build mit CGO und SQLite kann etwa acht Minuten lang ohne neue Ausgabe rechnen. Nicht allein deshalb abbrechen. Prüfe bei Zweifeln, ob der BuildKit-Container CPU verwendet.

6. Registry verifizieren

Prüfe sowohl die konkrete Version als auch stable:

docker buildx imagetools inspect "git.j-mahler.de/admin/domain-control-webinterface-prod:$VERSION"
docker buildx imagetools inspect git.j-mahler.de/admin/domain-control-webinterface-prod:stable

Beide Ausgaben müssen denselben Manifest-Digest und genau diese Plattformen zeigen:

  • linux/amd64
  • linux/arm64

Ein erfolgreicher Build ohne diese Manifestprüfung gilt nicht als abgeschlossenes Release.

7. Gitea-Releaseeintrag

Lege für den neuen Tag einen veröffentlichten, nicht als Entwurf und nicht als Pre-Release markierten Gitea-Releaseeintrag an.

Die Release Notes enthalten mindestens:

  • kurze, benutzerverständliche Änderungen seit der vorherigen Version
  • vollständigen Image-Namen mit Version
  • linux/amd64 und linux/arm64

Nutze vorhandene Anmeldedaten sicher über Gitea UI oder API. Schreibe Tokens niemals in die URL oder die Release Notes. Lies den erzeugten Eintrag anschließend zurück und kontrolliere Tag, Titel und Beschreibung.

8. Laufenden Container auf das Release umstellen

Erst nach erfolgreicher Registry-Prüfung:

docker compose pull
docker compose up -d --force-recreate

Das Compose verwendet dieselben benannten Daten- und Secret-Volumes. Erstelle sie nicht neu und lösche sie nicht.

Prüfe abschließend:

docker compose ps
docker inspect --format 'image={{.Config.Image}} digest={{.Image}} health={{if .State.Health}}{{.State.Health.Status}}{{end}} readonly={{.HostConfig.ReadonlyRootfs}} user={{.Config.User}}' hetzner-api-dyndns-records-webinterface-dyndns-1
docker image inspect --format '{{json .RepoDigests}}' "git.j-mahler.de/admin/domain-control-webinterface-prod:$VERSION"
curl --silent --show-error --fail http://127.0.0.1:9090/healthz
docker logs --tail 80 hetzner-api-dyndns-records-webinterface-dyndns-1
git status --short --branch

Erwartet werden:

  • Containerstatus healthy
  • Image-Name mit der konkreten Version, nicht mit einem lokalen Testtag
  • schreibgeschütztes Root-Dateisystem
  • Benutzer dyndns:dyndns
  • Registry-Digest identisch mit dem zuvor geprüften Versionsmanifest
  • HTTP-Antwort ok
  • sauberer und mit origin/main synchroner Git-Arbeitsbaum

Abschlussmeldung

Melde dem Benutzer kompakt:

  • Version und Commit
  • Link zum Gitea-Release
  • veröffentlichte Tags
  • Manifest-Digest und Plattformen
  • bestandene Prüfungen
  • Healthstatus des laufenden Containers
  • einen weiterhin bestehenden Runner- oder Infrastrukturhinweis

Ein Git-Tag allein, ein nur lokal gebautes Image oder ein lediglich gestarteter Workflow ist kein abgeschlossenes Release.