clusev/docs/superpowers/specs/2026-06-22-3-repo-release-p...

6.9 KiB

3-Repo Release-Promotion — Design Spec

Datum: 2026-06-22 Status: entworfen (Repos existieren noch nicht — URL-agnostisch gebaut)

Ziel

Clusev über drei Repositories befördern — Gitea (dev) → GitHub privat (staging) → GitHub public (release) — mit CI/CD auf GitHub Actions, einem manuellen Gate vor Public und sauberer Public-Historie (ein Commit pro Release). Keine Repository-URL steht in versionierten Dateien, damit die noch nicht erstellten GitHub-Repos später eingehängt werden und das Public-Repo niemals die privaten/Gitea-URLs preisgibt.

Nicht-Ziele

  • Die GitHub-Repos jetzt anlegen (existieren noch nicht). Code + Workflows sind URL-agnostisch; die URLs werden beim Erstellen der Repos verdrahtet.
  • Keine selbstgebaute Webhook-Infrastruktur (Giteas eingebauter Push-Mirror + GitHub Actions decken alles ab).

Repos & Rollen

Repo Sichtbarkeit Rolle
Gitea git.bave.dev privat Einzige Dev-Quelle. Du pushst + taggst hier. Nie direkt in die GitHub-Spiegel committen.
GitHub privat privat Exakter Push-Mirror von Gitea (Gitea-Built-in). Trägt CI + Staging-Deploy.
GitHub public öffentlich Erhält nur freigegebene Versionen — ein sauberer Commit pro Release + Tag.

Repository-URL-Handhabung (Sicherheits-Kern)

Klarstellung des Anforderers: dev und staging dürfen alle URLs enthalten (in ihrer gitignorierten .env); nur das Public-Repo darf in versionierten Dateien ausschließlich die Public-URL tragen — damit niemand über das Public-Repo die Dev-/Staging-Repos findet oder dorthin zu pushen versucht.

  1. Getrackter Default = die PUBLIC-URL (sicher, weil ohnehin öffentlich), kein privater Fallback. config/clusev.php: 'repository' => env('CLUSEV_REPOSITORY', '<public-repo-url>'). Aktuell ist die Gitea-URL hart eingetragen ('repository' => 'https://git.bave.dev/boban/clusev') → ersetzen durch die Public-URL (Platzhalter, bis das Repo existiert). Das ist die einzige Repo-URL in versionierten Dateien — und sie ist die öffentliche.
  2. Private URLs nur in der .env. Gitea- + GitHub-privat-URL stehen ausschließlich in der gitignorierten .env von dev/staging (CLUSEV_REPOSITORY=…), nie in einem versionierten Baum. → Das Public-Repo enthält null private URLs.
  3. Automatische Ableitung aus dem Klon-Ursprung. install.sh/update.sh ermitteln die Quelle per git -C <proj> remote get-url origin und schreiben CLUSEV_REPOSITORY=<das> in die .env. → dev (von Gitea geklont) bekommt automatisch die Gitea-URL in seine .env, staging die GitHub-privat-URL, Public-Nutzer die Public-URL. Kein manuelles Eintragen, und die private URL landet nur in der gitignorierten .env.
  4. Graceful Degradation. Ist CLUSEV_REPOSITORY leer und kein Default gesetzt (Phase bevor das Public-Repo existiert), behandeln ReleaseChecker + Versions-Seite das sauber (kein Update-Check, kein Crash, kein Sidebar-Badge).
  5. Dev-Maschine jetzt: CLUSEV_REPOSITORY=https://git.bave.dev/boban/clusev in die Dev-.env (oder von install.sh ableiten lassen), damit der Update-Check schon heute auf Gitea zeigt.

Ergebnis: Versionierte Dateien tragen höchstens die Public-URL. Beim Anlegen der Repos trägst du die URLs nur in (a) Giteas Mirror-Einstellung, (b) GitHub-Actions-Secrets/Variablen, (c) die Dev-/Staging-.env, (d) den Public-Default in config/clusev.php ein.

