From 0355654d283263ac46550361e190f83570f0a446 Mon Sep 17 00:00:00 2001 From: "David M. Klein" <4+leander19961@noreply.localhost> Date: Wed, 26 Aug 2026 15:23:08 +0200 Subject: [PATCH] =?UTF-8?q?README.md=20gel=C3=B6scht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 327 ------------------------------------------------------ 1 file changed, 327 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index ed1547f..0000000 --- a/README.md +++ /dev/null @@ -1,327 +0,0 @@ -# RPM Mirror Sync - -`sync.sh` spiegelt einzeln benannte Pakete aus Drittanbieter-RPM-Repos in ein -lokales Repo, ohne dabei Pakete mitzuspiegeln, die AlmaLinux selbst schon -liefert (BaseOS, AppStream, Extras, CRB/"Extras for Enterprise"). Nur die in -der YAML-Liste explizit genannten Pakete bekommen AppStream-Metadaten und -werden damit in GNOME Software / Discover als installierbar angezeigt. - -## Voraussetzungen (auf dem Server, der das Skript ausführt) - -``` -dnf install -y dnf-plugins-core createrepo_c python3-pyyaml git -``` - -Das Skript muss als root laufen (schreibt kurzzeitig eine `.repo`-Datei nach -`/etc/yum.repos.d/`). - -## Konfiguration - -Alles über Umgebungsvariablen, z.B. in der systemd-Unit oder im Cron-Job: - -| Variable | Bedeutung | Default | -|---------------------|------------------------------------------------------------------|---------| -| `SOURCES_GIT_REPO` | Git-Repo mit der YAML-Liste (leer = lokale Datei verwenden) | – | -| `SOURCES_GIT_BRANCH` | Branch im Git-Repo | `main` | -| `SOURCES_YAML_FILE` | Pfad zur YAML-Datei (im Git-Repo oder lokal) | `sources.yaml` | -| `WORK_DIR` | Arbeitsverzeichnis (Downloads, Logs, Zwischenlisten) | `/var/cache/rpm-mirror-sync` | -| `REPO_BASE_DIR` | Zielverzeichnis der gespiegelten Repos | `/srv/repo` | -| `CLIENT_REPO_DIR` | Wo fertige `.repo`-Dateien für Clients abgelegt werden | `${REPO_BASE_DIR}/client-repos` | -| `CLIENT_BASE_URL` | URL, unter der `REPO_BASE_DIR` für Clients erreichbar ist | `http://mirror.example.local/repo` | -| `ALMA_REPO_IDS` | repoids der lokalen AlmaLinux-Repos (`dnf repolist`) | `baseos appstream extras crb` | - -**Wichtig:** `ALMA_REPO_IDS` muss zu den tatsächlichen repoids auf dem -Server passen (`dnf repolist` prüfen) — sonst funktioniert der -Ausschluss-Mechanismus nicht korrekt. - -## Ausführen - -```bash -export SOURCES_GIT_REPO=https://git.example.com/infra/rpm-sources.git -export CLIENT_BASE_URL=http://repo.internal.example.com/repo -./sync.sh -``` - -Danach liegt unter `REPO_BASE_DIR/pkgs/` das eine kombinierte Repo mit allen -Paketen aller Quellen (per Webserver auszuliefern) und unter -`CLIENT_REPO_DIR/mirror.repo` die eine fertige `.repo`-Datei, die auf die -Clients verteilt wird (z.B. via Ansible/Config-Management nach -`/etc/yum.repos.d/`). - -Am besten per systemd-Timer (z.B. täglich) statt Cron, damit Logs sauber in -`journalctl` landen. - -## Wie der Ausschluss von AlmaLinux-Abhängigkeiten funktioniert - -1. Für jedes Source-Repo wird zunächst abgefragt, welche Pakete *dieses* - Repo überhaupt bereitstellt (`dnf repoquery` nur gegen das Source-Repo). -2. Anschließend wird die komplette Abhängigkeitskette der gewünschten - Pakete aufgelöst — dabei sind sowohl das Source-Repo als auch die - lokalen AlmaLinux-Repos aktiv, damit die Auflösung überhaupt gelingt - (z.B. wenn ein Paket von `glibc` abhängt). -3. Von den heruntergeladenen RPMs wird alles verworfen, was nicht in der - Liste aus Schritt 1 steht — also alles, was tatsächlich aus einem - AlmaLinux-Repo kam. Übrig bleiben nur RPMs, die es ausschließlich im - Drittanbieter-Repo gibt. - -**Wichtig: "übrig bleiben" ist meist mehr als nur die Namen unter -`packages:`.** Wenn ein angefordertes Paket von weiteren Drittanbieter-Paketen -abhängt, die AlmaLinux nicht bereitstellt (z.B. bei .NET: `dotnet-host`, -`dotnet-hostfxr-*`, `dotnet-apphost-pack-*`, `aspnetcore-runtime-*`, ...), -werden die zwangsläufig mitgespiegelt — sonst schlägt `dnf install -dotnet-sdk-8.0` auf dem Client mit einer fehlenden Abhängigkeit fehl, die -nirgendwo sonst verfügbar wäre. Nur echte AlmaLinux-Pakete werden -ausgeschlossen, nicht Drittanbieter-Abhängigkeiten. Im Log ist das ab jetzt -pro Paket sichtbar: - -``` -[...] keeping (requested): dotnet-sdk-8.0-8.0.130-1.el10_2.x86_64 -[...] keeping (dependency): dotnet-host-10.0.11-1.el10_2.x86_64 -[...] Source 'dotnet': kept 26 package(s) - 6 explicitly requested + 20 pulled-in third-party dependencies (...) -``` - -### Sonderfall: `baseurl` zeigt auf ein AlmaLinux-eigenes Repo - -`dnf` "sucht" nie selbstständig bei Drittanbietern — es lädt ausschließlich -von der URL, die ihr unter `baseurl` eintragt. Zeigt diese URL zufällig auf -ein AlmaLinux-eigenes Repo (z.B. `.../AppStream/...`), ist also gar keine -externe Quelle im Spiel. Genau das ist bei .NET auf AlmaLinux 10 der Fall: -`dotnet-sdk`, `dotnet-runtime`, `dotnet-host` & Co. sind Teil des offiziellen -AppStream-Repos (anders als bei älteren Alma-/RHEL-Versionen mit separatem -Microsoft-Repo). - -Konsequenz: Die "requested"-Pakete sind hier technisch schon über das -normale, auf jedem Client ohnehin aktivierte AppStream-Repo installierbar — -der Ausschluss-Mechanismus (Schritt 1-3 oben) erkennt das nicht, weil er nur -gegen die *anderen* Alma-Repos (BaseOS, Extras, CRB) vergleicht, nicht gegen -die Quelle selbst. Ein normaler Mirror-Lauf würde hier also RPM-Inhalte -duplizieren, die AlmaLinux bereits liefert. - -**Für genau diesen Fall gibt es `metadata_only: true`** (pro Quelle in der -YAML, siehe `sources.example.yaml`): Es werden **keine RPMs** heruntergeladen, -verifiziert oder umsigniert — nur kuratierte AppStream-Metadaten für die -gelisteten Paketnamen werden generiert und veröffentlicht. Das tatsächliche -Installieren übernimmt `dnf` weiterhin ganz normal über das Repo, das das -Paket wirklich bereitstellt (bei AlmaLinux-eigenen Paketen also das -Standard-AppStream-Repo, das auf jeder Standardinstallation ohnehin aktiv -ist). Der Effekt: Discover zeigt nur eure kuratierte Auswahl statt des -kompletten (oft sehr großen) AppStream-Kataloges, ohne dass ihr Inhalte -dupliziert, die es schon gibt. - -**Voraussetzung:** Die Clients müssen das Repo, das die Pakete tatsächlich -bereitstellt, bereits aktiviert haben — bei Standard-AlmaLinux-Installationen -(BaseOS/AppStream/Extras/CRB aktiv) ist das automatisch der Fall. - -Für ein echtes Drittanbieter-Repo (Pakete, die AlmaLinux nirgends hat) lasst -ihr `metadata_only` einfach weg (Default: `false`) — dann läuft der volle -Mirror-Prozess wie oben beschrieben, inklusive Verifikation und Umsignieren. - -## Umsignieren mit einem eigenen GPG-Key - -Damit eure Clients nicht für jede gespiegelte Quelle einen eigenen GPG-Key -importieren müssen, signiert `sync.sh` jedes gespiegelte Paket auf einen -einzigen, mirror-eigenen Schlüssel um: - -1. Beim ersten Lauf wird — falls noch keiner unter `GPG_HOME` existiert — - einmalig ein GPG-Schlüsselpaar erzeugt (unattended, ohne Passphrase; das - ist für automatisierte Repo-Signierung üblich). Der öffentliche Teil wird - nach `REPO_BASE_DIR/RPM-GPG-KEY-mirror` exportiert. -2. Für jede Quelle wird — sofern `gpgkey:` in der YAML gesetzt ist — das - heruntergeladene RPM zunächst gegen den **originalen** Key des Upstream - geprüft (`rpmkeys --checksig`). Schlägt die Prüfung fehl, bricht das - Skript für diese Quelle ab (kein stillschweigendes Mirroring manipulierter - Pakete). Ist kein `gpgkey:` angegeben, wird eine Warnung geloggt und die - Prüfung übersprungen. -3. Danach wird die ursprüngliche Signatur entfernt und durch eine Signatur - mit dem mirror-eigenen Key ersetzt (`rpmsign --resign`). -4. Alle generierten Client-`.repo`-Dateien verweisen auf denselben Key - (`${CLIENT_BASE_URL}/RPM-GPG-KEY-mirror`) — unabhängig davon, wie viele - Quellen gespiegelt werden. - -**`gpgcheck` und `repo_gpgcheck`:** Die generierte `.repo`-Datei setzt beide -auf `1`: -- `gpgcheck=1` prüft beim Installieren die Signatur **jedes einzelnen RPM** - gegen den Mirror-Key. -- `repo_gpgcheck=1` prüft zusätzlich die Signatur der **Repo-Metadaten - selbst** (`repodata/repomd.xml.asc`, ein detached signature). Ohne das - könnte jemand mit Schreibzugriff auf den Webserver/die Leitung - theoretisch die Paketliste manipulieren (z.B. ein älteres, verwundbares - Paket als aktuell ausgeben oder Einträge entfernen), ohne dass eine - einzelne RPM-Signatur berührt würde — `repo_gpgcheck` schließt genau diese - Lücke. - -`sync.sh` signiert `repomd.xml` nach jedem Lauf neu (nachdem `createrepo_c` -und `modifyrepo_c` fertig sind, da beide die Datei noch verändern) mit -demselben Mirror-Key, den auch die Pakete tragen — Clients brauchen also -weiterhin nur den einen `RPM-GPG-KEY-mirror`. - -**Wichtig zur Aufbewahrung des privaten Schlüssels:** `GPG_HOME` -(Default: `${WORK_DIR}/gnupg`) enthält den privaten Signierschlüssel und darf -**niemals** über nginx/den Webserver erreichbar sein — im mitgelieferten -Docker-Setup liegt er bewusst auf dem `sync-cache`-Volume, das *nicht* in den -`nginx`-Container gemountet wird, nur `repo-data` (die öffentlichen Dateien) -ist das. Wer Lesezugriff auf `GPG_HOME` hat, kann Pakete als euer Mirror -signieren — entsprechend schützen (Backups, Zugriffsrechte). - -`GPG_HOME` muss auf persistentem Speicher liegen: wird es gelöscht, erzeugt -das Skript beim nächsten Lauf einen neuen Schlüssel, und alle Clients müssen -den neuen öffentlichen Key erneut importieren. - -### Revocation-Zertifikat - -Da der Mirror-Key mit `expire=never` erzeugt wird, gibt es kein automatisches -Ablaufdatum, über das ein kompromittierter Key von selbst "entschärft" würde. -Deshalb erzeugt `sync.sh` beim allerersten Lauf zusammen mit dem Schlüssel -auch ein **Revocation-Zertifikat** unter `GPG_HOME/revocation-cert.asc` — das -ist die einzige Möglichkeit, den Key später für ungültig zu erklären, falls -der private Schlüssel jemals kompromittiert wird. - -**Direkt nach dem ersten Start aus dem Container herausholen und offline -sichern:** - -```bash -docker compose cp syncer:/var/cache/rpm-mirror-sync/gnupg/revocation-cert.asc \ - ./mirror-key-revocation-cert.asc -``` - -(Pfad anpassen, falls ihr `WORK_DIR`/`GPG_HOME` in `.env` geändert habt.) -Das Zertifikat danach an einem Ort außerhalb des Docker-Hosts aufbewahren -(Passwort-Safe, Offline-Backup o.ä.) — nicht im selben Ordner wie der -private Key selbst, sonst bringt es im Kompromittierungsfall nichts. Das -Skript loggt den Hinweis beim ersten Lauf zusätzlich in `sync.log`. - -Kommt der Ernstfall, importiert ihr das Zertifikat auf einer Maschine mit -Zugriff auf `GPG_HOME` (`gpg --homedir ... --import mirror-key-revocation-cert.asc`), -exportiert den nun als widerrufen markierten Public Key erneut nach -`REPO_BASE_DIR/RPM-GPG-KEY-mirror` und verteilt ihn an alle Clients — dnf/rpm -lehnen mit einem widerrufenen Schlüssel signierte Pakete danach ab. - -## Wie die Sichtbarkeit in Discover/GNOME Software gesteuert wird - -`createrepo_c` erzeugt Repo-Metadaten für **alle** im Zielverzeichnis -liegenden RPMs (nötig, damit `dnf install` Abhängigkeiten auflösen kann). -GNOME Software/Discover zeigt aber nur Pakete an, für die es -AppStream-Komponenten-Metadaten gibt. Diese werden vom Skript **nur** für -die explizit unter `packages:` gelisteten Namen erzeugt (nicht für -mitgezogene Abhängigkeiten) und per `modifyrepo_c --mdtype=appstream` in -die Repo-Metadaten injiziert. - -**Einschränkung:** Es werden nur "generic" AppStream-Komponenten erzeugt -(kein Icon, keine Screenshots, keine `.desktop`-Datei — die liefert das -Originalpaket nicht mit). Das reicht, damit ein Paket in Discover -installierbar ist und in der Suche auftaucht, aber es erscheint **nicht** -auf der kuratierten "Entdecken"-Startseite von GNOME Software — das ist ein -Verhalten von GNOME Software/appstreamcli, das sich über Repo-Metadaten -allein nicht ändern lässt. - -## Repo auf Clients einbinden - -Alle Quellen landen in **einem einzigen** kombinierten Repo unter -`REPO_BASE_DIR/pkgs/` — egal wie viele Einträge eure `sources.yaml` hat. -Entsprechend gibt es auch nur **eine** Client-`.repo`-Datei: -`CLIENT_REPO_DIR/mirror.repo` (Default: `/srv/repo/client-repos/mirror.repo`, -über nginx erreichbar unter `/repo/client-repos/mirror.repo`). - -**Einbinden auf einem Client — ein Kommando, ein Repo:** - -```bash -curl -fsSL http://:8080/repo/client-repos/mirror.repo \ - -o /etc/yum.repos.d/rpm-mirror.repo -dnf makecache -``` - -Das war's — egal ob ihr später weitere Quellen zur `sources.yaml` -hinzufügt, die Client-Konfiguration bleibt unverändert (`baseurl` und -`gpgkey` ändern sich nicht, nur der Inhalt des Repos wächst). Für die -Verteilung an viele Clients eignet sich dasselbe `curl`-Kommando in -Ansible/Config-Management oder einem Kickstart-Postscript. - -`https:///repo` selbst (ohne `/pkgs/`) ist weiterhin **kein** dnf-Repo, -sondern nur ein Verzeichnis-Listing (nginx `autoindex`) — `baseurl` muss auf -`/repo/pkgs/` zeigen (das steht schon so in der generierten -`mirror.repo`, ihr müsst daran nichts anpassen). - -**Namenskonflikte:** Da jetzt alle Quellen in ein gemeinsames Verzeichnis -spiegeln, dürfen zwei Quellen keine RPMs mit demselben Paketnamen aber -unterschiedlichem Inhalt liefern (identische NEVRA aus zwei Quellen ist -unproblematisch, das ist einfach dasselbe Paket). Achtet bei mehreren -Drittanbieter-Repos in einer `sources.yaml` auf eindeutige Paketnamen. - -## Dateien - -- `sync.sh` — Hauptskript -- `sync_helpers.py` — YAML-Parsing & AppStream-XML-Generierung (muss im - selben Verzeichnis wie `sync.sh` liegen) -- `sources.example.yaml` — Beispiel-Quellenliste -- `docker-compose.yml`, `.env.example`, `docker/` — Container-Setup (siehe unten) - -## Docker-Setup - -Zwei Container: - -- **syncer** — AlmaLinux 10 Image, führt `sync.sh` beim Start und danach alle - `SYNC_INTERVAL_SECONDS` erneut aus (einfache Sleep-Loop, kein Cron/systemd - im Container). Schreibt nach `/srv/repo` (benanntes Volume `repo-data`). -- **nginx** — liefert `repo-data` read-only unter `http://:${NGINX_PORT}/repo/` - mit Directory-Listing aus, inklusive `client-repos/` für die fertigen - `.repo`-Dateien. - -### Start - -```bash -cp .env.example .env -# .env anpassen: mindestens CLIENT_BASE_URL und ALMA_REPO_IDS prüfen -docker compose up -d --build -docker compose logs -f syncer -``` - -**`.env` ist die einzige Konfigurationsquelle:** `docker-compose.yml` selbst -enthält keine hartkodierten Umgebungsvariablen oder Defaults mehr — beide -Container laden alles per `env_file: .env`. Wer etwas ändern will (Pfade, -Sync-Intervall, Client-URL, Signier-Identität, Port, ...), bearbeitet -ausschließlich `.env` und macht `docker compose up -d`, ohne `docker-compose.yml` -anzufassen. - -Danach ist das (eine, kombinierte) Repo erreichbar unter -`http://localhost:8080/repo/pkgs/` und die eine Client-`.repo`-Datei unter -`http://localhost:8080/repo/client-repos/mirror.repo`. - -### Eigene Quellenliste einbinden - -**Lokale Datei (Standard):** `LOCAL_SOURCES_YAML` in `.env` auf den Pfad -eurer echten `sources.yaml` setzen (relativ zum `docker-compose.yml`), z.B. -`LOCAL_SOURCES_YAML=./sources.yaml`. Wird read-only nach -`/config/sources.yaml` im Container gemountet. - -**Git-Repo statt lokaler Datei:** `SOURCES_GIT_REPO`, `SOURCES_GIT_BRANCH` -und `SOURCES_YAML_FILE` (Pfad *innerhalb* des Git-Repos, relativ) in `.env` -setzen. Der Bind-Mount aus der lokalen Variante wird dann schlicht ignoriert. - -### Einmaliger Sync statt Dauerlauf - -`RUN_ONCE=true` in `.env` setzen — der `syncer`-Container synct dann einmal -und beendet sich, statt in der Schleife zu laufen. Praktisch, wenn ihr den -Sync lieber über einen externen Scheduler (Kubernetes CronJob, systemd-Timer -mit `docker compose run`, etc.) anstoßen wollt. - -### ALMA_REPO_IDS prüfen - -Die repoids im Container können von einer bare-metal-Installation abweichen. -Nach dem ersten Start prüfen: - -```bash -docker compose exec syncer dnf repolist -``` - -und `ALMA_REPO_IDS` in `.env` entsprechend anpassen, dann -`docker compose up -d` erneut ausführen. - -### Persistenz - -- `repo-data` (Volume) — die gespiegelten Repos selbst; das ist auch, was - nginx ausliefert. -- `sync-cache` (Volume) — Arbeitsverzeichnis von `sync.sh` - (`WORK_DIR`, u.a. Logs unter `sync.log`). - -Beide überleben `docker compose down`; erst `docker compose down -v` löscht -sie.