feat: add Proxmox provisioning service with CI and deployment tooling
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
# API-Ablauf im Labor
|
||||
|
||||
Die API verwendet Sessioncookies und für alle Verwaltungsänderungen
|
||||
`X-CSRF-Token`. Die Beispiele benötigen Bash, curl und jq. Für PowerShell steht
|
||||
die gleiche API über `Invoke-RestMethod` zur Verfügung. Das vollständige Schema
|
||||
ist nach Anmeldung unter `/openapi.json` und als generierter Repositorystand in
|
||||
[openapi.json](openapi.json) verfügbar.
|
||||
|
||||
## Anmelden und Daten lesen
|
||||
|
||||
```bash
|
||||
export AIS_URL=https://provision.example.net
|
||||
umask 077
|
||||
read -rsp 'Administrator-Passwort: ' AIS_PASSWORD
|
||||
printf '\n'
|
||||
printf '%s' "$AIS_PASSWORD" | curl --fail --silent --show-error \
|
||||
--cookie-jar session.cookies --data-urlencode username=admin \
|
||||
--data-urlencode password@- "$AIS_URL/auth/login" --output /dev/null
|
||||
unset AIS_PASSWORD
|
||||
AIS_CSRF=$(curl --fail --silent --show-error --cookie session.cookies \
|
||||
"$AIS_URL/api/v1/me" | jq -r .csrf_token)
|
||||
curl --fail --silent --show-error --cookie session.cookies "$AIS_URL/api/v1/hosts"
|
||||
```
|
||||
|
||||
Cookies wie Zugangsdaten behandeln und nach Ende der Sitzung löschen.
|
||||
Der Server liefert bei nicht angemeldeten API-Aufrufen HTTP 401 und bei
|
||||
unzulässigen Rollen oder fehlendem CSRF-Token HTTP 403.
|
||||
|
||||
## Profilversion anlegen
|
||||
|
||||
Zuerst einen Root-Passwort-Hash als Geheimnis in der Weboberfläche speichern.
|
||||
Unter Linux kann ein SHA-512-crypt-Hash etwa mit `openssl passwd -6` interaktiv
|
||||
erzeugt werden. Den Hash nicht als Klartextfeld in ein Profil aufnehmen.
|
||||
|
||||
[sample-profile.json](sample-profile.json) kopieren und alle Platzhalter anpassen:
|
||||
Secret-ID, konkret geprüfter Build, Management-Interface, Datenträgerseriennummer
|
||||
und Inventarisierungsnachweis. `9.1-1` dient lediglich als Formatbeispiel und ist
|
||||
keine Kompatibilitätsfreigabe. FQDN und Management-IP kommen aus dem Hostinventar.
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --cookie session.cookies \
|
||||
--header "X-CSRF-Token: $AIS_CSRF" \
|
||||
--json @my-installation-profile.json "$AIS_URL/api/v1/profiles"
|
||||
```
|
||||
|
||||
Die Antwort enthält `id`, `version`, `digest` und `status=draft`. Ein weiterer
|
||||
POST mit demselben Namen und Typ erstellt eine neue unveränderliche Version.
|
||||
Bereits vorbereitete Läufe behalten die zuvor aufgelösten Werte und Versionen.
|
||||
|
||||
Veröffentlichung erfolgt in einer Sitzung einer anderen berechtigten Person:
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --cookie reviewer.cookies \
|
||||
--header "X-CSRF-Token: $REVIEWER_CSRF" \
|
||||
--json '{"test_evidence":"lab-report-2026-09-13","reason":"Labortest erfolgreich geprüft"}' \
|
||||
"$AIS_URL/api/v1/profiles/$PROFILE_ID/publish"
|
||||
```
|
||||
|
||||
Module werden analog über `POST /api/v1/modules` und
|
||||
`POST /api/v1/modules/{id}/publish` versioniert und veröffentlicht. Ausgangsvorlagen
|
||||
stehen unter `GET /api/v1/modules/builtin`. Sie enthalten keinen ausgefüllten
|
||||
Testnachweis und benötigen Anpassung sowie Prüfung auf dem Zielhost.
|
||||
|
||||
Ein Postinstallationsprofil verweist auf unveränderliche Modul-IDs:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "lab-postinstall",
|
||||
"kind": "postinstall",
|
||||
"target_builds": ["9.1-1"],
|
||||
"steps": [
|
||||
{
|
||||
"id": "final-check",
|
||||
"module_id": "REPLACE_WITH_PUBLISHED_MODULE_ID",
|
||||
"parameters": {},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"reboot_budget": 1
|
||||
}
|
||||
```
|
||||
|
||||
Modulabhängigkeiten müssen bereits in vorherigen Schritten enthalten sein.
|
||||
Mindestens eine Pflichtprüfung wird verlangt. Modulparameter werden gegen das
|
||||
JSON-Schema der gewählten Modulversion validiert.
|
||||
|
||||
## Host prüfen und Installation freigeben
|
||||
|
||||
Gruppentoken, geprüftes Medium und beide veröffentlichten Profile müssen bereits
|
||||
existieren. Der Host verweist über `installation_profile_id`,
|
||||
`postinstall_profile_id` und `iso_id` auf diese Objekte.
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --cookie session.cookies \
|
||||
"$AIS_URL/api/v1/hosts/$HOST_ID/preview"
|
||||
curl --fail --silent --show-error --cookie session.cookies \
|
||||
--header "X-CSRF-Token: $AIS_CSRF" \
|
||||
--json '{"expected_version":1,"valid_minutes":30,"confirmation":"pve01.lab.example.net","disks_confirmed":true,"reason":"Freigegebener dedizierter Labortest"}' \
|
||||
"$AIS_URL/api/v1/hosts/$HOST_ID/approve-install"
|
||||
```
|
||||
|
||||
Vor dem zweiten Aufruf tatsächlichen FQDN, aktuelle Hostversion und dargestellte
|
||||
Zielgeräte prüfen. Die Aktion erzeugt einen vorbereiteten Lauf und bindet die
|
||||
Konfiguration. Ein zweiter gleichzeitiger aktiver Lauf für denselben Host ist gesperrt.
|
||||
|
||||
Der Proxmox-Installer sendet seinen nativen Request selbst. Für einen isolierten
|
||||
API-Test zeigt dieses Beispiel die verwendete Payload-Struktur:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": {"version": "1.0"},
|
||||
"product": {"product": "pve"},
|
||||
"iso": {"release": "9.1", "build": "1"},
|
||||
"dmi": {
|
||||
"system": {
|
||||
"uuid": "d2e59b03-13cf-4ac9-a390-78c55f6a36d3",
|
||||
"serial": "LAB-HOST-001"
|
||||
}
|
||||
},
|
||||
"network-interfaces": [{"mac": "02:00:00:00:00:01"}]
|
||||
}
|
||||
```
|
||||
|
||||
Der echte Installer darf weitere Hardwarefelder senden. Registrierung und
|
||||
Antwortschema stammen aus den
|
||||
[offiziellen Installer-Typen](https://github.com/proxmox/proxmox-rs/blob/master/proxmox-installer-types/src/answer.rs).
|
||||
Ein unbekannter Host erhält keine TOML-Antwort. Der Gruppentoken muss zum Standort
|
||||
und zum freigegebenen Medium des Hosts passen.
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error \
|
||||
--header "Authorization: Bearer $INSTALLER_GROUP_TOKEN" \
|
||||
--json @installer-info.json "$AIS_URL/installer/v1/answer" \
|
||||
--output answer.toml
|
||||
```
|
||||
|
||||
Dieser Abruf verbraucht eine Freigabe. `answer.toml` enthält den Root-Hash und
|
||||
runbezogene Downloadberechtigungen; wie Zugangsdaten schützen. Innerhalb des
|
||||
kurzen Auslieferungsfensters liefern Wiederholungen dieselbe Antwort.
|
||||
|
||||
## Runner und Status
|
||||
|
||||
Der Starthelfer richtet den Runner automatisch ein. Das Gerät erzeugt einen
|
||||
eigenen Ed25519-Schlüssel, registriert dessen öffentlichen Anteil und signiert
|
||||
weitere Requests einschließlich Methode, Pfad, Zeitstempel, Nonce und Body-Digest.
|
||||
Ein gewöhnlicher curl-Aufruf mit Installer-Gruppentoken berechtigt nicht zum
|
||||
Manifest-, Artefakt- oder Secret-Abruf.
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --cookie session.cookies \
|
||||
"$AIS_URL/api/v1/runs/$RUN_ID"
|
||||
```
|
||||
|
||||
Die Laufansicht enthält Schrittzustände, redigierte Logs und die fixierte
|
||||
Konfiguration. Fortsetzung und Abbruch benötigen `expected_version` und einen
|
||||
Begründungstext über `/api/v1/runs/{id}/resume` bzw. `/cancel`. Eine Fortsetzung
|
||||
erst nach Prüfung des lokalen Zustands anfordern. Ein bereits laufender
|
||||
Paketmanager wird beim Abbruch am nächsten sicheren Übergang angehalten.
|
||||
|
||||
Für einen dauerhaft verlassenen Lauf steht `/api/v1/runs/{id}/reconcile` zur
|
||||
Verfügung. Zuerst den Abbruch anfordern, ausgestellte Leases und das
|
||||
Antwortauslieferungsfenster ablaufen lassen sowie Installer und Runner lokal
|
||||
stoppen und prüfen. Danach `expected_version`, `reason`, `confirmation` mit dem
|
||||
Host-FQDN und `execution_stopped: true` senden. Diese gesonderte Bestätigung beendet
|
||||
den Lauf als `cancelled`; eine neue Installation benötigt anschließend eine
|
||||
eigene Freigabe. Der Ablauf ist auch für `needs_review` nach Restore vorgesehen.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Kompatibilität und Abnahme
|
||||
|
||||
Es gibt zum Auslieferungszeitpunkt **keinen auf Hardware oder in einer VM
|
||||
freigegebenen Proxmox-ISO-Build**. Die Registrierung `test_status=passed` ist eine
|
||||
vom Administrator dokumentierte Freigabe; sie ersetzt keinen tatsächlichen Test.
|
||||
|
||||
| Kombination | Status | Nachweis |
|
||||
| --- | --- | --- |
|
||||
| API/SQLite/Sicherheitslogik, Python 3.14 unter Windows | Automatisierte lokale Softwaretests | `python -m pytest` |
|
||||
| Python 3.13 im bereitgestellten Dockerfile | Image gebaut; Tests, TLS-Start und Dienstneustart bestanden | Ergebnisse im [Testbericht](test-report.md) |
|
||||
| Python 3.12+ | Deklarierte Python-Untergrenze; keine vollständige CI-Matrix | Auf gewählter Version testen |
|
||||
| Proxmox VE ISO mit nativem Answer-Token und First-Boot-URL | Pro konkretem ISO-Build zu testen | Noch offen |
|
||||
| UEFI, BIOS und Secure Boot | Je verwendetem Bootmodus zu testen | Noch offen |
|
||||
| Netzunterbrechung/Neustart auf echtem Proxmox-Host | Runner-Softwaretests plus erforderlicher Integrationstest | Hardware-/VM-Test offen |
|
||||
| 100 Hosts, zehn gleichzeitige Installationen | Planungsziel | Lasttest offen |
|
||||
| RPO 24 Stunden / RTO zwei Stunden | Planungsziel | Betrieblicher Restore-Test offen |
|
||||
|
||||
Ein ISO ohne native Unterstützung für `--answer-auth-token` wird nicht freigegeben.
|
||||
Die Assistant-Version allein belegt nicht die Funktionen der Komponenten in einer
|
||||
älteren ISO. Alle Beispieldaten sind Platzhalter und stellen keine Freigabe dar.
|
||||
|
||||
## Freigabe eines Builds
|
||||
|
||||
Für jeden Build ein eigenes Prüfprotokoll außerhalb der Anwendung archivieren:
|
||||
|
||||
- Original-ISO und SHA-256, Assistant-Paketversion, Build-Befehl, Ergebnisdatei
|
||||
und Prüfsumme; Tokens im Protokoll redigieren.
|
||||
- Installer-Request als redigiertes JSON; UUID-, Seriennummer- und MAC-Zuordnung
|
||||
sowie Ablehnung unbekannter und widersprüchlicher Identitäten.
|
||||
- Antwortdatei ohne Geheimnisse, Prüfung mit dem zugehörigen Assistant, korrekte
|
||||
Netzwerkwerte und Auswahl ausschließlich der vorgesehenen Testdatenträger.
|
||||
- Bearer-Header, TLS-Verifikation, First-Boot-Download, Zeitpunkt des ersten
|
||||
Starts und Vertrauen in die Server-CA.
|
||||
- Modulabläufe `check`, `apply`, `verify`, Verlust einer Serverantwort,
|
||||
Netzausfall, erneuter Start und kontrollierter Reboot. Unterbrochene nicht sichere
|
||||
Änderungen müssen einen prüfbedürftigen Zustand ergeben.
|
||||
- Abschluss nur nach erfolgreichen Pflichtprüfungen; danach keine erneute
|
||||
Installationsantwort oder weitere Skriptausführung mit alten Berechtigungen.
|
||||
- Hardware-/VM-Konfiguration und Bootverfahren. Secure Boot nur nennen,
|
||||
wenn genau dieser Modus tatsächlich geprüft wurde.
|
||||
|
||||
Die Akzeptanzszenarien A01–A16 des Feinkonzepts sind die vollständige fachliche
|
||||
Abnahmeliste. Repositorytests decken die simulierbaren Server- und Runner-Teile ab,
|
||||
nicht die Betriebssysteminstallation oder reale Ausfälle.
|
||||
@@ -0,0 +1,241 @@
|
||||
# Deployment mit einem Image aus der Container Registry
|
||||
|
||||
Proxmox AIS wird auf dem Zielserver aus einem bereits veröffentlichten
|
||||
Containerimage gestartet. Der [GitHub-Actions-Workflow](github-actions.md) erstellt dieses Image; auf dem
|
||||
Zielserver werden nur Docker Compose und die Betriebskonfiguration benötigt.
|
||||
Diese Anleitung setzt einen erfolgreichen `container-publish`-Job und ein
|
||||
weiterhin abrufbares Image voraus.
|
||||
|
||||
## Voraussetzungen und Dateien
|
||||
|
||||
Der Zielserver benötigt Linux auf amd64, Docker Engine, das Docker-Compose-Plugin
|
||||
und einen HTTPS-Reverse-Proxy. Die Docker-Befehle unter einem Deploymentkonto
|
||||
mit Docker-Zugriff ausführen. Dasselbe Konto für `docker login` und
|
||||
`docker compose` verwenden, damit die Registry-Anmeldung verfügbar ist.
|
||||
Die [Docker-Installationsanleitung](https://docs.docker.com/engine/install/debian/)
|
||||
beschreibt beispielsweise die Installation auf Debian.
|
||||
|
||||
Python, Node.js, ein CI-Runner und Build-Werkzeuge gehören zur Build-Umgebung;
|
||||
die Anwendung samt Laufzeit ist im veröffentlichten Image enthalten. Ein
|
||||
Checkout des Anwendungsquellcodes ist auf dem Zielserver nicht erforderlich.
|
||||
|
||||
Ein beschreibbares Deploymentverzeichnis bereitstellen, beispielsweise
|
||||
`/opt/proxmox-ais`, und diese beiden Dateien aus dem gewünschten Projektstand
|
||||
herunterladen beziehungsweise dorthin kopieren:
|
||||
|
||||
| Datei aus dem Repository | Dateiname auf dem Zielserver |
|
||||
| --- | --- |
|
||||
| [compose.yaml](../compose.yaml) | `/opt/proxmox-ais/compose.yaml` |
|
||||
| [config/deployment.env.example](../config/deployment.env.example) | `/opt/proxmox-ais/.env` |
|
||||
|
||||
Im weiteren Verlauf alle Compose-Befehle in diesem Verzeichnis ausführen:
|
||||
|
||||
```bash
|
||||
cd /opt/proxmox-ais
|
||||
chmod 600 .env
|
||||
docker version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
Die Compose-Datei verwendet das Image aus `PROVISIONER_IMAGE`. Ein fehlender
|
||||
Wert führt bereits bei der Konfigurationsprüfung zu einem Fehler.
|
||||
|
||||
## Image und öffentliche Adresse festlegen
|
||||
|
||||
In GitHub unter **Actions** den erfolgreichen Workflow-Lauf mit
|
||||
`container-publish` öffnen, unter **Artifacts** das Archiv `container-deploy`
|
||||
herunterladen und entpacken. Es enthält `build.env` und `deploy.env`.
|
||||
Die vollständige Zeile `PROVISIONER_IMAGE=…@sha256:…` aus `deploy.env` in die
|
||||
`.env` auf dem Zielserver übernehmen. Der Digest legt genau das veröffentlichte
|
||||
Image fest. Compose liest `deploy.env` nicht automatisch ein.
|
||||
[GitHub-Artefakte herunterladen](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts).
|
||||
|
||||
Alternativ auf GitHub das zugehörige **Packages**-Paket öffnen und den vollständigen
|
||||
Imagepfad mit einem tatsächlich vorhandenen Versions-Tag kopieren. Der aktuelle
|
||||
Imagepfad der Beispieldatei sieht so aus:
|
||||
|
||||
```dotenv
|
||||
PROVISIONER_IMAGE=ghcr.io/netivra/proxmox-ais:0.1.0
|
||||
PUBLIC_URL=https://provision.example.net
|
||||
MAINTENANCE=false
|
||||
```
|
||||
|
||||
`PROVISIONER_IMAGE` und `PUBLIC_URL` für die Installation festlegen. Der
|
||||
Imagepfad entspricht dem aktuellen Wert der Vorlage; die Verfügbarkeit des
|
||||
Tags `0.1.0` wird damit nicht vorausgesetzt. Maßgeblich sind das
|
||||
Pipeline-Artefakt oder die Registry-Anzeige deines Projekts.
|
||||
Der Registry-Endpunkt ist `ghcr.io`. Für reproduzierbare Deployments den Digest
|
||||
bevorzugen und die bisher verwendeten Digests für spätere Updates aufbewahren.
|
||||
|
||||
`PUBLIC_URL` ist die vollständige HTTPS-Basisadresse ohne zusätzlichen Pfad,
|
||||
unter der Browser, Installer und installierte Hosts den Dienst erreichen.
|
||||
DNS und Zertifikatskette müssen für diese Systeme gültig sein. Der
|
||||
Reverse-Proxy auf dem Zielserver leitet diese Adresse an
|
||||
`http://127.0.0.1:8080` weiter. Der Compose-Port ist nur auf Loopback gebunden.
|
||||
Bei einem Proxy auf einem anderen Host oder in einem anderen Container dessen
|
||||
Verbindung gezielt nach der [Betriebsanleitung](operations.md#tls-und-reverse-proxy)
|
||||
einrichten; `127.0.0.1` bezeichnet dort jeweils das eigene System.
|
||||
|
||||
## An der Registry anmelden und Image laden
|
||||
|
||||
Öffentliche GHCR-Pakete lassen sich ohne Anmeldung herunterladen. Für ein
|
||||
privates Paket einen GitHub Personal Access Token **(classic)** mit dem Scope
|
||||
`read:packages` verwenden. Das zugehörige GitHub-Konto benötigt Lesezugriff auf
|
||||
das Paket; bei Organisations-SSO den Token dafür autorisieren. Der Token wird
|
||||
bei der Anmeldung als Passwort verdeckt eingegeben und gehört nicht in `.env`.
|
||||
Das kurzlebige `GITHUB_TOKEN` des CI-Jobs ist kein Deployment-Zugang.
|
||||
[GHCR-Authentifizierung](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic).
|
||||
|
||||
Nur für private Pakete anmelden und `GITHUB_USERNAME` durch den GitHub-Namen
|
||||
des Token-Inhabers ersetzen:
|
||||
|
||||
```bash
|
||||
docker login ghcr.io --username GITHUB_USERNAME
|
||||
```
|
||||
|
||||
Konfiguration prüfen und das gewählte Image herunterladen:
|
||||
|
||||
```bash
|
||||
docker compose config --quiet
|
||||
docker compose pull provisioner
|
||||
```
|
||||
|
||||
Erst nach erfolgreichem Pull fortfahren. `unauthorized` oder `denied` weist
|
||||
auf Anmeldung beziehungsweise Zugriffsrechte hin; bei `manifest unknown` den
|
||||
Imagepfad und den verfügbaren Tag/Digest prüfen. Bei internen Zertifikaten
|
||||
den CA-Vertrauensanker im Docker-Dienst des Zielservers einrichten.
|
||||
|
||||
## Einmalige Initialisierung und Start
|
||||
|
||||
Nur bei der ersten Inbetriebnahme mit neuen Daten- und Schlüsselvolumes den
|
||||
Administrator und den Master-Key anlegen:
|
||||
|
||||
```bash
|
||||
docker compose run --rm --no-deps \
|
||||
--volume proxmox-ais-keys:/run/secrets:rw provisioner init --username admin
|
||||
```
|
||||
|
||||
Das Passwort wird zweimal interaktiv abgefragt. Es gibt kein Standardpasswort.
|
||||
Das Schlüsselvolume ist für diesen einen Aufruf beschreibbar. Im Regelbetrieb
|
||||
wird es schreibgeschützt eingebunden. Bei einer bestehenden Installation oder
|
||||
einem Update diesen Initialisierungsbefehl nicht erneut ausführen.
|
||||
|
||||
Anschließend den Dienst starten:
|
||||
|
||||
```bash
|
||||
docker compose up -d --no-build --wait
|
||||
docker compose ps
|
||||
docker compose logs --tail=100 provisioner
|
||||
```
|
||||
|
||||
`--wait` wartet auf einen laufenden, gesunden Dienst gemäß dem Healthcheck im
|
||||
Image. Der Proxyzugriff wird separat geprüft.
|
||||
[Compose-Startoptionen](https://docs.docker.com/reference/cli/docker/compose/up/).
|
||||
Mit `curl`, sofern auf dem Zielserver verfügbar, den lokalen Dienst und die
|
||||
öffentliche HTTPS-Adresse prüfen; die Beispieladresse ersetzen:
|
||||
|
||||
```bash
|
||||
curl --fail http://127.0.0.1:8080/health/ready
|
||||
curl --fail https://provision.example.net/health/ready
|
||||
```
|
||||
|
||||
Beide Aufrufe sollen `"status":"ready"` liefern. Danach die öffentliche
|
||||
Adresse im Browser öffnen und mit dem angelegten Administrator anmelden.
|
||||
|
||||
## Daten und Schlüssel
|
||||
|
||||
Die Anwendung läuft im Container als UID/GID `10001:10001`. Die Compose-Datei
|
||||
verwendet diese dauerhaften, von Docker verwalteten Volumes:
|
||||
|
||||
| Volume | Inhalt | Containerpfad |
|
||||
| --- | --- | --- |
|
||||
| `proxmox-ais-data` | SQLite-Datenbank und Artefakte | `/var/lib/proxmox-ais` |
|
||||
| `proxmox-ais-keys` | Master-Key für verschlüsselte Geheimnisse | `/run/secrets` |
|
||||
|
||||
Für neue Volumes übernimmt Docker die vorbereiteten Verzeichnisse aus dem
|
||||
Image; sie sind für den Dienstbenutzer angelegt. Beim Wiederverwenden oder
|
||||
Wiederherstellen bestehender Volumes müssen Eigentümer und Rechte weiterhin
|
||||
zu UID/GID 10001 passen. Lokale Bind-Mount-Verzeichnisse benötigen dieselben
|
||||
passenden Rechte, falls die Compose-Datei später entsprechend angepasst wird.
|
||||
[Docker-Volumes](https://docs.docker.com/engine/storage/volumes/).
|
||||
|
||||
Containerneustarts und Updates erhalten diese Volumes.
|
||||
`docker compose down --volumes` würde sie löschen. Für mehrere getrennte Instanzen auf demselben
|
||||
Host die Volume-Namen in Compose und beim Initialisierungsaufruf je Instanz
|
||||
anpassen. Daten und Master-Key nach der
|
||||
[Backup-Anleitung](operations.md#backup) getrennt sichern.
|
||||
|
||||
## Updates
|
||||
|
||||
Ein Wartungsfenster vorsehen. Vor dem ersten Neustart den Digest des
|
||||
tatsächlich laufenden Images ermitteln, insbesondere bei Verwendung eines
|
||||
beweglichen Tags:
|
||||
|
||||
```bash
|
||||
AIS_RUNNING_IMAGE_ID=$(docker inspect --format '{{.Image}}' "$(docker compose ps -q provisioner)")
|
||||
docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$AIS_RUNNING_IMAGE_ID"
|
||||
```
|
||||
|
||||
Den zum Projekt passenden Registry-Digest zusammen mit dem Backup aufbewahren
|
||||
und in `.env` als `PROVISIONER_IMAGE` eintragen. Damit verwendet auch der
|
||||
Wartungsneustart genau die bisherige Version. Falls kein Registry-Digest
|
||||
angezeigt wird, die bisherige Version vor dem Update eindeutig zuordnen.
|
||||
|
||||
Jetzt in `.env` `MAINTENANCE=true` setzen und die Einstellung mit der
|
||||
bisherigen Version übernehmen:
|
||||
|
||||
```bash
|
||||
docker compose up -d --no-build --wait
|
||||
```
|
||||
|
||||
Der Wartungsmodus sperrt neue Installationsfreigaben. Bereits gestartete
|
||||
Installer und Host-Runner werden dadurch nicht beendet. Ihre laufenden
|
||||
Phasen vor dem eigentlichen Versionswechsel prüfen und einen geeigneten
|
||||
Zeitpunkt für den Dienststopp abwarten.
|
||||
|
||||
Jetzt die Daten mit der bisher eingesetzten Anwendungsversion sichern und
|
||||
den Master-Key separat verwahren. Die [Backup-Anleitung](operations.md#backup)
|
||||
enthält dafür die Containerbefehle. Zusätzlich die aktuelle `.env`, den
|
||||
bisherigen Image-Digest und die Proxykonfiguration sichern.
|
||||
|
||||
Danach die neue `PROVISIONER_IMAGE`-Zeile aus dem erfolgreichen
|
||||
`container-publish`-Job in `.env` übernehmen. `PUBLIC_URL` und die
|
||||
Volume-Namen beibehalten, `MAINTENANCE=true` gesetzt lassen. Folgender Ablauf
|
||||
prüft und lädt das neue Image, bevor er den bisherigen Dienst stoppt:
|
||||
|
||||
```bash
|
||||
(
|
||||
set -eu
|
||||
docker compose config --quiet
|
||||
docker compose pull provisioner
|
||||
docker compose stop provisioner
|
||||
docker compose up -d --no-build --wait
|
||||
docker compose ps
|
||||
)
|
||||
```
|
||||
|
||||
Bei einem fehlgeschlagenen Pull läuft der bisherige Dienst weiter. Nach dem
|
||||
Update Readiness über HTTPS, Anmeldung und Inventar prüfen. Erst nach
|
||||
erfolgreicher Prüfung in `.env` `MAINTENANCE=false` setzen und übernehmen:
|
||||
|
||||
```bash
|
||||
docker compose up -d --no-build --wait
|
||||
```
|
||||
|
||||
Der Initialisierungsbefehl gehört ausschließlich zur ersten Inbetriebnahme.
|
||||
|
||||
## Rückkehr zum vorherigen Stand
|
||||
|
||||
Falls der neue Dienst nicht gesund startet, zunächst
|
||||
`docker compose logs --tail=100 provisioner` prüfen. Das vorherige Image darf
|
||||
nur auf eine damit kompatible Datenbank zugreifen. Bei bestätigter
|
||||
Kompatibilität die gesicherte `.env` beziehungsweise den bisherigen exakten
|
||||
Image-Digest wiederherstellen und den Updateablauf erneut ausführen; den
|
||||
Wartungsmodus erst nach erfolgreicher Prüfung beenden.
|
||||
|
||||
Hat die neue Version das Schema bereits verändert, den Dienst stoppen und
|
||||
mit der zum Backup passenden Anwendungsversion nach der
|
||||
[Restore-Anleitung](operations.md#restore) in ein neues leeres Datenvolume
|
||||
wiederherstellen. Die vorhandenen Daten erhalten, bis die Wiederherstellung
|
||||
geprüft ist. Der Restore setzt Maschinenberechtigungen zurück und erfordert
|
||||
den dort beschriebenen Abgleich laufender Installationen.
|
||||
@@ -0,0 +1,203 @@
|
||||
# GitHub Actions und Container Registry
|
||||
|
||||
Der [Workflow](../.github/workflows/ci.yml) führt die Python-Tests aus, prüft die
|
||||
JavaScript-Syntax und baut und testet das Anwendungsimage. Freigegebene Builds
|
||||
werden in die GitHub Container Registry (GHCR) veröffentlicht. Der Workflow
|
||||
endet beim Image-Push und aktualisiert keinen Zielserver. Die anschließenden
|
||||
Betriebsschritte stehen in der [Deployment-Anleitung](deployment.md).
|
||||
|
||||
## Auslöser und Image-Tags
|
||||
|
||||
Der Workflow läuft bei Pushes auf alle Branches und Tags, bei Pull Requests
|
||||
und beim manuellen Start mit `workflow_dispatch`.
|
||||
|
||||
| Ref und Ereignis | Container-Job | Veröffentlichung |
|
||||
| --- | --- | --- |
|
||||
| Pull Request, auch aus einem Fork | `container-verify` | Keine |
|
||||
| Feature-Branch, ungeschützter Ref oder sonstiger Tag | `container-verify` | Keine |
|
||||
| Geschützter Standardbranch bei Push oder manuellem Start | `container-publish` | `sha-<vollständiger Commit-SHA>` und `edge` |
|
||||
| Geschützter Tag `vX.Y.Z` bei Push oder manuellem Start | `container-publish` | `sha-<vollständiger Commit-SHA>` und `X.Y.Z` |
|
||||
|
||||
Release-Tags müssen dem Muster `v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)`
|
||||
vollständig entsprechen. Führende Nullen, Vorabversionen wie `v1.0.0-rc.1` und
|
||||
Build-Zusätze veröffentlichen daher kein Image. Bei einem gültigen geschützten
|
||||
Release-Tag muss dessen Version außerdem exakt `[project].version` in
|
||||
[pyproject.toml](../pyproject.toml) entsprechen; eine Abweichung bricht den Lauf ab.
|
||||
|
||||
Das Image liegt für dieses Repository unter `ghcr.io/netivra/proxmox-ais`.
|
||||
Der Workflow leitet den Pfad aus dem kleingeschriebenen GitHub-Repositorynamen
|
||||
ab; bei einem Fork oder einer Umbenennung ändert sich deshalb der Imagepfad.
|
||||
`edge` bezeichnet den zuletzt veröffentlichten Standardbranch-Build. Ein
|
||||
erneuter Lauf eines älteren Commits kann `edge` zurücksetzen. SHA-Tags enthalten
|
||||
alle 40 Zeichen des Commits; erneute Builds desselben Quellstands können durch
|
||||
externe Build-Abhängigkeiten einen anderen Digest ergeben. Es gibt keinen
|
||||
`latest`-Tag. Im Betrieb den Digest aus `deploy.env` verwenden und die
|
||||
zugehörigen Registry-Versionen für Updates und Rollback aufbewahren.
|
||||
|
||||
## Jobs und Artefakte
|
||||
|
||||
`python-tests` und `javascript-check` prüfen den Quellstand. Das Skript
|
||||
[ci/python-tests.sh](../ci/python-tests.sh) installiert `.[dev]` in einer
|
||||
eigenen temporären virtuellen Umgebung, führt Pytest aus und entfernt die
|
||||
Umgebung anschließend. Die JavaScript-Prüfung verwendet
|
||||
`node --check provisioner/static/app.js`; sie ist kein Browsertest.
|
||||
`container-policy` bestimmt anhand des GitHub-Ereignisses, des Ref-Schutzes
|
||||
und des Branch- beziehungsweise Tag-Namens, welcher Container-Job laufen darf.
|
||||
|
||||
Nach erfolgreichen Python- und JavaScript-Prüfungen läuft genau einer der
|
||||
Jobs `container-verify` und `container-publish`. Beide bauen über
|
||||
[ci/container.sh](../ci/container.sh) ein `linux/amd64`-Image und prüfen es
|
||||
mit dem [Container-Smoke-Test](../tests/container_smoke.py). Der Publish-Job
|
||||
lädt genau das bereits getestete Image hoch, ohne einen zweiten Build.
|
||||
Die Veröffentlichung wird im Skript erneut auf Ereignis und Ref geprüft;
|
||||
dort erfolgt auch der Abgleich mit der Projektversion.
|
||||
|
||||
Publish-Jobs teilen eine Warteschlange mit `queue: max` und laufen einzeln.
|
||||
Bis zu 100 Jobs können warten; ein laufender Publish-Job wird durch neue
|
||||
Commits nicht abgebrochen. Die Reihenfolge richtet sich nach dem Eintritt in
|
||||
die Warteschlange, nicht zwingend nach dem Alter der Commits. Bei Pull Requests
|
||||
und ungeschützten Refs ersetzt ein neuer Lauf den vorherigen Lauf desselben Refs.
|
||||
[GitHub-Parallelität und Warteschlangen](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency).
|
||||
|
||||
Der Smoke-Test prüft TLS, Anmeldung, CSRF, ausgelieferte 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 Build verwendet `--provenance=false --sbom=false` und veröffentlicht
|
||||
keine Provenance- oder SBOM-Attestierungen. Die OCI-Labels für Quellrepository,
|
||||
Commit und Anwendungsversion bleiben erhalten.
|
||||
|
||||
| Artefakt in GitHub Actions | Inhalt | Aufbewahrung |
|
||||
| --- | --- | --- |
|
||||
| `python-test-results` | `reports/pytest.xml` als JUnit-Datei, auch nach fehlgeschlagenen Tests, sofern erzeugt | 7 Tage |
|
||||
| `container-build` | `build.env` nach erfolgreichem Verify-Job | 7 Tage |
|
||||
| `container-deploy` | `build.env` und `deploy.env` nach erfolgreichem Publish-Job | 30 Tage |
|
||||
|
||||
Die Aufbewahrung wird im Workflow mit `retention-days` gesetzt und ist durch
|
||||
die übergeordneten GitHub-Einstellungen begrenzt. Die JUnit-Datei wird als
|
||||
Download bereitgestellt; der Workflow installiert keinen zusätzlichen
|
||||
Testberichtsdienst. Auf der Übersicht des jeweiligen Laufs unter **Artifacts**
|
||||
das gewünschte Archiv herunterladen und entpacken.
|
||||
[Workflow-Artefakte](https://docs.github.com/en/actions/tutorials/store-and-share-data),
|
||||
[Artefakte herunterladen](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts).
|
||||
|
||||
`build.env` enthält den temporären lokalen Image-Tag, Commit und
|
||||
Anwendungsversion. Dieser `ci-…`-Tag wird nicht veröffentlicht und beim
|
||||
Jobende lokal entfernt. `deploy.env` enthält ausschließlich die
|
||||
Deploymentreferenz `PROVISIONER_IMAGE=ghcr.io/netivra/proxmox-ais@sha256:…`.
|
||||
Die Dateien enthalten keine Zugangsdaten. `deploy.env` wird auf dem Zielserver
|
||||
nicht automatisch eingelesen; seine Image-Zeile in die dortige `.env` übernehmen.
|
||||
|
||||
## GitHub einrichten
|
||||
|
||||
Ein eigener CI-Runner ist nicht erforderlich. Alle Jobs verwenden
|
||||
GitHub-gehostete Linux-amd64-Runner mit `ubuntu-24.04`. Der Workflow richtet
|
||||
Python 3.13 über `actions/setup-python` und Node.js 24 über `actions/setup-node`
|
||||
ein. Docker und Buildx werden vom Runner bereitgestellt und vor dem Build
|
||||
geprüft. Bash, OpenSSL und der OpenSSH-Client werden dort ebenfalls verwendet.
|
||||
Diese Build-Umgebung ist
|
||||
unabhängig vom AIS-Host-Runner für die Postinstallation auf Proxmox-Hosts.
|
||||
[GitHub-gehostete Runner](https://docs.github.com/en/actions/reference/runners/github-hosted-runners).
|
||||
|
||||
Im Repository unter **Settings → Actions → General** GitHub Actions aktivieren
|
||||
und die im Workflow verwendeten Actions von `actions/*` zulassen. Diese sind
|
||||
im Workflow auf vollständige Commit-SHAs festgelegt. Es werden
|
||||
keine selbst angelegten Repository-Secrets oder CI-Variablen benötigt.
|
||||
Der Workflow verwendet standardmäßig `contents: read`; nur
|
||||
`container-publish` erhält zusätzlich `packages: write` und verwendet das
|
||||
automatisch bereitgestellte `GITHUB_TOKEN` für die Anmeldung an `ghcr.io`.
|
||||
[Actions-Einstellungen](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository).
|
||||
|
||||
GHCR-Pakete sind bei der ersten Veröffentlichung standardmäßig privat. Ein
|
||||
bereits existierendes Paket muss mit diesem Repository verbunden sein und
|
||||
dessen Actions Schreibzugriff haben. In den Paketeinstellungen unter
|
||||
**Manage Actions access** gegebenenfalls `Netivra/Proxmox-AIS` mit Schreibrecht
|
||||
hinzufügen. Dort auch die gewünschte Paketsichtbarkeit festlegen.
|
||||
[GHCR und GITHUB_TOKEN](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-in-a-github-actions-workflow),
|
||||
[Paketzugriff und Sichtbarkeit](https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility).
|
||||
|
||||
## Standardbranch und Release-Tags schützen
|
||||
|
||||
**Vor der ersten Veröffentlichung müssen aktive Schutzregeln für die
|
||||
veröffentlichten Refs eingerichtet sein.** Der Workflow und das Skript
|
||||
verlangen `github.ref_protected == true`. GitHub setzt dieses Merkmal, wenn
|
||||
Branchschutz oder ein passendes Ruleset für den auslösenden Ref konfiguriert
|
||||
ist. Ohne Schutz wird nur geprüft.
|
||||
[GitHub-Ref-Kontext](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#github-context).
|
||||
|
||||
Unter den Repository-Einstellungen für Rulesets zwei Regeln anlegen und
|
||||
jeweils **Enforcement status: Active** wählen:
|
||||
|
||||
| Ruleset | Ziel | Vorgesehene Regeln |
|
||||
| --- | --- | --- |
|
||||
| Standardbranch | **Include default branch**, etwa `main` | Pull Request und Review verlangen; Löschen und Force-Push verhindern; erfolgreiche Statuschecks verlangen |
|
||||
| Releases | Tags mit dem Muster `v*` | Erstellung, Aktualisierung und Löschung beschränken; nur Release-Verantwortliche in die Bypass-Liste aufnehmen |
|
||||
|
||||
Für Pull Requests die Statuschecks `python-tests`, `javascript-check`,
|
||||
`container-policy` und `container-verify` nach ihrem ersten Lauf als
|
||||
verpflichtend auswählen. `container-publish` ist bei Pull Requests absichtlich
|
||||
übersprungen und wird deshalb nicht als Pflichtprüfung eingerichtet.
|
||||
Das Tag-Ruleset schützt alle `v*`-Tags; die engere Versionsprüfung übernimmt
|
||||
zusätzlich das CI-Skript. Release-Verantwortliche setzen Tags auf einen
|
||||
geprüften Commit mit der passenden Projektversion. Ein Tag auf einem Commit
|
||||
des geschützten Standardbranches ist selbst noch kein geschützter Tag.
|
||||
[Rulesets anlegen](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository),
|
||||
[Verfügbare Schutzregeln](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets).
|
||||
|
||||
Die Regeln und Paketberechtigungen im GitHub-Projekt sind Teil der Einrichtung;
|
||||
sie werden durch den Commit der Workflow-Datei nicht automatisch angelegt.
|
||||
Welche Rulesets verfügbar sind, hängt von Repository-Sichtbarkeit und
|
||||
GitHub-Tarif ab. Die CI-Abfragen ersetzen keine restriktiven Schreibrechte
|
||||
auf Repository und Paket; Änderungen an Workflow und CI-Skripten im Review prüfen.
|
||||
[Ruleset-Verfügbarkeit](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository).
|
||||
|
||||
## Erster Lauf und Release
|
||||
|
||||
Nach dem Push der Workflow-Datei unter **Actions** den Lauf öffnen.
|
||||
Python- und JavaScript-Prüfung sowie der passende Container-Job müssen
|
||||
erfolgreich sein. Für einen geschützten Standardbranch zusätzlich prüfen,
|
||||
dass das GHCR-Paket `sha-<Commit>` und `edge` enthält und das Artefakt
|
||||
`container-deploy` mit `deploy.env` bereitsteht. Ein lokaler Test ersetzt
|
||||
diesen ersten GitHub-Lauf samt GHCR-Anmeldung und Push nicht.
|
||||
|
||||
Für ein Release zuerst `[project].version` auf die gewünschte Version setzen
|
||||
und den geprüften Commit übernehmen. Anschließend durch ein für das
|
||||
Release-Ruleset berechtigtes Konto den passenden Tag erstellen und pushen.
|
||||
Im vorhandenen Checkout heißt der GitHub-Remote `GH-AIS`; in anderen Checkouts
|
||||
den Namen mit `git remote -v` prüfen und im Befehl entsprechend ersetzen.
|
||||
Für Projektversion `0.1.0`, sofern der Tag noch nicht existiert:
|
||||
|
||||
```bash
|
||||
git tag -a v0.1.0 -m "Release 0.1.0"
|
||||
git push GH-AIS v0.1.0
|
||||
```
|
||||
|
||||
Der Release-Lauf muss zusätzlich den Image-Tag `0.1.0` erzeugen. Für einen
|
||||
manuellen Branch-Lauf unter **Actions** den Workflow und **Run workflow**
|
||||
wählen. Dafür muss die Workflow-Datei bereits auf dem Standardbranch liegen.
|
||||
Ein manueller Lauf veröffentlicht nur, wenn dieselben Ref-Schutz- und
|
||||
Versionsbedingungen erfüllt sind.
|
||||
[Workflow manuell starten](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow).
|
||||
|
||||
Branch-Push und Pull Request können für denselben Quellstand getrennte Läufe
|
||||
erzeugen. Pull Requests werden im Merge-Kontext geprüft; ihre Tests führen
|
||||
keinen Registry-Push aus. Die Jobs laden keine produktiven Deployment-Zugänge.
|
||||
|
||||
## Fehlersuche und Deployment
|
||||
|
||||
Bei übersprungenem Publish-Job zuerst Ereignis, Standardbranch, Tagformat und
|
||||
aktive Schutzregeln prüfen. Bei einer Versionsabweichung müssen Git-Tag und
|
||||
`pyproject.toml` aufeinander abgestimmt werden. Bei `denied` oder `unauthorized`
|
||||
im Publish-Job die Organisationsrichtlinien, `packages: write` und den
|
||||
Actions-Zugriff auf das GHCR-Paket kontrollieren. Fehlt `container-deploy`,
|
||||
die Logs des Build-, Smoke- und Push-Schritts prüfen; vor erfolgreicher
|
||||
Veröffentlichung steht kein Deployment-Digest bereit.
|
||||
|
||||
Zum Deployment [compose.yaml](../compose.yaml) und
|
||||
[config/deployment.env.example](../config/deployment.env.example) auf den
|
||||
Zielserver kopieren und die Image-Zeile aus `deploy.env` verwenden.
|
||||
Die [Deployment-Anleitung](deployment.md) beschreibt GHCR-Anmeldung,
|
||||
Erstinitialisierung, HTTPS-Prüfung, Updates und Rollback. Für private Pakete
|
||||
benötigt das Deploymentkonto einen Personal Access Token (classic) mit
|
||||
`read:packages`; öffentliche Pakete lassen sich anonym laden.
|
||||
@@ -0,0 +1,50 @@
|
||||
# ISO auf einer Build-Maschine vorbereiten
|
||||
|
||||
Die ISO wird außerhalb des Webcontainers mit dem offiziellen
|
||||
`proxmox-auto-install-assistant` erstellt. Der Container benötigt weder
|
||||
privilegierte Rechte noch Zugriff auf einen Docker-Socket.
|
||||
|
||||
1. Original-ISO aus vertrauenswürdiger Proxmox-Quelle beziehen und ihre SHA-256
|
||||
gegen den veröffentlichten Wert prüfen. Build und Assistant-Version festhalten.
|
||||
2. In der Anwendung einen standortbezogenen Gruppentoken erzeugen. Den einmal
|
||||
ausgegebenen vollständigen Wert `<gruppenname>:<secret>` sicher übernehmen.
|
||||
3. Zertifikatsfingerprint des verwendeten TLS-Endpunkts über einen
|
||||
vertrauenswürdigen Weg ermitteln. Bei einem Reverse-Proxy dessen Zertifikat verwenden.
|
||||
4. Verfügbare Optionen prüfen und die ISO erstellen:
|
||||
|
||||
```bash
|
||||
proxmox-auto-install-assistant prepare-iso --help
|
||||
proxmox-auto-install-assistant prepare-iso SOURCE.iso \
|
||||
--fetch-from http \
|
||||
--url 'https://provision.example.net/installer/v1/answer' \
|
||||
--cert-fingerprint '<SHA256-Zertifikatsfingerprint>' \
|
||||
--answer-auth-token '<gruppenname>:<secret>'
|
||||
```
|
||||
|
||||
URL-, Fingerprint- und Token-Optionen entsprechen der
|
||||
[offiziellen Proxmox-Dokumentation](https://pdm.proxmox.com/docs/automated-installations.html#preparing-an-iso).
|
||||
Die URL zeigt hier auf den eigenen Antwortdienst. Den Befehl mit echtem Token
|
||||
nicht in öffentliche Logs oder gemeinsam genutzte Shell-History schreiben.
|
||||
|
||||
5. Ergebnis-ISO, Ausgangs-ISO, Assistant-Version, Build, Fingerprint und Gruppe
|
||||
in der Medienverwaltung registrieren. Zunächst `draft` verwenden.
|
||||
6. Den Build anhand der [Abnahmematrix](compatibility.md) im isolierten Labor
|
||||
prüfen. Ein neuer Assistant aktualisiert die Komponenten einer alten ISO
|
||||
nicht automatisch. Erst nach Prüfung `passed` setzen und Testnachweis referenzieren.
|
||||
|
||||
Der Installer sendet Hardwaremerkmale per POST und erhält bei eindeutiger,
|
||||
freigegebener Zuordnung eine TOML-Antwort. Ein kurzes Wiederholungsfenster liefert
|
||||
dieselbe Antwort für denselben Lauf. Nach dessen Ende oder Runner-Anmeldung
|
||||
ist der Abruf gesperrt. Bei abgelehnter Zuordnung die Ursache korrigieren und
|
||||
den Installationsversuch erneut starten.
|
||||
|
||||
Der dynamische First-Boot-Abschnitt verwendet `source = "from-url"` und
|
||||
`ordering = "network-online"`. Der Installer lädt den Starthelfer, das installierte
|
||||
System führt ihn beim ersten Boot aus. Diese Trennung beschreibt der
|
||||
[offizielle First-Boot-Implementierungsentwurf](https://lists.proxmox.com/pipermail/pve-devel/2024-November/066649.html).
|
||||
Die Gesamtfunktion muss mit der konkreten ISO praktisch geprüft werden.
|
||||
|
||||
Ein gemeinsamer ISO-Token ist vom Medium auslesbar, Hardwaremerkmale sind kopierbar.
|
||||
Zugriff auf Medium und Provisionierungsnetz beschränken. Nach erfolgreicher
|
||||
Installation die Bootreihenfolge auf das lokale System umstellen; eine bereits
|
||||
ausgelieferte Antwort kann der Server nicht nachträglich ungültig machen.
|
||||
+2502
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,216 @@
|
||||
# Betrieb und Wiederherstellung
|
||||
|
||||
Für die Installation aus der GitHub Container Registry (GHCR) siehe
|
||||
[Deployment-Anleitung](deployment.md). Alle `docker compose`-Befehle unten
|
||||
werden im Deployment-Verzeichnis mit `compose.yaml` und `.env` ausgeführt.
|
||||
`PROVISIONER_IMAGE` verweist auf das veröffentlichte Image, vorzugsweise per Digest.
|
||||
Die CLI ist im Container enthalten; auf dem Zielserver ist keine eigene
|
||||
Python-Installation erforderlich.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
`proxmox-ais` liest die optionale TOML-Datei aus `APP_CONFIG`, anschließend
|
||||
gleichnamige großgeschriebene Umgebungsvariablen. Relative Pfade beziehen sich
|
||||
auf das Arbeitsverzeichnis.
|
||||
|
||||
Die mitgelieferte Compose-Datei übernimmt `PROVISIONER_IMAGE`, `PUBLIC_URL`
|
||||
und `MAINTENANCE` aus `.env`. Weitere Anwendungseinstellungen bei Bedarf unter
|
||||
`services.provisioner.environment` in `compose.yaml` ergänzen.
|
||||
|
||||
| Einstellung | Standard | Bedeutung |
|
||||
| --- | --- | --- |
|
||||
| `DATA_DIR` | `./data` | SQLite, Artefakte und Dienstsperre auf lokalem Speicher |
|
||||
| `MASTER_KEY_FILE` | `./secrets/master.key` | Separat abgelegter Fernet-Schlüssel |
|
||||
| `PUBLIC_URL` | `https://localhost:8080` | Extern erreichbare Basis-URL ohne Pfad |
|
||||
| `SECURE_COOKIES` | `true` | Sessioncookies nur über HTTPS |
|
||||
| `FOUR_EYES` | `true` | Autor darf eigene Versionen nicht veröffentlichen |
|
||||
| `MAINTENANCE` | `false` | Neue Installationsfreigaben aussetzen |
|
||||
| `ANSWER_WINDOW_SECONDS` | `300` | Wiederholungsfenster des Antwortabrufs |
|
||||
| `ENROLLMENT_HOURS` | `4` | Maximale Registrierungsfrist |
|
||||
| `LEASE_SECONDS` | `900` | Gültigkeit der Runner-Ausführungsberechtigung |
|
||||
| `RUNNER_CA_FILE` | nicht gesetzt | Vertrauensanker für Runner bei interner CA |
|
||||
|
||||
`TESTING` und Bootstrap-Passwörter nicht im Regelbetrieb setzen. Benutzer werden
|
||||
interaktiv mit `init` und danach über die Administrationsoberfläche angelegt.
|
||||
Das Datenverzeichnis darf den Master-Key nicht enthalten. Unter Windows schützt
|
||||
zusätzlich eine auf das Dienstkonto beschränkte NTFS-ACL das Schlüsselverzeichnis.
|
||||
|
||||
## TLS und Reverse-Proxy
|
||||
|
||||
Der Compose-Port ist nur an Loopback gebunden. Ein bestehender Proxy terminiert
|
||||
TLS. Bei externem Proxy das private Container-/Verwaltungsnetz gezielt freigeben.
|
||||
Der Dienst wertet Proxy-Header standardmäßig nicht aus; alle absoluten URLs
|
||||
entstehen aus `PUBLIC_URL`. Bei einer nativen Installation kann der Dienst
|
||||
TLS auch direkt terminieren:
|
||||
|
||||
```bash
|
||||
proxmox-ais serve --host 0.0.0.0 --port 8080 \
|
||||
--tls-cert /etc/proxmox-ais/fullchain.pem \
|
||||
--tls-key /etc/proxmox-ais/server.key
|
||||
```
|
||||
|
||||
Zertifikatskette und DNS müssen im Installer und auf installierten Hosts gültig
|
||||
sein. Ein ISO-Fingerprint ersetzt nicht automatisch den CA-Vertrauensanker des
|
||||
Runners. Bei interner CA `RUNNER_CA_FILE` konfigurieren und die gesamte Kette vom
|
||||
Installer bis zum Runner prüfen. Keine TLS-Prüfung abschalten. Zertifikatsrotation
|
||||
erfordert die Prüfung vorhandener Medien.
|
||||
|
||||
Capability-Tokens stehen zwangsläufig in Bootstrap-URLs. Der integrierte Server
|
||||
schreibt daher keine Access-Logs. Im vorgeschalteten Proxy Bootstrap- und
|
||||
Installer-Report-Pfade redigieren oder deren Access-Logging deaktivieren. Keine
|
||||
Authorization-Header protokollieren. Skriptausgaben werden zusätzlich redigiert;
|
||||
Skripte sollten Geheimnisse grundsätzlich nie ausgeben.
|
||||
|
||||
## Betrieb
|
||||
|
||||
Der Dienst verwendet genau einen Anwendungsprozess. Mehrere Uvicorn-Worker oder
|
||||
gleichzeitige Dienste für dasselbe Datenverzeichnis verhindert die Dienstsperre.
|
||||
SQLite benötigt lokalen Speicher mit zuverlässigen Dateisperren; kein NFS/SMB-Volume
|
||||
einsetzen. Als Startwert sind 2 vCPU, 2 GB RAM und 20 GB Speicher ohne ISO-Archiv
|
||||
vorgesehen; Kapazität muss unter realer Last geprüft werden.
|
||||
|
||||
`/health/live` prüft den Prozess, `/health/ready` Datenbank und Konfiguration.
|
||||
Readiness, freien Speicher, Backupalter und ausbleibende Runner-Meldungen überwachen.
|
||||
DHCP, DNS, NTP und Paketquellen sind externe Voraussetzungen. Der Server baut keine
|
||||
eingehenden SSH-Verbindungen zu Hosts auf.
|
||||
|
||||
## Backup
|
||||
|
||||
Beim Registry-Deployment die Sicherung mit der aktuell eingesetzten Image-Version
|
||||
erstellen, bevor `PROVISIONER_IMAGE` für ein Update geändert wird. Das
|
||||
Hostverzeichnis `/srv/ais-backups` muss vorhanden und für UID/GID 10001
|
||||
beschreibbar sein. Beispielsweise einmalig vom Administrator anlegen:
|
||||
|
||||
```bash
|
||||
sudo install -d -o 10001 -g 10001 -m 0700 /srv/ais-backups
|
||||
docker compose run --rm --no-deps --volume /srv/ais-backups:/backups \
|
||||
provisioner backup /backups/2026-09-13
|
||||
```
|
||||
|
||||
Für jede Sicherung einen neuen Zielnamen wählen. Bei einer nativen
|
||||
Python-Installation lautet der entsprechende Befehl
|
||||
`proxmox-ais backup /srv/ais-backups/2026-09-13`.
|
||||
|
||||
Das Ziel muss neu sein und außerhalb von `DATA_DIR` liegen. Die Anwendung kann
|
||||
weiterlaufen: Die SQLite-Backup-API erzeugt einen konsistenten Snapshot, anschließend
|
||||
werden unveränderliche Artefakte kopiert. Artefakte währenddessen nicht manuell
|
||||
löschen. Die Sicherung enthält `database.sqlite3`, `artifacts/`, `settings.json`
|
||||
und `manifest.json`. Der Master-Key wird ausgeschlossen; sein Fingerprint verhindert
|
||||
die Wiederherstellung mit einem versehentlich falschen Schlüssel.
|
||||
|
||||
`settings.json` enthält die wirksamen Anwendungseinstellungen einschließlich
|
||||
Standortvorgaben, Fristen und Freigaberegeln. Lokale Daten-/Schlüsselpfade und
|
||||
Bootstrap-Zugangsdaten sind ausgeschlossen. Individuelle TOML-Datei,
|
||||
Umgebungsvariablen, Proxykonfiguration, CA-/TLS-Dateien, ursprüngliche
|
||||
ISOs und Prüfprotokolle separat sichern. Registrierte ISO-Metadaten liegen in der
|
||||
Datenbank. Backup-Prüfsummen erkennen beschädigte Dateien; sie sind keine Signatur
|
||||
gegen einen Angreifer mit Schreibzugriff. Backupverzeichnis deshalb schützen.
|
||||
|
||||
Den Master-Key aus `proxmox-ais-keys` über einen getrennten verschlüsselten
|
||||
Sicherungsweg verwahren.
|
||||
Eine lokale Kopie in ein bereits vorhandenes Zielverzeichnis ist möglich:
|
||||
|
||||
```bash
|
||||
docker compose cp provisioner:/run/secrets/master.key /secure/offline/master.key
|
||||
```
|
||||
|
||||
Die Schlüsselkopie ist genauso vertraulich wie alle gespeicherten Geheimnisse.
|
||||
|
||||
## Restore
|
||||
|
||||
1. Wartungsmodus aktivieren, neue Freigaben stoppen und Anwendungsdienst
|
||||
herunterfahren. Die Host-Runner separat berücksichtigen: ein bereits
|
||||
gestarteter Befehl kann noch laufen.
|
||||
2. Bisheriges Datenverzeichnis erhalten. Ein neues leeres Datenverzeichnis
|
||||
wählen und den zum Backup gehörenden Master-Key separat bereitstellen.
|
||||
3. Die gleiche Anwendungsversion wie beim Backup verwenden. Beim
|
||||
Registry-Deployment deren Digest in `.env` als `PROVISIONER_IMAGE` setzen
|
||||
und herunterladen. Das Schlüsselvolume muss bereits den passenden
|
||||
Master-Key als `master.key`, lesbar für UID/GID 10001, enthalten. In ein
|
||||
neues, leeres Datenvolume wiederherstellen:
|
||||
|
||||
```bash
|
||||
docker compose stop provisioner
|
||||
docker compose pull provisioner
|
||||
docker volume create proxmox-ais-data-restored
|
||||
docker compose run --rm --no-deps \
|
||||
--volume /srv/ais-backups:/backups:ro \
|
||||
--volume proxmox-ais-data-restored:/var/lib/proxmox-ais \
|
||||
provisioner restore /backups/2026-09-13
|
||||
```
|
||||
|
||||
`proxmox-ais-data-restored` muss neu sein; existiert es schon, einen anderen
|
||||
unbenutzten Namen wählen. Nach erfolgreichem Restore in `compose.yaml` unter
|
||||
`volumes.ais-data.name` diesen neuen Namen eintragen. Das bisherige Datenvolume
|
||||
bleibt erhalten. Bei einer nativen Python-Installation alternativ:
|
||||
|
||||
```bash
|
||||
export DATA_DIR=/srv/proxmox-ais-restored
|
||||
export MASTER_KEY_FILE=/secure/offline/master.key
|
||||
proxmox-ais restore /srv/ais-backups/2026-09-13
|
||||
```
|
||||
|
||||
4. Individuelle Konfiguration und Zertifikate wiederherstellen. Im Wartungsmodus
|
||||
starten, anmelden und Inventar sowie aktuelle Hostzustände abgleichen.
|
||||
Beim Registry-Deployment dafür `MAINTENANCE=true` in `.env` setzen und
|
||||
`docker compose up -d --no-build --wait` ausführen.
|
||||
5. Aktive Läufe stehen auf `needs_review`. Alte Sessions, Gerätezugänge,
|
||||
Bootstrap-/Enrollment-/Report-Capabilities und Gruppentokens gelten nicht mehr;
|
||||
offene Installationsfreigaben sind gesperrt. Gruppentokens und davon abhängige
|
||||
Medien neu ausgeben. Zuvor ausgelieferte Installer nicht ungeprüft neu booten.
|
||||
6. Installer und Runner auf jedem betroffenen Host lokal stoppen und den
|
||||
tatsächlichen Zustand prüfen. Danach den verlassenen Lauf über
|
||||
`POST /api/v1/runs/{id}/reconcile` abschließen: aktuelle `expected_version`,
|
||||
aussagekräftige `reason`, `confirmation` mit dem Host-FQDN und
|
||||
`execution_stopped: true` übermitteln. Die Aktion beendet den Lauf als
|
||||
`cancelled`, sperrt seine Berechtigungen und schreibt einen Auditeintrag.
|
||||
Erst danach ist eine neue ausdrückliche Installationsfreigabe möglich.
|
||||
Der automatische Wiederanschluss alter Geräteidentitäten nach Restore
|
||||
ist nicht Teil dieser Version.
|
||||
|
||||
Außerhalb eines Restore zuerst `/cancel` anfordern. Ein offline befindlicher
|
||||
Runner kann seine bereits erhaltene Lease bis zum Ablauf behalten; eine
|
||||
Abbruchanforderung zieht diese nicht zurück. `/reconcile` weist den Abgleich
|
||||
deshalb zurück, solange eine zuvor ausgestellte Lease oder das
|
||||
Antwortauslieferungsfenster noch gültig ist. Auch nach deren Ablauf ist die
|
||||
ausdrückliche Bestätigung erforderlich, dass Installer und Runner lokal gestoppt
|
||||
und kontrolliert wurden. Eine nicht mehr erreichbare Maschine ist kein Nachweis
|
||||
dafür. Das Verfahren erteilt selbst keine Neuinstallationsfreigabe.
|
||||
|
||||
Restore überschreibt keine bestehende Datenbank. Die Dienstsperre verhindert
|
||||
Restore während eines laufenden Dienstes im Zielverzeichnis. Ein fehlgeschlagener
|
||||
Restore hinterlässt `RESTORE_FAILED`; der CLI-Start verweigert dann den Betrieb.
|
||||
Nach Ursachenklärung erneut in ein neues leeres Ziel restaurieren.
|
||||
|
||||
Bereits heruntergeladene Antwortdateien kann der Server nicht zurückziehen.
|
||||
Ebenso beendet eine Credential-Sperrung einen lokal laufenden Skriptprozess nicht
|
||||
unmittelbar. Restore erfordert deshalb den physischen Zustandsabgleich der Hosts.
|
||||
|
||||
RPO 24 Stunden und RTO zwei Stunden sind Ziele aus dem Konzept. Erst wiederholte
|
||||
Sicherung und zeitlich gemessene Wiederherstellung unter realen Bedingungen
|
||||
weisen diese Ziele nach.
|
||||
|
||||
## Updates
|
||||
|
||||
Der vollständige Ablauf für Registry-Images steht unter
|
||||
[Deployment aktualisieren](deployment.md#updates).
|
||||
Vor dem ersten Neustart `PROVISIONER_IMAGE` auf den Digest des tatsächlich
|
||||
laufenden Images festlegen, wie dort beschrieben. Anschließend
|
||||
`MAINTENANCE=true` in `.env` setzen und die laufende Version mit
|
||||
`docker compose up -d --no-build --wait` neu erstellen. Der Wartungsmodus
|
||||
sperrt neue Freigaben; bereits gestartete Installations- und Modulabläufe werden
|
||||
dadurch nicht beendet. Diese vor dem Update kontrolliert abschließen lassen.
|
||||
|
||||
Mit der bisherigen Version Daten und Schlüssel sichern und den alten
|
||||
Image-Digest festhalten. Erst danach `PROVISIONER_IMAGE` ändern, das neue Image
|
||||
mit `docker compose pull provisioner` laden, den Dienst mit
|
||||
`docker compose stop provisioner` stoppen und mit
|
||||
`docker compose up -d --no-build --wait` neu starten. Nach Prüfung von Anmeldung,
|
||||
Inventar und Readiness `MAINTENANCE=false` setzen und erneut `up` ausführen.
|
||||
Bei Updates wird `init` nicht wiederholt.
|
||||
|
||||
Die Datenbank
|
||||
führt eine Migrationstabelle und `PRAGMA user_version`. Neuere unbekannte Schemas
|
||||
werden abgewiesen. Downgrades erfolgen über die zur Sicherung passende Version
|
||||
und den beschriebenen Restore; keine alte Anwendung auf eine bereits migrierte
|
||||
Produktivdatenbank starten.
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "lab-system-single-disk",
|
||||
"kind": "installation",
|
||||
"target_builds": ["9.1-1"],
|
||||
"reason": "Beispiel zur Anpassung im isolierten Labor",
|
||||
"values": {
|
||||
"root_secret_id": "REPLACE_WITH_SECRET_ID",
|
||||
"global": {
|
||||
"keyboard": "de",
|
||||
"country": "de",
|
||||
"timezone": "Europe/Berlin",
|
||||
"mailto": "admin@example.net"
|
||||
},
|
||||
"network": {
|
||||
"source": "from-answer",
|
||||
"gateway": "192.0.2.1",
|
||||
"dns": "192.0.2.53",
|
||||
"filter": {"ID_NET_NAME_MAC": "enx020000000001"}
|
||||
},
|
||||
"disk_setup": {
|
||||
"filesystem": "ext4",
|
||||
"filter": {"ID_SERIAL_SHORT": "LAB_SYSTEM_DISK_001"},
|
||||
"filter_match": "all",
|
||||
"expected_count": 1,
|
||||
"expected_serials": ["LAB_SYSTEM_DISK_001"],
|
||||
"inventory_evidence": "REPLACE_WITH_VERIFIED_DISK_INVENTORY_REFERENCE"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
# Softwaretestbericht
|
||||
|
||||
Stand: 13. September 2026. Die hier dokumentierten Softwaretests führen keine
|
||||
Proxmox-Installation aus und verändern keine Hostdatenträger, Paketquellen oder
|
||||
Produktivdienste. Die noch offenen praktischen Prüfungen stehen in der
|
||||
[Kompatibilitätsmatrix](compatibility.md).
|
||||
|
||||
## Automatisierte Prüfungen
|
||||
|
||||
Die 49 Anwendungstests wurden unter Windows mit Python 3.14 ausgeführt:
|
||||
47 Tests bestanden; zwei Linux-spezifische Prozesstests wurden dort übersprungen.
|
||||
Die 68 zusätzlichen CI-Skripttests bestanden unter Windows mit Git Bash.
|
||||
Insgesamt enthält die Testsuite jetzt 117 Testfälle; der aktuelle Gesamtlauf
|
||||
unter Windows/Python 3.14.7 ergab 115 bestandene und zwei übersprungene Tests.
|
||||
Unter Linux/Python 3.13.15 bestanden alle 117 Tests über das migrierte
|
||||
CI-Einstiegsskript `ci/python-tests.sh`, einschließlich der OpenSSH-Prüfung.
|
||||
|
||||
| Testgruppe | Umfang | Geprüftes Verhalten |
|
||||
| --- | --- | --- |
|
||||
| `tests/test_acceptance.py` | 14 bestanden | Authentifizierung, CSRF, Rollen und Vieraugenregel; Ablehnung unbekannter, gesperrter und widersprüchlicher Hosts; Gruppenzuordnung und Freigabeablauf; zehn parallele Antwortabrufe mit genau einem Lauf; unveränderliche Konfiguration; Enrollment, Signaturen, Nonce-Replay, fremde Läufe, Ereignisreihenfolge und Pflichtverifikation; Artefaktmanipulation; Verschlüsselung; Backup/Restore und Schlüsselprüfung; Abgleich verlassener Läufe erst nach Lease-Ablauf und ausdrücklicher lokaler Bestätigung |
|
||||
| `tests/test_protocol.py` | 8 bestanden | Tatsächlicher Runner gegen die HTTP-API über einen Testtransport mit gültigen Ed25519-Signaturen; Check-/Apply-Abläufe, verlorene Abschlussantwort, simulierter Neustart, fehlgeschlagene Verifikation und Operatorfortsetzung, wiederholte Abbruchbestätigung, optionale Fehler mit verpflichtender Abschlussprüfung sowie redigierte Logs |
|
||||
| `tests/test_runner.py` | Windows: 25 bestanden, 2 übersprungen; Linux-CI: 27 bestanden | Dauerhafte Checkpoints, Wiederaufnahme nach Unterbrechung, Ablauf von Leases, Rebootbudget, Queue- und Logbegrenzung, Secrets-Redigierung, Hashprüfung, TLS-Zwang, eigenständiger Starthelfer, lokale Hardwaremerkmale, echte OpenSSL-Signaturen, SSH-Schlüsselprüfung, Repositoryprüfung, Bash-Syntaxprüfung sowie Prozess-Timeout und Ausgaberedigierung |
|
||||
| `tests/test_ci.py` | 68 bestanden | Veröffentlichung nur von geschützten Standardbranch-/Versions-Refs und erlaubten GitHub-Ereignissen; Release-Versionsabgleich; GHCR-Pfade; kein Push nach Build-/Smoke-Fehlern; Digest-Artefakte; temporäre Docker-Zugangsdaten und Image-Tags; Isolation nach Run-ID, Wiederholung und Job-ID; eigene Python-Testumgebung mit Aufräumen bei Erfolg und Fehlern; fehlende Host-Werkzeuge verhindern übersprungene Pflichtprüfungen |
|
||||
|
||||
Die API- und Protokolltests verwenden eine temporäre SQLite-Datenbank sowie
|
||||
temporäre Schlüssel. Die Protokolltests führen die Runner-Steuerung und
|
||||
Signaturprüfung aus; die eigentlichen Modulphasen werden simuliert. Runner-Tests
|
||||
prüfen Bash-Syntax und eingebetteten Python-Code ohne Provisionierungsänderungen.
|
||||
Das ist kein Nachweis für funktionierende Betriebssysteminstallation oder
|
||||
erfolgreiche Konfiguration echter Proxmox-Dienste.
|
||||
|
||||
Unter Windows benötigt die Veröffentlichung von Bash-Modulen Git Bash.
|
||||
Der vorhandene WSL-Bash-Starter ohne Linux-Distribution genügt nicht. Die Anwendung
|
||||
erkennt die native Git-Bash-Installation für die Syntaxprüfung.
|
||||
|
||||
Alle Tests erneut ausführen:
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\python.exe -m pytest
|
||||
```
|
||||
|
||||
Beim Testlauf können Upstream-DeprecationWarnings von Starlette/httpx und AnyIO
|
||||
erscheinen. Sie sind von fehlgeschlagenen Tests zu unterscheiden.
|
||||
|
||||
## Containerprüfung
|
||||
|
||||
`proxmox-ais-server:0.1.0` wurde erfolgreich mit Python 3.13 und Debian slim
|
||||
gebaut. Das Dockerfile fixiert die geprüfte Basis über den Digest
|
||||
`sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285`.
|
||||
|
||||
Ein kurzlebiger Container wurde mit schreibgeschütztem Dateisystem, deaktiviertem
|
||||
Netzwerk und entfernten Linux-Capabilities gestartet. Die Prüfung ergab:
|
||||
|
||||
| Merkmal | Beobachtung |
|
||||
| --- | --- |
|
||||
| Benutzer | UID 10001 |
|
||||
| Bash | `/usr/bin/bash`, GNU Bash 5.2.37 |
|
||||
| OpenSSL | `/usr/bin/openssl`, OpenSSL 3.5.7 |
|
||||
| CLI-Einstiegspunkt | `proxmox-ais`, Unterbefehle `init`, `serve`, `backup`, `restore` |
|
||||
|
||||
Auch Wheel-Erstellung und Installation wurden erfolgreich geprüft. In einem
|
||||
früheren Linux-Testlauf im Anwendungsimage bestanden 48 Tests; ein Test wurde übersprungen, weil
|
||||
`ssh-keygen` im Anwendungsimage nicht installiert ist. Die SSH-Schlüsselprüfung
|
||||
wurde unter Windows ausgeführt. Der Linux-Lauf umfasste die POSIX-spezifischen
|
||||
Prüfungen zur Prozessbeendigung nach Timeout und zur Redigierung von Ausgaben.
|
||||
Der Testcontainer lief ohne Rootrechte, mit schreibgeschütztem Dateisystem,
|
||||
entfernten Capabilities und `no-new-privileges`.
|
||||
|
||||
`tests/container_smoke.py` prüfte zusätzlich den installierten Dienst als echten
|
||||
Prozess im Container: Anmeldung über TLS mit eigener vertrauenswürdiger Test-CA,
|
||||
Ablehnung eines nicht vertrauten Zertifikats, CSRF, ausgelieferte Webdateien sowie
|
||||
Erhalt von Hosts und Sitzungen nach einem Dienstneustart. Der Prozess lief als
|
||||
UID 10001 mit schreibgeschütztem Root-Dateisystem, ohne Capabilities und mit
|
||||
`no-new-privileges`; sämtliche Testdaten lagen in einem temporären Verzeichnis.
|
||||
|
||||
Die folgende Abfrage verändert weder Anwendungsdaten noch Konfiguration. Reproduzierbarer
|
||||
Befehl für Bash unter Linux oder Git Bash:
|
||||
|
||||
```bash
|
||||
docker run --rm --read-only --network none --cap-drop ALL \
|
||||
--entrypoint python proxmox-ais-server:0.1.0 \
|
||||
-c 'import os, shutil, subprocess; print(os.getuid()); print(shutil.which("bash")); print(shutil.which("openssl")); subprocess.run(["bash", "--version"], check=True); subprocess.run(["openssl", "version"], check=True)'
|
||||
```
|
||||
|
||||
TLS-Reverse-Proxy, reale Provisionierungsnetze, Medienboot, Initialisierung und
|
||||
Wiederherstellung in der späteren Betriebsumgebung müssen zusätzlich geprüft werden.
|
||||
Ein erfolgreicher Image-Build allein ist keine Betriebsfreigabe.
|
||||
|
||||
## GitHub Actions und GHCR
|
||||
|
||||
Der [Workflow](../.github/workflows/ci.yml) verwendet GitHub-gehostete
|
||||
Ubuntu-24.04-Runner, Python 3.13 und Node.js 24. Python- und JavaScript-Prüfung
|
||||
müssen erfolgreich sein, bevor das Containerimage gebaut und getestet wird.
|
||||
Die [Einrichtungsanleitung](github-actions.md) beschreibt Schutzregeln,
|
||||
Paketberechtigungen und die erste Veröffentlichung.
|
||||
|
||||
Bei der Migration wurden die folgenden Prüfungen lokal ausgeführt:
|
||||
|
||||
- Die vollständige Windows-Testsuite bestand mit 115 erfolgreichen Tests;
|
||||
zwei Linux-Prozesstests wurden plattformbedingt übersprungen.
|
||||
- Unter Linux/Python 3.13.15 führte `ci/python-tests.sh` alle 117 Tests
|
||||
erfolgreich als UID 10001 aus. Das Skript installierte die Abhängigkeiten
|
||||
in einer eigenen virtuellen Umgebung, schrieb den JUnit-Bericht und
|
||||
entfernte die Umgebung anschließend.
|
||||
- Die 68 CI-Skripttests prüfen mit Docker- und Python-Testdoubles
|
||||
GitHub-Ereignisse, geschützte Refs, Versionsabgleich, GHCR-Pfade,
|
||||
Publish-Reihenfolge, Digest-Artefakte und Fehlerpfade. Run-ID, Wiederholung
|
||||
und Job-ID isolieren temporäre Ressourcen. Ein Build- oder Smoke-Fehler
|
||||
verhindert Registry-Anmeldung und Push.
|
||||
- Die im Workflow eingebettete Veröffentlichungsentscheidung wurde mit
|
||||
17 Fällen ausgeführt: geschützte Branches und Release-Tags, manueller Start,
|
||||
andere Standardbranchnamen, Pull Requests, ungeschützte Refs, ungültige Tags
|
||||
und Shell-Sonderzeichen im Ref-Namen.
|
||||
- `actionlint` 1.7.12 prüfte den Workflow mit genau einer ausgenommenen
|
||||
Schema-Meldung: Die aktuelle Version kennt `concurrency.queue` noch nicht.
|
||||
`queue: max` wurde gegen die offizielle
|
||||
[GitHub-Syntax](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency)
|
||||
geprüft; die fehlende Linter-Unterstützung ist in
|
||||
[actionlint #657](https://github.com/rhysd/actionlint/issues/657) dokumentiert.
|
||||
Weitere Linter-Befunde gab es nicht.
|
||||
- `node --check provisioner/static/app.js` und `docker compose config --quiet`
|
||||
mit gesetzten Beispielwerten für Image und öffentliche URL waren erfolgreich.
|
||||
- `ci/container.sh verify` lief mit GitHub-Umgebungsvariablen gegen den lokalen
|
||||
Linux-Docker-Dienst 29.7.2. amd64-Build und Container-Smoke-Test bestanden:
|
||||
vertrauenswürdiges TLS, Ablehnung eines fremden Zertifikats, Anmeldung,
|
||||
CSRF, Webdateien sowie Erhalt von Hosts und Sitzungen beim Dienstneustart.
|
||||
Das Skript erzeugte `build.env` und entfernte den temporären Image-Tag.
|
||||
|
||||
Ein tatsächlicher GitHub-Actions-Lauf einschließlich GHCR-Anmeldung,
|
||||
Registry-Push und Artefakt-Upload wurde bei dieser lokalen Prüfung nicht
|
||||
ausgeführt. Die Push-Fehlerpfade und Digest-Auswertung sind durch die
|
||||
CI-Skripttests abgedeckt; die Paketberechtigungen und aktiven Schutzregeln
|
||||
müssen im GitHub-Repository eingerichtet und beim ersten Lauf bestätigt werden.
|
||||
|
||||
## Browserprüfung
|
||||
|
||||
In Chromium wurden Anmeldung, alle neun Verwaltungsansichten, Hostanlage und
|
||||
Hostbearbeitung, Geheimnisreferenzen, Gruppentoken, ISO-Registrierung und Buildbefehl,
|
||||
Modulvorlagen, Skript- und Profilveröffentlichung, Installationsvorschau,
|
||||
Freigabe, Abbruch, Laufabgleich, Benutzeranlage und Leserrechte durchgespielt.
|
||||
Die mobile Ansicht bei 390 Pixeln wurde ebenfalls geprüft. Es traten keine
|
||||
JavaScript- oder CSP-Fehler auf. Die separate Testkonfiguration verwendete
|
||||
temporäre Daten und deaktivierte die Vieraugenregel für den Formulartest mit
|
||||
einem Administrator; die Vieraugenregel wurde in den API-Tests geprüft.
|
||||
|
||||
## API-Artefakt
|
||||
|
||||
[openapi.json](openapi.json) wird über `create_app(Settings(...)).openapi()` aus
|
||||
dem aktuellen Anwendungscode erzeugt. Der Export verwendet ausschließlich
|
||||
temporäre Testverzeichnisse, keine produktiven Benutzer oder Schlüssel. Er
|
||||
enthält Schemas und Endpunktbeschreibungen, keine Datenbankinhalte, Sessions oder
|
||||
Geheimniswerte. Bei Änderungen an den API-Modellen muss der Export erneuert werden.
|
||||
Reference in New Issue
Block a user