#!/bin/bash # Synct die Paket-Whitelist aus Git (oder aus einer lokal gemounteten Datei) # und laedt die selektierten Pakete aus den jeweils angegebenen Fremd-Repos. # Erzeugt anschliessend RPM- und - sofern der Generator vorhanden ist - # AppStream-Metadaten. set -euo pipefail # createrepo_c uebernimmt die umask des Aufrufers. Ohne "umask 022" kann # repodata/ mit 0700 landen; der nginx-Worker (User nginx) kommt dann nicht # hinein und liefert 403 - bzw. 404, falls die nginx-Config try_files nutzt. umask 022 # --- Pfade ---------------------------------------------------------------- # Muessen zu den Volumes in docker-compose.yml passen: # repo-data -> /srv/repo (im nginx-Container: /usr/share/nginx/html/repo) # sync-cache -> /var/cache/rpm-mirror-sync # Daraus ergibt sich die oeffentliche URL: /repo/pkgs/repodata/repomd.xml WORK_DIR="${WORK_DIR:-/var/cache/rpm-mirror-sync}" REPO_ROOT="${REPO_ROOT:-/srv/repo}" DEST="$REPO_ROOT/pkgs" CONFIG_DIR="$WORK_DIR/config" LOCAL_PACKAGES_FILE=/config/packages.yaml TMP_REPO_DIR=/etc/yum.repos.d LOCKFILE="$WORK_DIR/sync.lock" APPSTREAM_GEN="${APPSTREAM_GEN:-/usr/local/bin/appstream-gen.py}" mkdir -p "$WORK_DIR" "$DEST" exec 200>"$LOCKFILE" if ! flock -n 200; then echo "[sync] Ein anderer Sync-Lauf ist bereits aktiv, breche ab." exit 0 fi echo "[sync] $(date -Iseconds) Starte Sync-Lauf" # --- 1. Whitelist besorgen ------------------------------------------------ # Eine gemountete /config/packages.yaml hat Vorrang. Nur wenn sie fehlt, # wird SOURCES_GIT_REPO gebraucht. if [ -f "$LOCAL_PACKAGES_FILE" ]; then PACKAGES_FILE="$LOCAL_PACKAGES_FILE" echo "[sync] Nutze lokal gemountete $PACKAGES_FILE" else : "${SOURCES_GIT_REPO:?weder SOURCES_GIT_REPO gesetzt noch /config/packages.yaml gemountet}" CONFIG_REPO_BRANCH="${CONFIG_REPO_BRANCH:-main}" PACKAGES_FILE="$CONFIG_DIR/packages.yaml" if [ -n "${GIT_SSH_KEY_PATH:-}" ] && [ -f "$GIT_SSH_KEY_PATH" ]; then export GIT_SSH_COMMAND="ssh -i ${GIT_SSH_KEY_PATH} -o StrictHostKeyChecking=accept-new" fi if [ -d "$CONFIG_DIR/.git" ]; then git -C "$CONFIG_DIR" fetch --depth=1 origin "$CONFIG_REPO_BRANCH" git -C "$CONFIG_DIR" reset --hard "origin/$CONFIG_REPO_BRANCH" else # Reste eines abgebrochenen Clones wegraeumen, sonst scheitert git clone. rm -rf "$CONFIG_DIR" git clone --depth=1 --branch "$CONFIG_REPO_BRANCH" "$SOURCES_GIT_REPO" "$CONFIG_DIR" fi fi if [ ! -f "$PACKAGES_FILE" ]; then echo "[sync] FEHLER: $PACKAGES_FILE nicht gefunden." exit 1 fi # --- 2. Pro Quelle: temporaeres Repo aktivieren, Pakete gezielt ziehen ----- source_count=$(yq -o=json '.sources | length' "$PACKAGES_FILE") download_failed=0 for i in $(seq 0 $((source_count - 1))); do src=$(yq -o=json ".sources[$i]" "$PACKAGES_FILE") name=$(echo "$src" | jq -r '.name') baseurl=$(echo "$src" | jq -r '.baseurl') gpgkey=$(echo "$src" | jq -r '.gpgkey // empty') # Standard: false. Nur bei echten Fremd-Repos aktivieren, deren # Abhaengigkeiten die Clients nicht ueber ihre Basis-Repos bekommen. resolve_deps=$(echo "$src" | jq -r '.resolve_deps // false') # Paketeintraege koennen Strings ODER Mappings mit Metadaten sein. mapfile -t packages < <( echo "$src" | jq -r '.packages[] | if type == "string" then . else .name end' ) if [ "${#packages[@]}" -eq 0 ]; then echo "[sync] Quelle '$name': keine Pakete gelistet, ueberspringe." continue fi echo "[sync] Quelle '$name': ${#packages[@]} Paket(e) -> $baseurl" repofile="$TMP_REPO_DIR/tmp-${name}.repo" { echo "[tmp-${name}]" echo "name=${name}" echo "baseurl=${baseurl}" echo "enabled=0" if [ -n "$gpgkey" ]; then echo "gpgcheck=1" echo "gpgkey=${gpgkey}" else echo "gpgcheck=0" fi } > "$repofile" # Die Basis-Repos des Containers bleiben aktiviert, damit dnf # Abhaengigkeiten wie glibc oder libstdc++ ueberhaupt aufloesen kann. # Ohne sie scheitert bereits die Aufloesung, nicht erst der Download. dl_args=(--destdir="$DEST" --enablerepo="tmp-${name}") if [ "$resolve_deps" = "true" ]; then echo "[sync] Quelle '$name': Abhaengigkeiten werden mitgespiegelt" # Bewusst OHNE --alldeps: bereits im Container installierte Pakete # (glibc & Co.) haben die Clients ohnehin und muessen nicht ins Repo. dl_args+=(--resolve) fi if ! dnf download "${dl_args[@]}" "${packages[@]}"; then echo "[sync] WARNUNG: Download fuer Quelle '$name' fehlgeschlagen." download_failed=1 fi rm -f "$repofile" done # --- 3. Alte Paketversionen aufraeumen ------------------------------------ # repomanage braucht bereits vorhandene Metadaten. Beim allerersten Lauf # gibt es noch keine, deshalb der Existenztest. if [ -f "$DEST/repodata/repomd.xml" ]; then echo "[sync] Raeume veraltete Paketversionen auf" if ! dnf repomanage --old "$DEST" | xargs -r rm -f; then echo "[sync] WARNUNG: repomanage fehlgeschlagen, ueberspringe Aufraeumen." fi else echo "[sync] Erster Lauf, kein Aufraeumen noetig." fi # --- 4. RPM-Metadaten neu erzeugen ---------------------------------------- echo "[sync] Aktualisiere Repo-Metadaten" createrepo_c --update "$DEST" # --- 5. AppStream-Katalog erzeugen und einhaengen -------------------------- # Ohne diesen Schritt sind die Pakete zwar per dnf installierbar, tauchen # aber weder in Discover noch in GNOME Software auf. Der Generator ist # optional: fehlt er, laeuft der Sync trotzdem sauber durch. if [ -x "$APPSTREAM_GEN" ]; then echo "[sync] Erzeuge AppStream-Katalog" ASWORK=$(mktemp -d) trap 'rm -rf "$ASWORK"' EXIT # Welche Pakete liegen tatsaechlich im Repo? Verhindert, dass Discover # Eintraege anzeigt, deren RPM gar nicht da ist. if compgen -G "$DEST"/*.rpm > /dev/null; then rpm -qp --qf '%{NAME}\n' "$DEST"/*.rpm 2>/dev/null \ | sort -u > "$ASWORK/vorhanden.txt" else echo "[sync] WARNUNG: Keine RPMs in $DEST gefunden." : > "$ASWORK/vorhanden.txt" fi python3 "$APPSTREAM_GEN" "$PACKAGES_FILE" \ --output "$ASWORK/appstream.xml.gz" \ --available-packages "$ASWORK/vorhanden.txt" # Optionale Validierung, falls appstreamcli im Image vorhanden ist. if command -v appstreamcli > /dev/null 2>&1; then if ! appstreamcli validate --no-net "$ASWORK/appstream.xml.gz"; then echo "[sync] WARNUNG: AppStream-Katalog hat Validierungshinweise." fi fi # modifyrepo_c MUSS nach createrepo_c laufen, da createrepo_c die # repomd.xml komplett neu schreibt. modifyrepo_c --mdtype=appstream "$ASWORK/appstream.xml.gz" "$DEST/repodata/" else echo "[sync] HINWEIS: $APPSTREAM_GEN nicht vorhanden - AppStream wird uebersprungen." fi # --- 6. Leserechte fuer den Webserver sicherstellen ------------------------ # a+rX = Leserecht auf alles, Betretungsrecht nur auf Verzeichnisse. chmod -R a+rX "$REPO_ROOT" if [ "$download_failed" -ne 0 ]; then echo "[sync] $(date -Iseconds) Sync-Lauf mit Fehlern abgeschlossen" exit 1 fi echo "[sync] $(date -Iseconds) Sync-Lauf abgeschlossen"