diff --git a/README.md b/README.md new file mode 100644 index 0000000..af1e990 --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +# 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:///flatpak/...` - bereits vorhanden +- `https:///dnf/...` - dieser Stack + +## Funktionsprinzip + +Der `dnf-mirror`-Container laeuft dauerhaft und fuehrt alle +`SYNC_INTERVAL_SECONDS` (Default 6h) folgenden Zyklus aus: + +0. **packages.yaml aktualisieren**: `git fetch`/`reset --hard` gegen das in + `SOURCES_GIT_URL` konfigurierte Git-Repo, das eine Datei `packages.yaml` + enthaelt (siehe `sources-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.yaml` listet Fremdrepos mit ihrer URL und genau + den Paketnamen, die daraus geholt werden sollen, z.B.: + ```yaml + 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: + +1. **`dnf download`** holt aus dem Fremdrepo genau die unter `packages:` + gelisteten Paketnamen (keine automatische Abhaengigkeitsaufloesung, keine + sonstigen Pakete aus diesem Repo - siehe `sources-repo-template/README.md`). +2. **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 in `packages.yaml` eintraegt). Ausnahmen lassen sich im + `force_include`-Feld von `packages.yaml` eintragen (z.B. wenn eine + Anwendung eine neuere Version einer Bibliothek braucht als Alma mitbringt). +3. **Umsignieren**: Die verbleibenden, tatsaechlich zu spiegelnden Pakete + werden mit `rpmsign --resign` komplett 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. +4. **Metadaten bauen**: `createrepo_c` fuer die eigentlichen Repo-Metadaten, + `appstream-builder` fuer AppStream-Katalogdaten, damit die Anwendungen mit + Name/Icon/Beschreibung in **Discover (GNOME Software)** auftauchen statt + nur per `dnf`/`pkcon` auf der Kommandozeile sichtbar zu sein. +5. `repomd.xml` wird zusaetzlich separat signiert (`repomd.xml.asc`), damit + Clients `repo_gpgcheck=1` nutzen koennen (schuetzt auch die Metadaten + selbst, nicht nur die einzelnen Pakete). +6. Eine fertige Client-`.repo`-Datei wird nach `/srv/dnf/.repo` + geschrieben und ist unter `https:///dnf/.repo` abrufbar. + +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 + +```sh +# 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, 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:///dnf/RPM-GPG-KEY-mirror` - der Public Key des Spiegels +- `https:///dnf/vscode.repo` - fertige Repo-Datei fuer Clients +- `https:///dnf/vscode/` - das eigentliche Repository + +## 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: + +```sh +./client/bootstrap.sh software.example.org vscode +``` + +Das importiert den Mirror-GPG-Key und legt die `.repo`-Datei ab. 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**: `appstream-builder` (Paket `appstream-glib`, aus + EPEL) extrahiert 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. +- **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_include` in `packages.yaml` nutzen. +- **Keine Abhaengigkeitsaufloesung**: `packages.yaml` listet die zu + spiegelnden Pakete explizit; `dnf download` holt 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 in + `packages:` aufgenommen werden. +- **GPG-Key-Ablage**: Der Signing-Key liegt (statt in einem anonymen Docker- + Volume) im Ordner `./gpg` direkt neben der `docker-compose.yml` - dadurch + laesst er sich mit normalen Host-Mitteln sichern/backupen/einschraenken. + Der Container startet dafuer intern kurz als root, chownt `./gpg` auf den + unprivilegierten `mirror`-User und wechselt dann per `setpriv` dauerhaft zu + diesem (siehe `mirror/entrypoint.sh`) - der eigentliche Sync inkl. `git`/ + `dnf`/`gpg` laeuft 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 in `run-as-mirror.sh` fuer die Schluesselerzeugung + vorbereitet, automatisches Signieren mit passphrasegeschuetztem Key ist + aber NICHT fertig verdrahtet (braucht zusaetzlich `gpg-preset-passphrase` + o.ae.) - fuer den unbeaufsichtigten Betrieb wird die leere Passphrase + empfohlen. +- **Git-Zugriff**: `SOURCES_GIT_URL` wird 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 einen `listen 443 ssl` Server-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. +- 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 + up` sollte 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-Image mit dnf-plugins-core, createrepo_c, + rpm-sign, 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 -> dnf + download je Fremdrepo -> filtern -> signieren -> + 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) +``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d60395a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +services: + dnf-mirror: + build: ./mirror + restart: unless-stopped + environment: + MIRROR_DOMAIN: ${MIRROR_DOMAIN} + GPG_NAME: ${GPG_NAME} + GPG_EMAIL: ${GPG_EMAIL} + GPG_PASSPHRASE: ${GPG_PASSPHRASE} + SYNC_INTERVAL_SECONDS: ${SYNC_INTERVAL_SECONDS:-21600} + SOURCES_GIT_URL: ${SOURCES_GIT_URL} + SOURCES_GIT_REF: ${SOURCES_GIT_REF:-main} + SOURCES_GIT_SSH_KEY: ${SOURCES_GIT_SSH_KEY:-} + ALMA_RELEASEVER: ${ALMA_RELEASEVER:-9} + ALMA_BASEARCH: ${ALMA_BASEARCH:-x86_64} + ALMA_BASELINE_REPOIDS: ${ALMA_BASELINE_REPOIDS:-baseos,appstream,extras,crb,epel} + volumes: + - ./mirror/config/alma-baseline.repo.d:/etc/mirror/alma-baseline.repo.d:ro + - ./git-ssh:/run/secrets/git-ssh:ro + - ./gpg:/var/lib/mirror-gpg + - dnf-repo-data:/srv/dnf + - mirror-cache:/var/cache/mirror + # kein Port-Publish: der Container schreibt nur in das Volume, das nginx + # ausliefert. Braucht ausgehenden Internetzugriff zu den Upstream-Repos + # UND zum Git-Repo mit der Paketliste (SOURCES_GIT_URL), das er vor jedem + # Sync-Lauf neu abruft. + + nginx: + image: nginx:1.27-alpine + restart: unless-stopped + depends_on: + - dnf-mirror + ports: + - "80:80" + # - "443:443" # TLS: siehe README (eigene Zertifikate mounten oder + # vorgeschalteten Reverse Proxy/Load Balancer nutzen) + volumes: + - dnf-repo-data:/usr/share/nginx/html/dnf:ro + - ./nginx/conf.d:/etc/nginx/conf.d:ro + +volumes: + dnf-repo-data: + mirror-cache: