DNF-Spiegelserver fuer Pakete ohne Flatpak
Ergaenzt den bestehenden Flathub-Filter-Spiegel um einen zweiten Weg fuer Anwendungen, die es nur als RPM gibt. Beide sollen unter einer Domain laufen:
https://<domain>/flatpak/...- bereits vorhandenhttps://<domain>/dnf/...- dieser Stack
Funktionsprinzip
Der dnf-mirror-Container laeuft dauerhaft und fuehrt alle
SYNC_INTERVAL_SECONDS (Default 6h) folgenden Zyklus aus:
- packages.yaml aktualisieren:
git fetch/reset --hardgegen das inSOURCES_GIT_URLkonfigurierte Git-Repo, das eine Dateipackages.yamlenthaelt (siehesources-repo-template/). Das passiert vor jedem Sync-Lauf - neue/geaenderte Eintraege in diesem separaten Repo wirken also spaetestens beim naechsten automatischen Lauf, ganz ohne Neubau/Neustart des Containers.packages.yamllistet Fremdrepos mit ihrer URL und genau den Paketnamen, die daraus geholt werden sollen, z.B.:repos: - id: vscode baseurl: https://packages.microsoft.com/yumrepos/vscode gpgkey: https://packages.microsoft.com/keys/microsoft.asc packages: - code
Danach, pro dort definiertem Fremdrepo, zunaechst in einen PRIVATEN, internen Zwischenspeicher (noch nicht oeffentlich sichtbar):
dnf downloadholt aus dem Fremdrepo genau die unterpackages:gelisteten Paketnamen (keine automatische Abhaengigkeitsaufloesung, keine sonstigen Pakete aus diesem Repo - siehesources-repo-template/README.md).- Filtern: Jedes heruntergeladene Paket wird trotzdem gegen die
Paketnamen geprueft, die AlmaLinux selbst liefert (BaseOS/AppStream/
Extras/CRB/EPEL - konfigurierbar ueber
ALMA_BASELINE_REPOIDS). Ist der Name dort vorhanden, wird das Paket verworfen: die Clients haben ihre Alma-Repos ja selbst schon eingebunden und loesen es von dort auf (Schutz gegen versehentliches Doppelspiegeln, falls jemand faelschlich ein Alma-Paket inpackages.yamleintraegt). Ausnahmen lassen sich imforce_include-Feld vonpackages.yamleintragen (z.B. wenn eine Anwendung eine neuere Version einer Bibliothek braucht als Alma mitbringt). - Umsignieren: Die verbleibenden, tatsaechlich zu spiegelnden Pakete
werden mit
rpmsign --resignkomplett neu signiert - die Original- Herstellersignatur wird ersetzt. Dadurch brauchen Clients nur einen GPG-Key (den des Spiegelservers), egal von wie vielen verschiedenen Herstellern die gespiegelten Pakete urspruenglich stammen.
Erst danach, EINMALIG ueber alle Fremdrepos zusammen:
- Zusammenfuehren: Die signierten Pakete aller Fremdrepos werden zu
einem gemeinsamen, oeffentlichen Repo gemergt (
rsync --deleteueber alle privaten Zwischenspeicher hinweg in/srv/dnf). Ein Fremdrepo, das in einem Sync-Lauf mal nicht erreichbar war, behaelt einfach seinen letzten erfolgreichen Stand im gemeinsamen Repo; wird ein Fremdrepo oder ein einzelnes Paket auspackages.yamlentfernt, verschwindet es beim naechsten Lauf auch aus dem gemeinsamen Repo. - Metadaten bauen:
createrepo_cfuer die eigentlichen Repo-Metadaten des gemeinsamen Repos, dazu AppStream-Katalogdaten (appstream-builderbzw.appstreamcli compose, je nachdem was im Image verfuegbar ist - siehe unten), damit die Anwendungen mit Name/Icon/Beschreibung in Discover (GNOME Software) auftauchen statt nur perdnf/pkconauf der Kommandozeile sichtbar zu sein. repomd.xmlwird zusaetzlich separat signiert (repomd.xml.asc), damit Clientsrepo_gpgcheck=1nutzen koennen (schuetzt auch die Metadaten selbst, nicht nur die einzelnen Pakete).- Eine fertige Client-
.repo-Datei wird nach/srv/dnf/$MIRROR_REPO_ID.repogeschrieben und ist unterhttps://<domain>/dnf/$MIRROR_REPO_ID.repoabrufbar - das ist das einzige Repo, das Clients einbinden muessen.
Der nginx-Container bekommt das Ausgabe-Volume read-only gemountet und
liefert es unter /dnf/ aus. Er baut keine Pakete, sondern ist nur fuer den
Zugriff von aussen zustaendig.
Erste Einrichtung
# 1. Paketliste: sources-repo-template/packages.yaml (angepasst) in ein
# EIGENES, separates Git-Repo pushen (siehe sources-repo-template/README.md)
cp sources-repo-template/packages.yaml /pfad/zum/eigenen/git-repo/packages.yaml
# Inhalt pruefen/anpassen (eigene Fremdrepos + Paketnamen eintragen), dann
# committen+pushen ins neue Git-Repo
# 2. Compose-Stack konfigurieren
cp .env.example .env
# .env anpassen: mindestens MIRROR_DOMAIN, MIRROR_REPO_ID, GPG_NAME, GPG_EMAIL,
# SOURCES_GIT_URL
# 3. Bei SSH-Zugriff aufs Git-Repo: Deploy-Key (nur Lesezugriff) ablegen
cp /pfad/zum/deploy-key ./git-ssh/id_ed25519
chmod 600 ./git-ssh/id_ed25519
docker compose up -d --build
docker compose logs -f dnf-mirror # ersten Sync-Lauf beobachten
Der ./gpg-Ordner neben der docker-compose.yml wird beim ersten Start vom
Container selbst befuellt (GPG-Schluesselpaar) - dort ist nichts manuell
vorzubereiten, aber der Ordner sollte auf dem Host entsprechend geschuetzt
(nicht world-readable) und gesichert/gebackupt werden: geht er verloren,
muessen alle Clients den neuen Public Key neu importieren.
Nach dem ersten Lauf:
https://<domain>/dnf/RPM-GPG-KEY-mirror- der Public Key des Spiegelshttps://<domain>/dnf/company-mirror.repo(bzw.$MIRROR_REPO_ID.repo) - die eine Repo-Datei, die Clients einbindenhttps://<domain>/dnf/- das gemeinsame Repository selbst, enthaelt die Pakete aus allen inpackages.yamlkonfigurierten Fremdrepos
Neue Anwendungen spiegeln
Fuer jede Anwendung ohne Flatpak einen Eintrag unter repos: in der
packages.yaml im separaten Paketlisten-Git-Repo ergaenzen (Fremdrepo-
URL + die gewuenschten Paketnamen unter packages:), committen, pushen - der
Container zieht das beim naechsten Sync-Lauf automatisch. Kein Zugriff auf
den Docker-Host/Compose-Stack noetig, das kann also z.B. per Merge-Request-
Review an ein anderes Team delegiert werden.
Clients anbinden
Einmalig (per Ansible/Kickstart/Intune-Skript/Image-Build - vermutlich schon Teil eurer bestehenden Entra-ID-Provisioning-Pipeline), z.B. mit dem mitgelieferten Skript:
./client/bootstrap.sh software.example.org
Das importiert den Mirror-GPG-Key und legt die eine .repo-Datei ab -
Clients sehen darin von Anfang an alle gespiegelten Anwendungen aus allen
konfigurierten Fremdrepos, ganz ohne pro Anwendung ein weiteres Repo
einbinden zu muessen. Rootlose Installation/Update/Deinstallation laeuft
danach ueber die bereits bestehenden Polkit-Regeln fuer PackageKit wie
gewohnt - hier ist nichts zusaetzlich noetig.
Bekannte Einschraenkungen
- AppStream/Discover: Beide Werkzeuge extrahieren AppStream-Metadaten aus
Paketen, die selbst
.metainfo.xml/.desktop-Dateien mitbringen. Bringt eine Anwendung so etwas nicht mit, erscheint sie trotzdem installierbar, aber ohne Katalogeintrag (Name/Icon/Beschreibung) in Discover - nur per Suche nach dem Paketnamen. Das ist eine Eigenschaft des Quellpakets, keine Konfigurationsluecke.- AlmaLinux 9:
appstream-builder(Paketappstream-glib, aus EPEL) liest die.rpm-Dateien direkt ein - der laenger etablierte, gut erprobte Weg. - AlmaLinux 10:
appstream-glibist dort (Stand jetzt) weder im AlmaLinux- noch im EPEL-Repo paketiert. Stattdessen nutztsync.shappstreamcli compose(Paketeappstream+appstream-compose- Letzteres ist ein eigenes Subpaket, ohne dasappstreamcli composezur Laufzeit mit "AppStream Compose binary ... was not found" scheitert - beide direkt aus AlmaLinux 10). Da dieses Kommando anders alsappstream-builderauf einem Verzeichnisbaum statt auf.rpm-Dateien arbeitet, entpacktsync.shvorher die relevanten Pfade (usr/share/{applications,metainfo,icons,pixmaps}) aus allen Paketen in so einen Baum. Dieser Weg ist neuer/weniger erprobt als der EL9-Pfad - nach der ersten echten Nutzungdocker compose logs dnf-mirrorund die generierten Metadaten in Discover pruefen. - jpackage-basierte Java/JavaFX-Apps (u.a. XPipe): Diese legen ihre
.desktop-Datei typischerweise NICHT unter/usr/share/applications/ab, sondern direkt im eigenen Installationsverzeichnis (z.B./opt/xpipe/xpipe.desktop) und verschieben sie erst per%post-Skript dorthin - das siehtsync.shbeim reinen Lesen des Pakets nicht. Derappstreamcli compose-Pfad (AlmaLinux 10) durchsucht deshalb zusaetzlich das gesamte Paket nach.desktop/.metainfo.xml/.appdata.xml-Dateien ausserhalb der Standardpfade und holt genau diese gezielt nach. Derappstream-builder-Pfad (AlmaLinux 9) hat diesen Zusatzschritt aktuell NICHT - dort fehlt so einer Anwendung ggf. weiterhin der Katalogeintrag. - Fehlende
.metainfo.xml+<pkgname>: Hat eine Anwendung nur eine.desktop-Datei, aber keine.metainfo.xml(der Normalfall bei unabhaengigen Drittanbieter-RPMs), erzeugtsync.shselbst eine minimale Ersatz-Metainfo daraus (Name/Beschreibung/Icon/Kategorien aus der.desktop-Datei).appstreamcli compose(AppStream 1.0+) verlangt dafuer zwingend eine<description>, sonst schlaegt die betroffene Komponente mitdescription-missingfehl. Ausserdem braucht JEDE Komponente - auch eine echte, vom Hersteller mitgelieferte.metainfo.xml- ein<pkgname>-Element, damit PackageKit sie einem installierbaren RPM zuordnen kann; Upstream-Metainfo-Dateien haben das so gut wie nie (kennen den Distro-Paketnamen nicht),sync.shergaenzt es deshalb automatisch beim Einsammeln der Dateien. - Ist keins von beiden installiert, schlaegt die Erzeugung fehl, oder
bringt das Paket auch an keinem dieser Orte eine
.desktop-/AppStream- Datei mit, bleiben Pakete ganz normal perdnf/pkconinstallierbar, erscheinen aber ohne Katalogeintrag in Discover (siehe Log-Warnung).
- AlmaLinux 9:
- PackageKit-Cache auf dem Client:
dnf makecacheaktualisiert nur den Cache des interaktivendnf-Kommandos. PackageKit (und damit Discover) haelt einen davon getrennten eigenen Metadaten-Cache und aktualisiert ihn per Default nicht zuverlaessig von selbst zeitnah.client/bootstrap.shruft deshalb zusaetzlichpkcon refresh -c 1auf - ohne das kann ein neu eingebundenes Repo (oder neu hinzugekommene Pakete darin) in Discover trotz korrekter AppStream-Daten verzoegert oder gar nicht auftauchen. - Namensbasierter Abgleich: Paket X wird uebersprungen, sobald ein Paket
gleichen Namens in Alma existiert - unabhaengig von der Version. Falls eine
gespiegelte Anwendung eine neuere Version einer Alma-Bibliothek als
Abhaengigkeit braucht, dafuer
force_includeinpackages.yamlnutzen. - Keine Abhaengigkeitsaufloesung:
packages.yamllistet die zu spiegelnden Pakete explizit;dnf downloadholt genau diese Namen aus dem jeweiligen Fremdrepo, ohne fehlende Abhaengigkeiten automatisch nachzuladen. Fehlt einer gespiegelten Anwendung eine Bibliothek, die weder aus Alma/EPEL noch aus demselben Fremdrepo kommt, muss sie explizit mit inpackages:aufgenommen werden. - Ein gemeinsames Repo, ein gemeinsamer Namensraum: Alle Fremdrepos aus
packages.yamlwerden zu einem einzigen Repo zusammengefuehrt. Zwei verschiedene Fremdrepos mit einem gleichnamigen Paket (gleicher .rpm-Dateiname) landen deshalb im selben Speicherplatz - das zuletzt gesyncte gewinnt. Bei der Zusammenstellung vonpackages.yamlalso darauf achten, dass sich die gewaehlten Anwendungen nicht ueberschneiden. - GPG-Key-Ablage: Der Signing-Key liegt (statt in einem anonymen Docker-
Volume) im Ordner
./gpgdirekt neben derdocker-compose.yml- dadurch laesst er sich mit normalen Host-Mitteln sichern/backupen/einschraenken. Der Container startet dafuer intern kurz als root, chownt./gpgauf den unprivilegiertenmirror-User und wechselt dann persetprivdauerhaft zu diesem (siehemirror/entrypoint.sh) - der eigentliche Sync inkl.git/dnf/gpglaeuft nie als root. - GPG-Passphrase: Der Signing-Key wird standardmaessig ohne Passphrase
erzeugt, damit der Container vollautomatisch signieren kann. Der Schutz
liegt dann in den Dateirechten von
./gpg(nicht world-readable lassen!). Eine Passphrase ist inrun-as-mirror.shfuer die Schluesselerzeugung vorbereitet, automatisches Signieren mit passphrasegeschuetztem Key ist aber NICHT fertig verdrahtet (braucht zusaetzlichgpg-preset-passphraseo.ae.) - fuer den unbeaufsichtigten Betrieb wird die leere Passphrase empfohlen. - Git-Zugriff:
SOURCES_GIT_URLwird vor jedem Sync-Lauf gefetcht. Schlaegt das fehl (Netzwerk, Auth), laeuft der Sync mit der letzten erfolgreich geholten Paketliste weiter (nicht beim allerersten Lauf - dort bricht der Sync-Zyklus ohne erreichbares Git-Repo komplett ab). Nur ein Lesezugriff (Deploy-Key/Read-only-Token) ist noetig. - TLS: Der
nginx-Service ist bewusst schlank gehalten (Port 80). Fuer TLS entweder Zertifikate mounten und einenlisten 443 sslServer-Block ergaenzen, oder - passend zum Wunsch "eine Domain fuer beide Repos" - diesen Stack hinter einem bereits vorhandenen Reverse Proxy/Loadbalancer betreiben, der auch den Flatpak-Spiegel terminiert. - Upgrade von einer aelteren Version dieses Stacks (mit einem Repo PRO
Fremdrepo unter
/dnf/<id>/): Das Ausgabeformat hat sich grundlegend geaendert (jetzt ein gemeinsames Repo direkt unter/dnf/). Vor dem ersten Start mit dieser Version dasdnf-repo-data-Volume einmalig leeren (docker compose down+ Volume-Inhalt loeschen), damit keine alten<id>/-Verzeichnisse und<id>.repo-Dateien liegen bleiben, und anschliessend allen Clients die neue, eine.repo-Datei ausrollen. - Dieser Aufbau wurde nicht gegen echte Internet-Upstreams end-to-end
getestet (keine Docker-Umgebung/Internetzugriff in dieser Session
verfuegbar) - Skripte sind syntaktisch geprueft, der erste
docker compose upsollte aber mit echten Repo-Definitionen beobachtet werden.
Verzeichnisstruktur
docker-compose.yml
.env.example
gpg/ (leer, wird vom Container befuellt) - Signing-Key,
auf dem Host sichern/schuetzen
git-ssh/ optional: SSH-Deploy-Key fuers Paketlisten-Git-Repo
mirror/
Dockerfile AlmaLinux-10-Image mit dnf-plugins-core, createrepo_c,
rpm-sign, appstream+appstream-compose/appstream-glib,
git, python3-pyyaml, jq
entrypoint.sh laeuft als root: chownt ./gpg, wechselt per setpriv
zu "mirror"
run-as-mirror.sh laeuft als "mirror": GPG-Key-Erzeugung/-Export,
Sync-Schleife
sync.sh Kernlogik: packages.yaml aus Git holen -> je
Fremdrepo dnf download -> filtern -> signieren
(privater Cache) -> alle Fremdrepos zu EINEM Repo
zusammenfuehren -> Metadaten -> Client-.repo
config/
alma-baseline.repo.d/ optional: eigene Baseline-Repos fuer den Abgleich
sources-repo-template/ Vorlage fuer das SEPARATE Git-Repo mit der
Paketliste (packages.yaml)
nginx/
conf.d/repo.conf liefert /dnf/... aus, Platzhalter fuer /flatpak/...
client/
bootstrap.sh einmaliges Client-Onboarding (Key-Import + .repo)