CI-Pipeline (GitHub Actions, im GitHub-privat-Repo)

  • Trigger: Push eines Tags v*.*.* (gemäß Entscheidung „nur Versions-Tags").
  • Jobs (Container, wie lokal): composer installphp artisan test (aktuell 374) → shellcheck (koalaman/shellcheck:stable auf docker/wg/*.sh, install.sh, update.sh) → ./vendor/bin/pint --testnpm ci + npm run build.
  • Erfolg → Staging-Deploy (siehe unten). Fehler → kein Deploy; GitHub-Actions-Status meldet rot.

Staging-Deploy

Zwei Stufen, secret-frei startbar:

  • Default (kein Extra-Secret): CI deployt nicht selbst — der Tag erreicht GitHub-privat, du klickst auf dem Staging-Server (.165) in der Versions-Seite „Jetzt aktualisieren" (vorhandener Self-Pull). Passt zu deinem heutigen Update-Weg.
  • Optional (wenn du einen SSH-Deploy-Key als Actions-Secret hinterlegst): ein Deploy-Job per SSH auf Staging → sudo ./update.sh. Per if-Bedingung nur aktiv, wenn das Secret existiert.

Public-Promotion (Gate, saubere Historie)

  • Workflow promote-public.yml: workflow_dispatch mit Eingabe tag (z. B. v0.9.56), gebunden an ein GitHub-Environment „production" mit Pflicht-Reviewer (du) → läuft erst nach deiner Freigabe.
  • Schritte: GitHub-privat am Tag auschecken → Baum ohne .git (und ohne .github/workflows/ + staging-only Dateien) in einen Checkout von GitHub-public kopieren → Commit „Release vX.Y.Z" → Tag vX.Y.Z → Push (Push-Token als Secret).
  • Ergebnis: Public-main = ein Commit pro Release; Tags vorhanden für den ReleaseChecker. Keine Dev-Churn, keine private Historie.
  • Hygiene: Der Baum enthält keine .env* (gitignored, nie im Baum); das Token ist ein Actions-Secret, nie im Log.

Konfig pro Umgebung (Rekapitulation)

  • dev → CLUSEV_REPOSITORY = Gitea-URL · staging → GitHub-privat · public → GitHub-public — jeweils aus dem Klon-Ursprung abgeleitet.
  • ReleaseChecker, Sidebar-Badge und „Was ist neu" lesen CLUSEV_REPOSITORY → korrekte Quelle pro Umgebung.

Zu bauende Artefakte (im Implementierungsplan)

  1. config/clusev.php: Gitea-Hardcode raus → env('CLUSEV_REPOSITORY', '<public-url-platzhalter>') (Default = Public-URL).
  2. install.sh/update.sh: CLUSEV_REPOSITORY aus dem Klon-Ursprung ableiten + in .env schreiben.
  3. .github/workflows/ci-staging.yml (Tests bei Tag; optional Staging-Deploy).
  4. .github/workflows/promote-public.yml (Gated Clean-Push, Environment „production").
  5. scripts/promote.sh (Baum-Kopie + Commit + Tag + Push) — vom Workflow aufgerufen, lokal shell-testbar.
  6. README: 3-Repo-Flow dokumentieren + wie man die URLs beim Erstellen der Repos einträgt + Public-Clone-Befehl.
  7. ReleaseChecker/Versions: „keine Repository-URL → kein Update, kein Crash" verifizieren (Test ergänzen).

Platzhalter für später (wenn die GitHub-Repos existieren)

Nur Einstellungen, kein Code:

  • Giteas Push-Mirror-Ziel = GitHub-privat-URL + GitHub-PAT.
  • GitHub-Actions: Secrets STAGING_SSH_KEY (optional), PUBLIC_REPO_TOKEN; Variable/Secret PUBLIC_REPO_URL.
  • Public-README: Clone-Befehl mit der Public-URL.

Tests

  • install.sh-URL-Ableitung: shell-testbar (git remote get-url origin.env-Zeile).
  • config/clusev.php repository: liest CLUSEV_REPOSITORY, degradiert auf null wenn ungesetzt.
  • ReleaseChecker: „keine Repository-URL → updateAvailable() false, kein Netz, kein Crash" (Test ergänzen).
  • Workflows: lokal mit actionlint linten; echte Verifikation erst, sobald die Repos existieren.