# Spiegelserver: Flatpak + DNF unter einer Domain Ein Compose-Stack, zwei kuratierte Spiegel, ein Webserver, ein Signing-Key: | Pfad | Inhalt | Container | |------|--------|-----------| | `https:///flatpak/` | gefilterter Flathub-Spiegel (OSTree) | `flatpak-mirror` | | `https:///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 ' 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:///flatpak/.flatpakrepo` | fertige Remote-Definition inkl. Key | | `https:///flatpak/.gpg` | Public Key (binaer, fuer OSTree) | | `https:///dnf/.repo` | die eine `.repo`-Datei fuer Clients | | `https:///dnf/RPM-GPG-KEY-mirror` | Public Key (ASCII-armored, fuer RPM) | ## Clients anbinden ```sh # Flatpak flatpak remote-add --if-not-exists company-mirror \ https:///flatpak/company-mirror.flatpakrepo # DNF rpm --import https:///dnf/RPM-GPG-KEY-mirror curl -o /etc/yum.repos.d/company-mirror.repo \ https:///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//x86_64/` 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 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.