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.
- 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. - Private URLs nur in der
.env. Gitea- + GitHub-privat-URL stehen ausschließlich in der gitignorierten.envvon dev/staging (CLUSEV_REPOSITORY=…), nie in einem versionierten Baum. → Das Public-Repo enthält null private URLs. - Automatische Ableitung aus dem Klon-Ursprung.
install.sh/update.shermitteln die Quelle pergit -C <proj> remote get-url originund schreibenCLUSEV_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. - Graceful Degradation. Ist
CLUSEV_REPOSITORYleer und kein Default gesetzt (Phase bevor das Public-Repo existiert), behandelnReleaseChecker+ Versions-Seite das sauber (kein Update-Check, kein Crash, kein Sidebar-Badge). - Dev-Maschine jetzt:
CLUSEV_REPOSITORY=https://git.bave.dev/boban/clusevin die Dev-.env(oder voninstall.shableiten 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 install→php artisan test(aktuell 374) → shellcheck (koalaman/shellcheck:stableaufdocker/wg/*.sh,install.sh,update.sh) →./vendor/bin/pint --test→npm 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. Perif-Bedingung nur aktiv, wenn das Secret existiert.
Public-Promotion (Gate, saubere Historie)
- Workflow
promote-public.yml:workflow_dispatchmit Eingabetag(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" → TagvX.Y.Z→ Push (Push-Token als Secret). - Ergebnis: Public-
main= ein Commit pro Release; Tags vorhanden für denReleaseChecker. 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" lesenCLUSEV_REPOSITORY→ korrekte Quelle pro Umgebung.
Zu bauende Artefakte (im Implementierungsplan)
config/clusev.php: Gitea-Hardcode raus →env('CLUSEV_REPOSITORY', '<public-url-platzhalter>')(Default = Public-URL).install.sh/update.sh:CLUSEV_REPOSITORYaus dem Klon-Ursprung ableiten + in.envschreiben..github/workflows/ci-staging.yml(Tests bei Tag; optional Staging-Deploy)..github/workflows/promote-public.yml(Gated Clean-Push, Environment „production").scripts/promote.sh(Baum-Kopie + Commit + Tag + Push) — vom Workflow aufgerufen, lokal shell-testbar.- README: 3-Repo-Flow dokumentieren + wie man die URLs beim Erstellen der Repos einträgt + Public-Clone-Befehl.
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/SecretPUBLIC_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.phprepository: liestCLUSEV_REPOSITORY, degradiert aufnullwenn ungesetzt.ReleaseChecker: „keine Repository-URL →updateAvailable()false, kein Netz, kein Crash" (Test ergänzen).- Workflows: lokal mit
actionlintlinten; echte Verifikation erst, sobald die Repos existieren.