Initial commit
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# Spiegelserver: Flatpak + DNF unter einer Domain
|
||||
|
||||
Ein Compose-Stack, zwei kuratierte Spiegel, ein Webserver, ein Signing-Key:
|
||||
|
||||
| Pfad | Inhalt | Container |
|
||||
|------|--------|-----------|
|
||||
| `https://<domain>/flatpak/` | gefilterter Flathub-Spiegel (OSTree) | `flatpak-mirror` |
|
||||
| `https://<domain>/dnf/` | RPM-Spiegel fuer alles ohne Flatpak | `dnf-mirror` |
|
||||
|
||||
Beide Sync-Container schreiben nur in ihr eigenes Volume und haben keinen
|
||||
Port nach aussen. Der gemeinsame `nginx` bekommt beide Volumes read-only
|
||||
gemountet und liefert sie unter je einem Pfad aus.
|
||||
|
||||
Was sich beide teilen:
|
||||
|
||||
- **Denselben GPG-Signing-Key** - er wird extern erzeugt und beiden
|
||||
Containern ueber dasselbe Verzeichnis `./gpg` hereingereicht. Clients
|
||||
muessen damit nur *einem* Key vertrauen, egal ob Flatpak oder RPM.
|
||||
- **Dasselbe Git-Repo** mit den Freigabelisten: `flatpaks.yaml` fuer den
|
||||
Flatpak-Spiegel, `packages.yaml` fuer den DNF-Spiegel.
|
||||
- **Denselben nginx** und damit dieselbe Domain und dasselbe TLS-Zertifikat.
|
||||
|
||||
## Erste Einrichtung
|
||||
|
||||
```sh
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 1. Signing-Key bereitstellen
|
||||
|
||||
Der Key wird **extern** erzeugt (nicht mehr vom Container) und als
|
||||
GnuPG-Homedir in den Ordner `./gpg` gelegt. Zum Beispiel auf einer
|
||||
Admin-Maschine:
|
||||
|
||||
```sh
|
||||
export GNUPGHOME=$(mktemp -d)
|
||||
gpg --batch --passphrase '' --quick-generate-key 'Company Mirror <it@example.com>' rsa4096 sign never
|
||||
gpg --list-secret-keys --keyid-format=long # Fingerprint notieren
|
||||
rsync -a "$GNUPGHOME/" /pfad/zum/stack/gpg/
|
||||
```
|
||||
|
||||
Dann in der `.env` `GPG_KEY_ID` auf den Fingerprint setzen.
|
||||
|
||||
- Der Key darf **keine Passphrase** haben, sonst kann unbeaufsichtigt weder
|
||||
`rpmsign` noch `flatpak build-sign` signieren.
|
||||
- `./gpg` wird beiden Containern **read-only** nach `/run/secrets/gpg`
|
||||
gemountet. Jeder Container kopiert das Homedir beim Start in sein eigenes
|
||||
`GNUPGHOME` (`/etc/mirror-gpg`) - gpg schreibt naemlich in seinem Homedir
|
||||
(trustdb, random_seed, Lockfiles), und so bleibt das Original unveraendert
|
||||
und die beiden Container kommen sich beim Signieren nicht ins Gehege.
|
||||
- `./gpg` auf dem Host nicht world-readable lassen (`chmod 700`) und
|
||||
**sichern/backupen**: geht der Key verloren, muessen alle Clients den neuen
|
||||
Public Key neu importieren - fuer beide Repos.
|
||||
|
||||
### 2. Freigabe-Git-Repo anlegen
|
||||
|
||||
Inhalt von `sources-repo-template/` (`flatpaks.yaml` + `packages.yaml`) in ein
|
||||
eigenes Git-Repo pushen, anpassen und in der `.env` `SOURCES_GIT_URL` /
|
||||
`SOURCES_GIT_REF` setzen. Fuer privaten Zugriff entweder
|
||||
|
||||
- **Token:** `SOURCES_GIT_USERNAME` + `SOURCES_GIT_TOKEN` (Read-only-PAT), oder
|
||||
- **SSH:** Deploy-Key nach `./git-ssh/id_ed25519` legen (`chmod 600`) und
|
||||
`SOURCES_GIT_SSH_KEY=/run/secrets/git-ssh/id_ed25519` setzen.
|
||||
|
||||
Das Token wird nie in die Git-URL eingebettet (URLs landen in Logs), sondern
|
||||
per `GIT_CONFIG_*`-Header uebergeben - es ist damit auch nicht per `ps aux`
|
||||
auf dem Host sichtbar. Im Container-Environment und in der `.env` steht es
|
||||
trotzdem im Klartext, Dateirechte entsprechend setzen.
|
||||
|
||||
### 3. Starten
|
||||
|
||||
```sh
|
||||
docker compose up -d --build
|
||||
docker compose logs -f flatpak-mirror dnf-mirror
|
||||
```
|
||||
|
||||
Der erste Lauf dauert je nach Freigabeliste laenger (Firefox/LibreOffice sind
|
||||
gross). Danach steht bereit:
|
||||
|
||||
| URL | Zweck |
|
||||
|-----|-------|
|
||||
| `https://<domain>/flatpak/<MIRROR_REPO_ID>.flatpakrepo` | fertige Remote-Definition inkl. Key |
|
||||
| `https://<domain>/flatpak/<MIRROR_REPO_ID>.gpg` | Public Key (binaer, fuer OSTree) |
|
||||
| `https://<domain>/dnf/<MIRROR_REPO_ID>.repo` | die eine `.repo`-Datei fuer Clients |
|
||||
| `https://<domain>/dnf/RPM-GPG-KEY-mirror` | Public Key (ASCII-armored, fuer RPM) |
|
||||
|
||||
## Clients anbinden
|
||||
|
||||
```sh
|
||||
# Flatpak
|
||||
flatpak remote-add --if-not-exists company-mirror \
|
||||
https://<domain>/flatpak/company-mirror.flatpakrepo
|
||||
|
||||
# DNF
|
||||
rpm --import https://<domain>/dnf/RPM-GPG-KEY-mirror
|
||||
curl -o /etc/yum.repos.d/company-mirror.repo \
|
||||
https://<domain>/dnf/company-mirror.repo
|
||||
pkcon refresh -c 1 # PackageKit/Discover hat einen eigenen Cache
|
||||
```
|
||||
|
||||
Beide Male ist es derselbe Key - er muss nur einmal als vertrauenswuerdig
|
||||
eingestuft werden.
|
||||
|
||||
## Freigaben aendern
|
||||
|
||||
`flatpaks.yaml` bzw. `packages.yaml` im Freigabe-Repo bearbeiten, committen,
|
||||
pushen. Beide Container ziehen das Repo vor jedem Sync-Lauf neu
|
||||
(`FLATPAK_SYNC_INTERVAL_SECONDS`, Default 15 Min.;
|
||||
`DNF_SYNC_INTERVAL_SECONDS`, Default 6 h). Fuer einen sofortigen Lauf:
|
||||
|
||||
```sh
|
||||
docker compose restart flatpak-mirror # bzw. dnf-mirror
|
||||
```
|
||||
|
||||
## Funktionsprinzip
|
||||
|
||||
### Flatpak-Spiegel
|
||||
|
||||
1. `flatpaks.yaml` aus Git holen.
|
||||
2. Fuer jede gelistete App den Ref `app/<id>/x86_64/<branch>` per
|
||||
`ostree pull --mirror` von Flathub holen.
|
||||
3. Aus der `metadata` des gepullten Refs die benoetigte Runtime auslesen und
|
||||
rekursiv mitspiegeln (App -> Runtime -> Basis-Runtime).
|
||||
4. Jeden Ref mit dem Spiegel-Key neu signieren (`flatpak build-sign`, fuer
|
||||
`runtime/`-Refs mit `--runtime` - ohne das sucht flatpak die ID unter
|
||||
`app/` und die Runtime bliebe unsigniert).
|
||||
5. Refs loeschen, die weder freigegeben noch als Abhaengigkeit noetig sind.
|
||||
6. Summary + Static Deltas erzeugen und signieren; `.flatpakrepo` und Public
|
||||
Key ins Repo schreiben.
|
||||
|
||||
### DNF-Spiegel
|
||||
|
||||
1. `packages.yaml` aus Git holen.
|
||||
2. Pro Fremdrepo per `dnf download` genau die dort gelisteten Paketnamen holen
|
||||
(keine automatische Abhaengigkeitsaufloesung).
|
||||
3. **Filtern:** Pakete, deren Name AlmaLinux selbst liefert
|
||||
(`ALMA_BASELINE_REPOIDS`), werden verworfen - die Clients loesen sie aus
|
||||
ihren eigenen Alma-Repos auf. Ausnahmen ueber `force_include`.
|
||||
4. **Umsignieren** mit dem Spiegel-Key (`rpmsign --resign`), in einen privaten
|
||||
Cache pro Fremdrepo.
|
||||
5. Alle Fremdrepos zu **einem** oeffentlichen Repo zusammenfuehren - Clients
|
||||
binden nur dieses eine ein.
|
||||
6. `createrepo_c` + AppStream-Katalog (`appstreamcli compose`, fuer
|
||||
Discover/GNOME Software), `repomd.xml` separat signieren
|
||||
(`repo_gpgcheck=1`), Client-`.repo` schreiben.
|
||||
|
||||
## Verzeichnisstruktur
|
||||
|
||||
```
|
||||
docker-compose.yml beide Spiegel + der EINE nginx
|
||||
.env.example gemeinsame Konfiguration beider Spiegel
|
||||
gpg/ extern erzeugter Signing-Key (GnuPG-Homedir),
|
||||
read-only in BEIDE Container gemountet
|
||||
git-ssh/ optional: SSH-Deploy-Key fuers Freigabe-Repo
|
||||
nginx/
|
||||
conf.d/repo.conf ein Server-Block: /flatpak/ und /dnf/
|
||||
flatpak-mirror/
|
||||
Dockerfile debian-slim mit ostree, flatpak, git, yq
|
||||
entrypoint.sh uebernimmt den gemeinsamen Key nach $GNUPGHOME
|
||||
gpg-import.sh Key-Uebernahme + Signier-Selbsttest (identische
|
||||
Kopie in dnf-mirror/, beide pflegen)
|
||||
sync.sh Pull + Signieren + Summary + Client-Dateien
|
||||
dnf-mirror/
|
||||
Dockerfile AlmaLinux 10 mit createrepo_c, rpm-sign, appstream
|
||||
entrypoint.sh uebernimmt den Key, droppt per setpriv auf "mirror"
|
||||
gpg-import.sh identische Kopie aus flatpak-mirror/
|
||||
run-as-mirror.sh Key pruefen/exportieren, Sync-Schleife
|
||||
sync.sh Download -> Filtern -> Signieren -> Merge -> Metadaten
|
||||
alma-baseline.repo.d/ optional: eigene Baseline-Repos fuer den Abgleich
|
||||
sources-repo-template/ Vorlage fuer das gemeinsame Freigabe-Git-Repo
|
||||
flatpaks.yaml
|
||||
packages.yaml
|
||||
```
|
||||
|
||||
## Fehlersuche: "kein geheimer Schluessel ... gefunden"
|
||||
|
||||
Beide Container brechen beim Start ab, wenn sie mit `GPG_KEY_ID` nicht
|
||||
signieren koennen. Die Meldung listet dann die Fingerprints, die im
|
||||
Schluesselbund tatsaechlich liegen - meistens passt einfach `GPG_KEY_ID` in
|
||||
der `.env` nicht dazu (Platzhalter aus `.env.example` stehengeblieben,
|
||||
Key-ID statt Fingerprint, Tippfehler).
|
||||
|
||||
Direkt auf dem Host nachsehen, was in `./gpg` liegt:
|
||||
|
||||
```sh
|
||||
gpg --homedir ./gpg --batch --list-secret-keys --keyid-format=long
|
||||
```
|
||||
|
||||
- Kommt hier **`sec`** mit einem Fingerprint: genau diesen (ohne Leerzeichen)
|
||||
als `GPG_KEY_ID` in die `.env` eintragen, dann `docker compose up -d`.
|
||||
- Kommt **nur `pub`** und kein `sec`: im Ordner liegt nur der oeffentliche
|
||||
Teil. `private-keys-v1.d/` muss mindestens eine `.key`-Datei enthalten -
|
||||
sonst den Key neu erzeugen bzw. den geheimen Teil nachliefern.
|
||||
- Kommt gar nichts: der Ordner ist kein GnuPG-Homedir, sondern enthaelt z.B.
|
||||
eine exportierte `.asc`-Datei. Dann einmalig importieren:
|
||||
```sh
|
||||
gpg --homedir ./gpg --import /pfad/zum/secret-key.asc
|
||||
```
|
||||
- Meldet der Container "Signieren schlaegt fehl": der Key hat eine
|
||||
Passphrase. Unbeaufsichtigtes Signieren braucht einen Key ohne Passphrase -
|
||||
`gpg --homedir ./gpg --edit-key <fpr> passwd` und leer lassen.
|
||||
|
||||
Der Ordner wird beiden Containern read-only hereingereicht; sie kopieren
|
||||
daraus nur das Schluesselmaterial (`pubring.kbx`, `private-keys-v1.d`,
|
||||
`trustdb.gpg`, ...). Agent-Sockets (`S.gpg-agent` & Co.) und Lock-Dateien,
|
||||
die vom gpg-agent auf dem Host stammen, koennen also gefahrlos darin liegen
|
||||
bleiben - sie werden nicht mitkopiert.
|
||||
|
||||
## Bekannte Einschraenkungen
|
||||
|
||||
- **TLS** terminiert dieser Stack nicht. Entweder Zertifikate in den
|
||||
nginx-Container mounten und einen `listen 443 ssl`-Block ergaenzen, oder -
|
||||
meist einfacher - `HTTP_PORT` auf z.B. 8080 legen und einen vorhandenen
|
||||
Reverse Proxy davorhaengen. Da beide Repos jetzt hinter *einem* nginx
|
||||
liegen, reicht dafuer ein Zertifikat und ein Upstream.
|
||||
- **Passphrase-geschuetzte Keys** sind nicht unterstuetzt (siehe oben).
|
||||
- **AppStream/Discover:** Anwendungen ohne `.desktop`/`.metainfo.xml` bleiben
|
||||
per `dnf`/`pkcon` installierbar, erscheinen aber ohne Katalogeintrag in
|
||||
Discover. Reine CLI-Pakete lassen sich ueber `console_apps:` in
|
||||
`packages.yaml` trotzdem in den Katalog aufnehmen. Der DNF-Teil ist auf
|
||||
AlmaLinux 10+ ausgelegt (`appstreamcli compose` aus `appstream-compose`).
|
||||
- **Namensbasierter Abgleich (DNF):** Ein Paket wird uebersprungen, sobald
|
||||
Alma ein gleichnamiges hat - unabhaengig von der Version. Braucht eine
|
||||
Anwendung eine neuere Version, hilft `force_include`.
|
||||
- **Ein gemeinsamer Namensraum (DNF):** Zwei Fremdrepos mit gleichnamigem
|
||||
Paket ueberschreiben sich im gemeinsamen Repo; das zuletzt gesyncte gewinnt.
|
||||
- **Git-Ausfall:** Schlaegt der Fetch fehl, laeuft der DNF-Sync mit der
|
||||
letzten erfolgreich geholten Liste weiter (nur beim allerersten Lauf bricht
|
||||
er ab). Der Flatpak-Sync bricht den Lauf ab und versucht es beim naechsten
|
||||
Intervall erneut.
|
||||
- **Migration aus den beiden Einzel-Stacks:** Die alten Volumes werden nicht
|
||||
weiterverwendet (andere Namen, andere Pfade im Container) - beide Spiegel
|
||||
bauen sich beim ersten Lauf neu auf. Da jetzt ausserdem mit einem *neuen,
|
||||
gemeinsamen* Key signiert wird, muessen die Clients ohnehin einmalig neu
|
||||
onboardet werden.
|
||||
- Der zusammengefuehrte Stack wurde nicht gegen echte Upstreams end-to-end
|
||||
getestet (keine Docker-/Internet-Umgebung in dieser Session) - die Skripte
|
||||
sind syntaktisch geprueft, der erste `docker compose up` sollte beobachtet
|
||||
werden.
|
||||
Reference in New Issue
Block a user