Files
BartelLuis 3c2faa40b4
CI / container-policy (push) Successful in 3s
CI / javascript-check (push) Successful in 13s
CI / python-tests (push) Successful in 1m18s
CI / container-verify (push) Skipped
CI / container-publish (push) Successful in 16s
chore(release): prepare v0.9.1
2026-09-14 20:48:05 +02:00

243 lines
14 KiB
Markdown

# Gitea Actions und Container Registry
Das Projekt liegt auf
[Gitea](https://gitlab.bartelluis.de/BartelLuis/Proxmox-AIS-Server).
Der [Workflow](../.gitea/workflows/ci.yml) prüft Python und JavaScript, baut
das Image und führt den Container-Smoke-Test aus. Bei aktivierter
Veröffentlichung lädt er dasselbe getestete Image in die Gitea Container
Registry. Der Workflow endet beim Image-Push; die Schritte auf dem Zielserver
stehen in der [Deployment-Anleitung](deployment.md).
## Gitea und Runner einrichten
In den Repository-Einstellungen **Actions** aktivieren und unter
**Settings → Actions → Runners** einen registrierten Runner prüfen oder
einrichten. Gitea benötigt einen eigenen Runner-Dienst; ein GitHub-Runner-Label
stellt keine Rechenkapazität bereit.
[Gitea Actions einrichten](https://docs.gitea.com/usage/actions/quickstart/).
Alle Jobs verwenden `vars.AIS_RUNNER_LABEL` oder, wenn die Variable nicht gesetzt
ist, `ubuntu-latest`. Das ausgewählte Label muss einem für dieses Repository
verfügbaren, erreichbaren Linux-amd64-Runner entsprechen. Die Angabe
`ubuntu-latest` wählt ein Runner-Label; sie installiert kein Ubuntu-Image.
Für ein anderes Label in **Settings → Actions → Variables** beispielsweise
`AIS_RUNNER_LABEL=ais-ci` setzen und genau dieses Label am Runner konfigurieren.
Die tatsächliche Job-Umgebung benötigt Git, Bash, OpenSSL, `ssh-keygen`, Python
3.13 mit `venv`, Node.js 24 sowie Docker CLI und Buildx mit Zugriff auf einen
laufenden Docker-Daemon. Die Setup-Actions richten Python 3.13 und Node.js 24
für ihre jeweiligen Jobs ein. Die Umgebung muss außerdem bereits die
JavaScript-Laufzeit zum Ausführen der Actions unterstützen. Bei einem
Container-Runner müssen Werkzeuge und Docker-Zugriff im Job-Container verfügbar
sein, nicht nur auf dem Host. Ein Standard-Node-Image allein enthält nicht die
gesamte Build-Umgebung. Runner-Konfiguration und Label-Zuordnung sind in der
[Runner-Anleitung](https://docs.gitea.com/1.26/usage/actions/act-runner/#labels)
beschrieben.
Der Runner muss Gitea, die Registry, die absoluten Action-URLs im Workflow und
die Downloadquellen für Python, Node.js, Python-Pakete und das Docker-Basisimage
erreichen. Die Actions sind auf vollständige Commit-SHAs festgelegt.
Der Artefakt-Upload verwendet die Gitea-kompatible Action
`https://gitea.com/actions/gitea-upload-artifact`.
Diese Build-Umgebung ist unabhängig vom AIS-Host-Runner für die
Postinstallation auf Proxmox-Hosts. CI-Code mit Docker-Zugriff auf einer dafür
vorgesehenen Build-Maschine ausführen; Änderungen an Workflow und CI-Skripten
vor der Übernahme prüfen.
## Registry und Veröffentlichung einrichten
Ohne `PUBLISH_IMAGES=true` führen alle Refs ausschließlich Prüfungen durch.
In **Settings → Actions → Variables** folgende Repository-Variablen setzen:
| Variable | Wert und Bedeutung |
| --- | --- |
| `AIS_RUNNER_LABEL` | Optional: Label eines verfügbaren Runners; Standard `ubuntu-latest` |
| `ARTIFACT_SERVER_URL` | Optional: aus dem Job-Container erreichbare Basis-URL derselben Gitea-Instanz, etwa `https://gitlab.bartelluis.de`; Standard ist `gitea.server_url` wie beim Checkout |
| `REGISTRY_HOST` | Optional: Registry-Host ohne Schema oder Pfad; Standard `gitlab.bartelluis.de` |
| `REGISTRY_USERNAME` | Gitea-Benutzername des Token-Inhabers; für Veröffentlichung erforderlich |
| `PUBLISH_IMAGES` | Genau `true`, nachdem Registry-Zugang und Schutzregeln eingerichtet sind |
Im Gitea-Benutzerkonto einen Personal Access Token mit Paket-Schreibrecht
(`write:package`, in der Oberfläche **package: Read & Write**) erstellen.
Den Wert unter **Settings → Actions → Secrets** als `REGISTRY_TOKEN` speichern.
Das zugehörige Konto muss beim Paketinhaber `BartelLuis` Schreibzugriff besitzen.
Das kurzlebige `GITEA_TOKEN` des Jobs unterstützt die Paketveröffentlichung
nicht; es ersetzt dieses Secret nicht.
[Gitea-Secrets](https://docs.gitea.com/usage/actions/secrets/),
[Paketberechtigungen](https://docs.gitea.com/usage/packages/overview/#access-restrictions),
[Gitea-Paketautorisierung](https://docs.gitea.com/usage/actions/comparison/#package-repository-authorization).
Der Imagepfad ist `<REGISTRY_HOST>/<kleingeschriebener Repositoryname>`, hier
`gitlab.bartelluis.de/bartelluis/proxmox-ais-server`. Bei einem Fork oder einer
Umbenennung ändert sich der Pfad. Pakete gehören in Gitea einem Benutzer oder
einer Organisation. Nach dem ersten Push kann das Paket in seinen Einstellungen
mit dem Repository verknüpft werden, damit es auch in dessen Paketliste erscheint.
[Gitea Container Registry](https://docs.gitea.com/usage/packages/container/).
## Standardbranch und Release-Tags schützen
Vor `PUBLISH_IMAGES=true` in den Gitea-Repository-Einstellungen den
Standardbranch `main` und die Release-Tags schützen. Für `main` direkte Pushes
deaktivieren, Reviews und erfolgreiche Statuschecks verlangen sowie Force-Push
und Löschen einschränken.
Für `v*` die Erstellung, Änderung und Löschung auf Release-Verantwortliche
begrenzen. Release-Tags auf einen geprüften Commit mit passender Projektversion
setzen. Die Schutzregeln werden durch den Workflow-Commit nicht angelegt.
[Geschützte Branches](https://docs.gitea.com/usage/access-control/protected-branches/),
[Geschützte Tags](https://docs.gitea.com/usage/access-control/protected-tags/).
Nach dem ersten Pull-Request-Lauf die tatsächlich gemeldeten Statuskontexte
für `python-tests`, `javascript-check`, `container-policy` und `container-verify`
als verpflichtend auswählen. `container-publish` wird bei Pull Requests übersprungen und ist
deshalb keine Pflichtprüfung.
Die CI entscheidet anhand von `PUBLISH_IMAGES`, Ereignis und Ref. Sie fragt
keinen Schutzstatus ab: Giteas kompatibler `ref_protected`-Wert ist dafür nicht
verwendbar. Die Gitea-Schutzregeln und restriktive Schreibrechte bleiben daher
ein eigener Einrichtungsschritt.
## Auslöser und Image-Tags
Der Workflow läuft bei Branch- und Tag-Pushes, bei Pull Requests sowie bei
manuellem Start über `workflow_dispatch`.
| Ref und Ereignis | Container-Job | Veröffentlichung |
| --- | --- | --- |
| `PUBLISH_IMAGES` fehlt oder ist nicht `true` | `container-verify` | Keine |
| Pull Request, Feature-Branch oder sonstiger Tag | `container-verify` | Keine |
| Standardbranch, Push oder manueller Start, Veröffentlichung aktiviert | `container-publish` | `sha-<vollständiger Commit-SHA>` und `edge` |
| Tag `vX.Y.Z`, Push oder manueller Start, Veröffentlichung aktiviert | `container-publish` | `sha-<vollständiger Commit-SHA>` und `X.Y.Z` |
Release-Tags müssen vollständig dem Muster
`v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)` entsprechen. Führende
Nullen, Vorabversionen und Build-Zusätze veröffentlichen kein Image.
Bei einem zur Veröffentlichung ausgewählten Release muss die Version außerdem
exakt `[project].version` in [pyproject.toml](../pyproject.toml) entsprechen;
eine Abweichung bricht den Publish-Job ab.
`edge` ist ein beweglicher Tag. Ein erneuter Lauf eines älteren Commits kann
ihn zurücksetzen. SHA-Tags enthalten alle 40 Zeichen des Commits; externe
Build-Abhängigkeiten können bei erneuten Builds zu einem anderen Digest führen.
Es gibt keinen `latest`-Tag. Im Betrieb den Digest aus `deploy.env` verwenden
und bisherige Registry-Versionen für Updates und Rollback aufbewahren.
## Jobs und Artefakte
`python-tests` führt über [ci/python-tests.sh](../ci/python-tests.sh) Pytest in
einer temporären virtuellen Umgebung aus. `javascript-check` prüft mit
`node --check provisioner/static/app.js` die JavaScript-Syntax.
`container-policy` wählt den Container-Job aus. Nach erfolgreichen
Quellcodeprüfungen läuft genau einer der Jobs `container-verify` und
`container-publish`. Beide verwenden [ci/container.sh](../ci/container.sh);
das Skript prüft vor einer Veröffentlichung erneut Ereignis, Ref und Opt-in.
Der [Container-Smoke-Test](../tests/container_smoke.py) prüft das gebaute
`linux/amd64`-Image auf TLS, Anmeldung, CSRF, Webdateien und Datenerhalt über
einen Dienstneustart. Er läuft als UID 10001 mit schreibgeschütztem Dateisystem,
entfernten Linux-Capabilities und temporären Testdaten. Eine Proxmox-Installation
ist kein Bestandteil dieses Tests. Der Publish-Job lädt das getestete Image
ohne zweiten Build hoch. OCI-Labels enthalten Quellrepository, Commit und
Anwendungsversion; Provenance- und SBOM-Attestierungen werden nicht erzeugt.
| Artefakt im Gitea-Lauf | Inhalt | Angefragte Aufbewahrung |
| --- | --- | --- |
| `python-test-results` | `reports/pytest.xml`, auch bei fehlgeschlagenen Tests, sofern erzeugt | 7 Tage |
| `container-build` | `build.env` nach erfolgreicher Verifikation | 7 Tage |
| `container-deploy` | `build.env` und `deploy.env` nach erfolgreicher Veröffentlichung | 30 Tage |
Die tatsächliche Aufbewahrung unterliegt der Gitea-Instanzkonfiguration.
Unter **Actions** den erfolgreichen Lauf öffnen und das gewünschte Artefakt
herunterladen. `build.env` enthält den temporären lokalen Image-Tag, Commit
und Anwendungsversion. Der lokale `ci-…`-Tag wird nicht veröffentlicht und am
Jobende entfernt. `deploy.env` enthält ausschließlich
`PROVISIONER_IMAGE=gitlab.bartelluis.de/bartelluis/proxmox-ais-server@sha256:…`
beziehungsweise die konfigurierte Registry-Adresse. Beide Dateien enthalten
keine Zugangsdaten. Die Image-Zeile in `.env` auf dem Zielserver übernehmen.
## Erster Lauf und Release
Nach dem Push unter **Actions** prüfen, dass der Lauf einen Runner erhält und
Python-, JavaScript- und Containerprüfungen erfolgreich sind. Nach aktivierter
Veröffentlichung muss ein Standardbranch-Lauf außerdem die Image-Tags
`sha-<Commit>` und `edge` sowie `container-deploy` mit `deploy.env` liefern.
Lokale Tests ersetzen diesen Lauf mit Registry-Anmeldung und Push nicht.
Für ein Release zuerst `[project].version` ändern und den geprüften Commit
übernehmen. Anschließend mit einem berechtigten Konto den passenden Tag
erstellen und an den Gitea-Remote pushen. Im vorhandenen Checkout heißt er
`origin`; vor dem Push mit `git remote -v` prüfen. Für Projektversion `0.9.1`,
sofern der Tag noch nicht existiert:
```bash
git tag -a v0.9.1 -m "Release 0.9.1"
git push origin v0.9.1
```
Der Release-Lauf muss den Image-Tag `0.9.1` erzeugen. Für einen manuellen Lauf
unter **Actions** den Workflow und den gewünschten Ref auswählen. Auch dabei
gelten Opt-in, Standardbranch- beziehungsweise Tag-Regel und Versionsabgleich.
Branch-Push und Pull Request können für denselben Quellstand getrennte Läufe
erzeugen. Pull Requests veröffentlichen keine Images.
## Fehlersuche und Deployment
Bei **Waiting** beziehungsweise **no matching online runner** das angeforderte
Label mit den erreichbaren Runnern unter **Settings → Actions → Runners**
vergleichen. Der bisherige Workflow verlangte `ubuntu-24.04`; dafür war kein
passender Online-Runner verfügbar. `AIS_RUNNER_LABEL` auf ein tatsächlich
vorhandenes Label setzen oder einen geeigneten Runner registrieren und starten.
Ein Label-Wechsel allein ersetzt keine fehlende Runner-Installation.
Bei fehlendem Docker-Zugriff `docker info` und `docker buildx version` in der
Job-Umgebung prüfen. Bei DNS-, Checkout- oder Artefaktfehlern müssen Giteas
öffentliche `ROOT_URL`, die Checkout-Adresse und die vom Runner erreichbare
Adresse zusammenpassen. Das Projekt verwendet `gitlab.bartelluis.de`; eine
von der Instanz zurückgegebene abweichende Adresse wie `git.bartelluis.de` muss
ebenfalls auflösbar und erreichbar sein oder in der Gitea-Konfiguration
korrigiert werden.
Bei einem Timeout von `ArtifactService/CreateArtifact` den Wert von
`ACTIONS_RESULTS_URL` im Upload-Schritt prüfen. Der Runner setzt ihn zunächst
aus seiner Registrierungsadresse; Runner 3.4.2 kann ihn für die
Artefaktweiterleitung durch die Adresse seines Cache-Servers ersetzen.
Ein interner Docker-Hostname oder eine nur im Runner-Container erreichbare
Adresse kann im getrennten Job-Netz unerreichbar sein. Der gemountete
Docker-Socket verbindet diese Netze nicht miteinander.
Der Workflow setzt deshalb für alle drei Uploads `ACTIONS_RESULTS_URL` auf
`gitea.server_url`, also dieselbe Gitea-Adresse wie beim Checkout. Falls nötig,
`ARTIFACT_SERVER_URL=https://gitlab.bartelluis.de` als Repository-Variable setzen.
Die Basis-URL enthält Schema und Host, gegebenenfalls einen Port, aber keinen
`/api/actions_pipeline`-Pfad. Sie muss dieselbe Gitea-Instanz erreichen.
Die Überschreibung muss direkt im Upload-Schritt stehen, da der Runner
Umgebungsvariablen auf Workflow- oder Job-Ebene überschreiben kann.
[Runner-Artefaktweiterleitung](https://gitea.com/gitea/runner/src/tag/v3.4.2/internal/app/run/runner.go#L598),
[Gitea-Laufzeitvariablen](https://docs.gitea.com/usage/actions/actions-variables/#internal-environment-variables).
Die Erreichbarkeit innerhalb eines Job-Containers prüfen:
```bash
curl --connect-timeout 5 --max-time 10 --silent --show-error --output /dev/null \
--write-out 'HTTP %{http_code}\n' \
https://gitlab.bartelluis.de/twirp/github.actions.results.api.v1.ArtifactService/CreateArtifact
```
Ein `405` auf diese GET-Anfrage bestätigt, dass der POST-Endpunkt erreichbar
ist; der eigentliche Upload benötigt weiterhin das vom Runner bereitgestellte
Job-Token. Bei einem Timeout DNS, Routing und Proxy-Zugriff aus dem Job-Netz
prüfen oder Runner und Jobs an ein gemeinsames erreichbares Docker-Netz
anbinden. Eine längere Wiederholungszeit behebt eine unerreichbare Adresse nicht.
Bei übersprungenem Publish-Job `PUBLISH_IMAGES`, Ereignis, Standardbranch und
Tagformat prüfen. Bei `denied` oder `unauthorized` Registry-Host, Benutzername,
`REGISTRY_TOKEN`, dessen `write:package`-Scope und die Rechte am Paketinhaber
kontrollieren. Bei Versionsfehlern Git-Tag und `pyproject.toml` abgleichen.
Fehlt `container-deploy`, die Build-, Smoke-, Push- und Upload-Logs prüfen.
Zum Deployment [compose.yaml](../compose.yaml) und
[config/deployment.env.example](../config/deployment.env.example) auf den
Zielserver kopieren. Die [Deployment-Anleitung](deployment.md) beschreibt
Registry-Anmeldung, Erstinitialisierung, HTTPS-Prüfung, Updates und Rollback.
Für private Pakete benötigt das Deploymentkonto einen eigenen Token mit
`read:package`; öffentliche Pakete lassen sich anonym laden.