From bed742c3d065dba3c9dda22a58c8b2b6c41bc55b Mon Sep 17 00:00:00 2001 From: Bjoern Runden Date: Fri, 2 Oct 2026 22:42:56 +0200 Subject: [PATCH] =?UTF-8?q?pkg=20f=C3=BCr=20traefik=20ist=20drin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ai/sultan-system-architecture.md | 4 ++-- CLAUDE.md | 2 +- README.md | 11 +++++++---- deploy/.env.example | 10 ++++++++++ deploy/compose/agent/.env.example | 2 +- deploy/compose/server/.env.example | 2 +- deploy/push-image.sh | 28 ++++++++++++++++++++++------ 7 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 deploy/.env.example diff --git a/.ai/sultan-system-architecture.md b/.ai/sultan-system-architecture.md index bf12689..00c7bf2 100644 --- a/.ai/sultan-system-architecture.md +++ b/.ai/sultan-system-architecture.md @@ -787,7 +787,7 @@ services: | 2026-10-02 | **Leases und Befehle umgesetzt:** Lease mit eigenem Signaturpräfix; ungeplant getrennter Knoten zeigt die Wartungsseite bis Lease-Ende; Befehle über eine DB-Warteschlange (CLI ist eigener Prozess); alle Server-Events signiert; `rotate_key` mit Besitznachweis und abbruchsicherer Wiederaufnahme. | Kein 404 bei kurzen Ausfällen; keine Fernsteuerung ohne Server-Signatur; Schlüsselwechsel ohne Aussperren. | | 2026-10-02 | **Schritt 3 umgesetzt:** Checkmk-Ausgabe als reine Funktion (`pkg/checkmk`); Job bei jedem Refresh (Ablauf, Benachrichtigung, Aufräumen); Benachrichtigungen Slack-kompatibel + ntfy mit Wiederholung bis zur erfolgreichen Zustellung; Community läuft nicht über `valid_until` ab. | Testbar, ohne zusätzlichen Prozess; keine verlorenen Alarme. | | 2026-10-02 | **Mehrere Server-Adressen:** `--public-url` mehrfach; die Einladung trägt nur die erste; die Liste kommt per signiertem `config`-Befehl bei jedem Verbinden; der Agent weicht der Reihe nach aus und merkt sich die funktionierende Adresse. | Kurze Einladung; Ausfall einer Adresse (DNS, Weiterleitung) ohne Eingriff überbrückt. | -| 2026-10-02 | **Build & Auslieferung:** ein distroless-Image (nonroot) für Server und Agent, Multi-Arch amd64+arm64 per buildx mit Go-Cross-Compile (kein QEMU); Registry ist die Container-Registry von Gitea (`git.everdc.eu/chmln-app-dev/sultan`), ein Vanity-Name (`pods.chmln.de/`) entsteht später per Traefik-Proxy mit `replacePathRegex` auf eine feste Gitea-Organisation (kein Redirect: Docker verwirft den `Authorization`-Header bei Host-Wechsel). Binaries + `SHA256SUMS` statisch unter `sultan/releases//`, `releases/stable` als Zeiger. Agent-Container mit `network_mode: host`. Vorerst manuell über `deploy/*.sh`, später Gitea Actions bei Tags. | Kein Go-Stack beim Nutzer; ein Image für alle Architekturen; Agent meldet sonst die Container-IP als Backend; Registry ohne zusätzlichen Dienst. | +| 2026-10-02 | **Build & Auslieferung:** ein distroless-Image (nonroot) für Server und Agent, Multi-Arch amd64+arm64 per buildx mit Go-Cross-Compile (kein QEMU); Registry: eigener **Zot**-Server `pod.chmln.de`, Image `pod.chmln.de/sultan` (ohne Owner-Ebene, daher kein Rewrite-Proxy nötig); Zugangsdaten für den Push nur in `deploy/.env` (gitignored), ohne Passwort bricht `push-image.sh` ab. (Ursprünglich geplant: Gitea-Registry hinter Rewrite-Proxy – verworfen.) Binaries + `SHA256SUMS` statisch unter `sultan/releases//`, `releases/stable` als Zeiger. Agent-Container mit `network_mode: host`. Vorerst manuell über `deploy/*.sh`, später Gitea Actions bei Tags. | Kein Go-Stack beim Nutzer; ein Image für alle Architekturen; Agent meldet sonst die Container-IP als Backend; eigene Registry mit kurzen Image-Namen ohne Proxy-Tricks. | | 2026-10-02 | **Kleinere Punkte:** Rate-Limit `/enroll` 10/min/IP vor der Signaturprüfung (`-1` = aus); `SULTAN_` für alle Flags, Flags gewinnen; „wieder online“ nur nach Offline-Alarm; Checkmk-Zeiten in Server-Zeitzone. Ein Agent, dessen Enrollment mit 429 scheitert, beendet sich; der Neustart erfolgt über systemd bzw. Docker `restart`. | Schutz vor Durchprobieren; Container-/systemd-freundliche Konfiguration; vollständige Alarmkette. | | geplant | Geordneter Zertifikatswechsel; optionale Root-CA. (Signierte Server-Events: umgesetzt.) | Zweite Sicherheitsschicht neben dem TLS-Pin; Betrieb ohne Aussperren. | @@ -865,7 +865,7 @@ services: **Schritt 5 – Betrieb und Installationsskript** (teilweise umgesetzt: `Dockerfile`, `deploy/build.sh`, `deploy/push-image.sh`, `deploy/publish-static.sh`, `deploy/compose/`) * Strukturiertes Logging (`log/slog`, umgesetzt), Graceful Shutdown (umgesetzt), Versionsinfo * ✅ Dockerfile (statisch, distroless, Multi-Arch), Build-/Deploy-Skripte, Compose-Vorlagen -* offen: systemd-Unit, `install.sh`, Signatur von `SHA256SUMS` (Ed25519, Schlüssel nur im Runner), Gitea Actions (Tests bei Push, Release bei Tag `v*`), Image-Digest in der veröffentlichten Compose-Vorlage, Vanity-Proxy `pods.chmln.de`, optional Spiegel auf ghcr.io +* offen: systemd-Unit, `install.sh`, Signatur von `SHA256SUMS` (Ed25519, Schlüssel nur im Runner), Gitea Actions (Tests bei Push, Release bei Tag `v*`), Image-Digest in der veröffentlichten Compose-Vorlage, optional Spiegel auf ghcr.io * Installationsskript für `curl | sh`: Herkunft mit öffentlichem Zertifikat und/oder Prüfsumme bzw. Signatur. Die eigentliche Vertrauensfrage ist das Skript, nicht der Token. **Weitere offene Bausteine (ohne feste Reihenfolge):** diff --git a/CLAUDE.md b/CLAUDE.md index a6fd126..978422c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ Go-Modul `sultan` (Go 1.26). `main.go` ist nur eine Hülle um `pkg/cli`; umgeset **Mehrere Adressen (Schritt 4):** `Config.PublicURLs` (gemerkt als `settings.public_urls`, `server.LoadPublicURLs`), Versand per `config`-Befehl in `nodeOnline`; Agent: `Identity.ServerURLs`, `serverCandidates`/`nextServerURL` (Wechsel in `OnError` des Stream-Clients), `a.serverURL` nur über `currentServerURL()` lesen. -- [deploy/](deploy) – Auslieferung: `Dockerfile` im Root (distroless, nonroot 65532, Multi-Arch, kein `ENTRYPOINT`, `WORKDIR /`), `build.sh` (Binaries amd64/arm64 + `SHA256SUMS` nach `dist//`), `push-image.sh` (buildx → Gitea-Registry `git.everdc.eu/chmln-app-dev/sultan`), `publish-static.sh` (rsync nach `sultan/releases//`), `compose/{server,agent}` (Agent mit `network_mode: host`). Version überall aus `git describe`; `-dirty` wird nicht veröffentlicht. `.dockerignore` hält `suldata/`, `.ai/`, `CLAUDE.md` aus dem Build-Kontext. +- [deploy/](deploy) – Auslieferung: `Dockerfile` im Root (distroless, nonroot 65532, Multi-Arch, kein `ENTRYPOINT`, `WORKDIR /`), `build.sh` (Binaries amd64/arm64 + `SHA256SUMS` nach `dist//`), `push-image.sh` (liest `deploy/.env` mit `REGISTRY`/`REGISTRY_USER`/`REGISTRY_PASSWORD`, Vorlage `deploy/.env.example`; buildx → Zot-Registry `pod.chmln.de/sultan`; ohne Passwort Exit 1), `publish-static.sh` (rsync nach `sultan/releases//`), `compose/{server,agent}` (Agent mit `network_mode: host`). Version überall aus `git describe`; `-dirty` wird nicht veröffentlicht. `.dockerignore` hält `suldata/`, `.ai/`, `CLAUDE.md` aus dem Build-Kontext. **CLI-Umgebung:** `pkg/cli/env.go` – `annotateEnv`/`applyEnv` machen jedes Flag als `SULTAN_` setzbar (nach `fs.Parse`, nur für nicht gesetzte Flags; Listen-Flags `stringList` kommagetrennt). Neue Flags bekommen das automatisch. Rate-Limit: `pkg/server/ratelimit.go` (`EnrollLimit`, `-1` = aus). diff --git a/README.md b/README.md index d76ee54..13fc6ef 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,18 @@ docker run --rm sultan:latest sultan version Das Image ist distroless (nur das statische Binary, ~17 MB) und läuft als nonroot-User `65532`. Die Rolle wählt der Befehl: `sultan server …` bzw. `sultan agent …`. -### 4. Image in die Gitea-Registry schieben +### 4. Image in die Registry schieben (Zot, `pod.chmln.de`) ```bash -docker login git.everdc.eu # einmalig: Gitea-User + Token mit Scope write:package -deploy/push-image.sh # → git.everdc.eu/chmln-app-dev/sultan: +cp deploy/.env.example deploy/.env # einmalig; gitignored – nie einchecken! +$EDITOR deploy/.env # REGISTRY_PASSWORD eintragen (Pflicht) +deploy/push-image.sh # → pod.chmln.de/sultan: LATEST=1 deploy/push-image.sh # zusätzlich :latest (nur für Releases) ``` -Baut per `docker buildx` gleichzeitig für `linux/amd64` und `linux/arm64` (Go kompiliert quer, kein QEMU nötig) und verweigert Versionen mit `-dirty`. Anpassbar über `REGISTRY`, `OWNER`, `PLATFORMS`. +Das Skript liest `deploy/.env` (Defaults: `REGISTRY=pod.chmln.de`, `REGISTRY_USER=sultan`, `IMAGE_NAME=sultan`; ohne `REGISTRY_PASSWORD` Abbruch), meldet sich per `docker login --password-stdin` an und baut per `docker buildx` gleichzeitig für `linux/amd64` und `linux/arm64` (Go kompiliert quer, kein QEMU nötig). Versionen mit `-dirty` werden verweigert. Weitere Stellschrauben: `PLATFORMS`, `ENV_FILE`. + +Danach auf den Hosts: `SULTAN_IMAGE=pod.chmln.de/sultan:` in der Compose-`.env` und `docker compose pull && docker compose up -d`. ### 5. Binaries auf den statischen Webhost legen diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..98cf050 --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,10 @@ +# Zugangsdaten für deploy/push-image.sh – als deploy/.env kopieren (gitignored, nie einchecken!) + +# Container-Registry (Zot) +REGISTRY=pod.chmln.de +REGISTRY_USER=sultan +# Pflicht – ohne Passwort bricht push-image.sh ab +REGISTRY_PASSWORD= + +# Image-Name in der Registry → ${REGISTRY}/${IMAGE_NAME}: +IMAGE_NAME=sultan diff --git a/deploy/compose/agent/.env.example b/deploy/compose/agent/.env.example index 0130925..8bd5107 100644 --- a/deploy/compose/agent/.env.example +++ b/deploy/compose/agent/.env.example @@ -1,6 +1,6 @@ # Image: lokal gebaut (docker build -t sultan:latest .) oder aus der Registry SULTAN_IMAGE=sultan:latest -# SULTAN_IMAGE=git.everdc.eu/chmln-app-dev/sultan:v0.1.0 +# SULTAN_IMAGE=pod.chmln.de/sultan:v0.1.0 # Einladung vom Server: docker compose exec sultan-server sultan token invite --tenant=… SULTAN_TOKEN= diff --git a/deploy/compose/server/.env.example b/deploy/compose/server/.env.example index 88e87aa..b82dde2 100644 --- a/deploy/compose/server/.env.example +++ b/deploy/compose/server/.env.example @@ -1,6 +1,6 @@ # Image: lokal gebaut (docker build -t sultan:latest .) oder aus der Registry SULTAN_IMAGE=sultan:latest -# SULTAN_IMAGE=git.everdc.eu/chmln-app-dev/sultan:v0.1.0 +# SULTAN_IMAGE=pod.chmln.de/sultan:v0.1.0 # Adresse, unter der Agents den Server erreichen – steckt danach in jeder Einladung SULTAN_PUBLIC_URL=https://webproxy11.example.de:8443 diff --git a/deploy/push-image.sh b/deploy/push-image.sh index 29893a1..c336f06 100755 --- a/deploy/push-image.sh +++ b/deploy/push-image.sh @@ -1,21 +1,35 @@ #!/usr/bin/env bash # Baut das Multi-Arch-Image (amd64 + arm64) und schiebt es in die -# Container-Registry von Gitea. +# Container-Registry (Zot). # -# docker login git.everdc.eu # einmalig, Gitea-User + Token mit write:package +# cp deploy/.env.example deploy/.env && $EDITOR deploy/.env # einmalig: REGISTRY_PASSWORD # deploy/push-image.sh # Tag aus `git describe` # LATEST=1 deploy/push-image.sh # zusätzlich :latest setzen (nur für Releases) # -# Ergebnis: ${REGISTRY}/${OWNER}/sultan: (und ggf. :latest) +# Ergebnis: ${REGISTRY}/${IMAGE_NAME}: (und ggf. :latest) set -euo pipefail cd "$(dirname "$0")/.." -REGISTRY="${REGISTRY:-git.everdc.eu}" -OWNER="${OWNER:-chmln-app-dev}" -IMAGE="${REGISTRY}/${OWNER}/sultan" +ENV_FILE="${ENV_FILE:-deploy/.env}" +if [ -f "$ENV_FILE" ]; then + set -a + # shellcheck disable=SC1090 + . "$ENV_FILE" + set +a +fi + +REGISTRY="${REGISTRY:-pod.chmln.de}" +REGISTRY_USER="${REGISTRY_USER:-sultan}" +IMAGE_NAME="${IMAGE_NAME:-sultan}" +IMAGE="${REGISTRY}/${IMAGE_NAME}" VERSION="${VERSION:-$(git describe --tags --always --dirty)}" PLATFORMS="${PLATFORMS:-linux/amd64,linux/arm64}" +if [ -z "${REGISTRY_PASSWORD:-}" ]; then + echo "REGISTRY_PASSWORD fehlt – in ${ENV_FILE} setzen (Vorlage: deploy/.env.example)." >&2 + exit 1 +fi + case "$VERSION" in *-dirty) echo "Arbeitsverzeichnis hat uncommittete Änderungen (${VERSION}) – erst committen." >&2 @@ -23,6 +37,8 @@ case "$VERSION" in ;; esac +printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin + tags=(-t "${IMAGE}:${VERSION}") if [ "${LATEST:-0}" = 1 ]; then tags+=(-t "${IMAGE}:latest")