11 KiB
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
./gpghereingereicht. Clients muessen damit nur einem Key vertrauen, egal ob Flatpak oder RPM. - Dasselbe Git-Repo mit den Freigabelisten:
flatpaks.yamlfuer den Flatpak-Spiegel,packages.yamlfuer den DNF-Spiegel. - Denselben nginx und damit dieselbe Domain und dasselbe TLS-Zertifikat.
Erste Einrichtung
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:
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
rpmsignnochflatpak build-signsignieren. ./gpgwird beiden Containern read-only nach/run/secrets/gpggemountet. Jeder Container kopiert das Homedir beim Start in sein eigenesGNUPGHOME(/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../gpgauf 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_ed25519legen (chmod 600) undSOURCES_GIT_SSH_KEY=/run/secrets/git-ssh/id_ed25519setzen.
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
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
# 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:
docker compose restart flatpak-mirror # bzw. dnf-mirror
Funktionsprinzip
Flatpak-Spiegel
flatpaks.yamlaus Git holen.- Fuer jede gelistete App den Ref
app/<id>/x86_64/<branch>perostree pull --mirrorvon Flathub holen. - Aus der
metadatades gepullten Refs die benoetigte Runtime auslesen und rekursiv mitspiegeln (App -> Runtime -> Basis-Runtime). - Jeden Ref mit dem Spiegel-Key neu signieren (
flatpak build-sign, fuerruntime/-Refs mit--runtime- ohne das sucht flatpak die ID unterapp/und die Runtime bliebe unsigniert). - Refs loeschen, die weder freigegeben noch als Abhaengigkeit noetig sind.
- Summary + Static Deltas erzeugen und signieren;
.flatpakrepound Public Key ins Repo schreiben.
DNF-Spiegel
packages.yamlaus Git holen.- Pro Fremdrepo per
dnf downloadgenau die dort gelisteten Paketnamen holen (keine automatische Abhaengigkeitsaufloesung). - Filtern: Pakete, deren Name AlmaLinux selbst liefert
(
ALMA_BASELINE_REPOIDS), werden verworfen - die Clients loesen sie aus ihren eigenen Alma-Repos auf. Ausnahmen ueberforce_include. - Umsignieren mit dem Spiegel-Key (
rpmsign --resign), in einen privaten Cache pro Fremdrepo. - Alle Fremdrepos zu einem oeffentlichen Repo zusammenfuehren - Clients binden nur dieses eine ein.
createrepo_c+ AppStream-Katalog (appstreamcli compose, fuer Discover/GNOME Software),repomd.xmlseparat signieren (repo_gpgcheck=1), Client-.reposchreiben.
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:
gpg --homedir ./gpg --batch --list-secret-keys --keyid-format=long
- Kommt hier
secmit einem Fingerprint: genau diesen (ohne Leerzeichen) alsGPG_KEY_IDin die.enveintragen, danndocker compose up -d. - Kommt nur
pubund keinsec: 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: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> passwdund 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_PORTauf 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.xmlbleiben perdnf/pkconinstallierbar, erscheinen aber ohne Katalogeintrag in Discover. Reine CLI-Pakete lassen sich ueberconsole_apps:inpackages.yamltrotzdem in den Katalog aufnehmen. Der DNF-Teil ist auf AlmaLinux 10+ ausgelegt (appstreamcli composeausappstream-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 upsollte beobachtet werden.