# 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: ```bash 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` - Versionshinweis in `UPDATE.md` - `ARG VERSION` in `Dockerfile` - Fallback-Version in `internal/buildinfo/version.go` `MINIMUM_UPDATE_VERSION`, `MinimumUpdateVersion`, `MinimumAgentVersion` und die Vertragsnummern sind Kompatibilitätsgrenzen. Ändere sie nicht bei normalen Releases, sondern nur bei einem bewusst geplanten Vertragsbruch mit passender Migration und Dokumentation. Danach dürfen die gepinnten Image-Referenzen nicht mehr auf die vorherige Version zeigen. Historische Hinweise zur v1.4-Update-Basis bleiben bestehen. Setze `PREVIOUS` auf die tatsächlich vorherige Version: ```bash PREVIOUS=v1.2.1 if grep -n "domain-control-webinterface-prod:$PREVIOUS" README.md compose.yml; then echo "Alte Image-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: ```bash 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: ```bash docker run --rm \ -v "$PWD":/src \ -w /src golang:1.25.13-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: ```bash 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.13-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: ```bash 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.13-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: ```bash git add DATEI1 DATEI2 git diff --cached --check git diff --cached --stat git diff --cached ``` Erstelle einen sachlichen Commit und einen annotierten Tag: ```bash 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: ```bash 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: ```bash git ls-remote origin refs/heads/main refs/tags/vX.Y.Z 'refs/tags/vX.Y.Z^{}' ``` ## 5. Container-Image veröffentlichen Wenn Repository-Actions aktiviert sind, startet ein `v*`-Tag den Gitea-Workflow. Prüfe dann 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. Sind Repository-Actions deaktiviert, verwende ausschließlich den manuellen Multi-Arch-Fallback. ### Bekannter Gitea-Runner-Zustand Stand 29.08.2026 ist kein Gitea-Actions-Runner aktiv. Repository-Actions sind deshalb deaktiviert. Run 7 für `v1.3.0` liegt weiterhin intern auf `queued`, kann im deaktivierten Zustand aber nicht ausgeführt werden; sein Commit-Status wurde nach der bereits abgeschlossenen manuellen Veröffentlichung auf `success` korrigiert. Gitea 1.26.4 bietet keinen API-Endpunkt zum Abbrechen einzelner Runs. Verändere deshalb nicht direkt die Gitea-Datenbank. Bevor Actions oder ein Runner später aktiviert werden, muss Run 7 in der Gitea-Weboberfläche abgebrochen werden. Andernfalls könnte er das veraltete Image erneut als `stable` veröffentlichen. Aktiviere Actions erst danach und nur zusammen mit einem funktionsfähigen Runner. ### Manueller Multi-Arch-Fallback Melde Docker vorab interaktiv an, falls noch keine Registry-Anmeldung besteht: ```bash 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: ```bash docker buildx inspect domain-control-release-builder --bootstrap ``` Falls er nicht existiert: ```bash 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: ```bash 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: ```bash 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: ```bash docker buildx build \ --builder domain-control-release-builder \ --platform linux/amd64,linux/arm64 \ --build-arg "VERSION=$VERSION" \ --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`: ```bash 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: ```bash 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: ```bash 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.