#!/bin/bash # Ein Sync-Durchlauf: # 0. packages.yaml frisch aus Git ziehen (Fremdrepos + Paketnamen je Repo) # 1. je Fremdrepo genau die dort gelisteten Pakete per `dnf download` holen, # filtern (Alma-Baseline) und in einen privaten, pro Fremdrepo getrennten # Paket-Cache signieren # 2. ALLE Fremdrepos zu EINEM gemeinsamen, oeffentlichen Ausgabe-Repo # zusammenfuehren (ein repodata, ein GPG-Key, eine Client-.repo-Datei) - # Clients binden nur dieses eine Repo ein, unabhaengig davon, aus wie # vielen Fremdrepos die Pakete urspruenglich stammen # 3. Repo-Metadaten (createrepo_c) + AppStream-Metadaten (fuer Discover) bauen # 4. repomd.xml signieren, Client-.repo-Datei schreiben set -uo pipefail CACHE=/var/cache/mirror OUT=/srv/dnf SOURCES_GIT_CHECKOUT="$CACHE/sources-git" PACKAGES_JSON="$CACHE/packages.json" SIGNED_ROOT="$CACHE/signed" : "${GPG_NAME:?}" : "${MIRROR_DOMAIN:?}" : "${SOURCES_GIT_URL:?SOURCES_GIT_URL muss auf das Git-Repo mit packages.yaml zeigen}" : "${SOURCES_GIT_REF:=main}" : "${MIRROR_REPO_ID:=company-mirror}" : "${ALMA_RELEASEVER:=10}" : "${ALMA_BASEARCH:=x86_64}" : "${ALMA_BASELINE_REPOIDS:=baseos,appstream,extras,crb,epel}" mkdir -p "$CACHE/stage" "$CACHE/tmp-repos" "$SIGNED_ROOT" "$OUT" log() { echo "[sync $(date '+%F %T')] $*"; } # Zieht packages.yaml vor JEDEM Sync-Lauf frisch aus einem Git-Repo (statt aus # lokal gemounteten Dateien), damit Aenderungen an der Paketliste ohne # Container-Neubau/-Neustart wirksam werden. fetch_packages_yaml() { if [ -n "${SOURCES_GIT_SSH_KEY:-}" ] && [ -f "$SOURCES_GIT_SSH_KEY" ]; then export GIT_SSH_COMMAND="ssh -i ${SOURCES_GIT_SSH_KEY} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=${CACHE}/known_hosts" fi if [ -d "$SOURCES_GIT_CHECKOUT/.git" ]; then log "Aktualisiere packages.yaml aus Git ($SOURCES_GIT_URL @ $SOURCES_GIT_REF) ..." if git -C "$SOURCES_GIT_CHECKOUT" fetch --depth 1 origin "$SOURCES_GIT_REF" >>"$CACHE/git-sources.log" 2>&1 \ && git -C "$SOURCES_GIT_CHECKOUT" reset --hard FETCH_HEAD >>"$CACHE/git-sources.log" 2>&1 \ && git -C "$SOURCES_GIT_CHECKOUT" clean -fdx >>"$CACHE/git-sources.log" 2>&1; then : else log "WARNUNG: git fetch fehlgeschlagen, siehe $CACHE/git-sources.log - verwende letzten bekannten Stand von packages.yaml weiter." fi else log "Klone packages.yaml aus Git ($SOURCES_GIT_URL @ $SOURCES_GIT_REF) ..." rm -rf "$SOURCES_GIT_CHECKOUT" if ! git clone --branch "$SOURCES_GIT_REF" --single-branch --depth 1 \ "$SOURCES_GIT_URL" "$SOURCES_GIT_CHECKOUT" >>"$CACHE/git-sources.log" 2>&1; then log "FEHLER: Erstklon von packages.yaml aus Git fehlgeschlagen, siehe $CACHE/git-sources.log" return 1 fi fi local yaml_file="$SOURCES_GIT_CHECKOUT/packages.yaml" if [ ! -f "$yaml_file" ]; then log "FEHLER: $yaml_file existiert nicht im Git-Repo." return 1 fi if ! python3 -c ' import sys, json, yaml with open(sys.argv[1]) as f: data = yaml.safe_load(f) or {} data.setdefault("repos", []) data.setdefault("force_include", []) data.setdefault("console_apps", []) json.dump(data, sys.stdout) ' "$yaml_file" > "$PACKAGES_JSON.tmp" 2>"$CACHE/packages-yaml-parse.log"; then log "FEHLER: packages.yaml ist kein gueltiges YAML, siehe $CACHE/packages-yaml-parse.log" rm -f "$PACKAGES_JSON.tmp" return 1 fi mv "$PACKAGES_JSON.tmp" "$PACKAGES_JSON" } refresh_alma_names() { log "Ermittle Paketnamen, die AlmaLinux selbst bereitstellt ..." local -a enable_args=() local id IFS=',' read -ra ids <<< "$ALMA_BASELINE_REPOIDS" for id in "${ids[@]}"; do enable_args+=(--enablerepo="$id") done local -a reposdir_args=() if [ -n "${ALMA_BASELINE_REPOSDIR:-}" ] && ls "${ALMA_BASELINE_REPOSDIR}"/*.repo >/dev/null 2>&1; then reposdir_args=(--setopt="reposdir=${ALMA_BASELINE_REPOSDIR}") fi if ! dnf repoquery -y --quiet -a --qf '%{name}' \ --disablerepo='*' "${enable_args[@]}" \ --releasever="$ALMA_RELEASEVER" --forcearch="$ALMA_BASEARCH" \ "${reposdir_args[@]}" 2>>"$CACHE/repoquery.log" | sort -u > "$CACHE/alma-names.txt.tmp"; then log "WARNUNG: repoquery gegen AlmaLinux-Baseline fehlgeschlagen, siehe $CACHE/repoquery.log" log "Verwende ggf. vorherigen Stand von alma-names.txt weiter." rm -f "$CACHE/alma-names.txt.tmp" return fi mv "$CACHE/alma-names.txt.tmp" "$CACHE/alma-names.txt" log "$(wc -l < "$CACHE/alma-names.txt") Paketnamen als 'ueber Alma aufloesbar' erfasst." } is_force_included() { local name="$1" f for f in "${FORCE_INCLUDE_NAMES[@]}"; do [ "$f" = "$name" ] && return 0 done return 1 } # Steht das Paket in console_apps: der packages.yaml? Dann bekommt es einen # Katalogeintrag, auch wenn es (wie fast alle reinen CLI-Pakete) weder eine # .desktop- noch eine .metainfo.xml-Datei mitbringt. is_console_app() { local name="$1" f for f in "${CONSOLE_APP_NAMES[@]}"; do [ "$f" = "$name" ] && return 0 done return 1 } # Liest einen einzelnen Schluessel aus dem [Desktop Entry]-Abschnitt einer # .desktop-Datei (ignoriert lokalisierte Varianten wie Name[de]= und andere # Abschnitte wie [Desktop Action ...]). desktop_get() { local file="$1" key="$2" awk -v key="$key" ' /^\[Desktop Entry\]/ { insec=1; next } /^\[/ { insec=0 } insec && index($0, key "=") == 1 { sub("^" key "=", ""); print; exit } ' "$file" } # ACHTUNG: Ab bash 5.2 (AlmaLinux 10 liefert 5.2.26) steht ein unmaskiertes # "&" im ERSETZUNGS-Teil von ${var//muster/ersatz} - wie bei sed - fuer den # gefundenen Text. "${s///\>}" s="${s//\"/\"}" s="${s//\'/\'}" printf '%s' "$s" } # Erzeugt fuer ein Paket, das in console_apps: der packages.yaml steht, eine # console-application-Metainfo aus dem RPM-Header. Reine CLI-Pakete (dotnet-sdk, # terraform, kubectl, ...) bringen praktisch nie eine eigene .metainfo.xml oder # .desktop-Datei mit und tauchen ohne Katalogeintrag in Discover ueberhaupt # nicht auf - auch nicht bei exakter Suche nach dem Paketnamen, denn Discovers # PackageKit-Backend durchsucht ausschliesslich den AppStream-Pool. # # BEWUSST nur opt-in ueber packages.yaml: wuerde man das fuer jedes Paket ohne # .desktop-Datei tun, landeten auch saemtliche Runtime-, Bibliotheks- und # Debug-Unterpakete als "Anwendungen" im Katalog. # # Args: rpm-datei pkgname composeroot dateiliste-des-pakets generate_console_metainfo() { local rpm="$1" pkgname="$2" composeroot="$3" filelist="$4" local metadir="$composeroot/usr/share/metainfo" local target="$metadir/${pkgname}.metainfo.xml" local summary description url license binary mkdir -p "$metadir" [ -e "$target" ] && return summary=$(rpm -qp --qf '%{SUMMARY}' "$rpm" 2>/dev/null) description=$(rpm -qp --qf '%{DESCRIPTION}' "$rpm" 2>/dev/null) url=$(rpm -qp --qf '%{URL}' "$rpm" 2>/dev/null) license=$(rpm -qp --qf '%{LICENSE}' "$rpm" 2>/dev/null) [ -z "$summary" ] || [ "$summary" = "(none)" ] && summary="$pkgname" [ -z "$description" ] || [ "$description" = "(none)" ] && description="$summary" [ "$url" = "(none)" ] && url="" [ -z "$license" ] || [ "$license" = "(none)" ] && license="LicenseRef-proprietary" { echo '' echo '' echo " $(xml_escape "$pkgname")" echo " $(xml_escape "$pkgname")" echo ' CC0-1.0' echo " $(xml_escape "$license")" echo " $(xml_escape "$pkgname")" echo " $(xml_escape "$summary")" echo ' ' # %{DESCRIPTION} ist Fliesstext mit Leerzeilen als Absatztrennern - # AppStream verlangt hier

-Elemente. printf '%s\n' "$description" | awk ' NF == 0 { if (para != "") { print para; para = "" }; next } { para = (para == "" ? $0 : para " " $0) } END { if (para != "") print para } ' | while IFS= read -r para; do echo "

$(xml_escape "$para")

" done echo '
' [ -n "$url" ] && echo " $(xml_escape "$url")" # laesst Discover/appstreamcli die Komponente auch # ueber den Befehlsnamen finden, nicht nur ueber den Paketnamen. local -a binaries=() while IFS= read -r binary; do [ -n "$binary" ] && binaries+=("$binary") done < <(grep -E '^\./usr/(local/)?s?bin/[^/]+$' "$filelist" 2>/dev/null | sed 's|.*/||' | sort -u) if [ "${#binaries[@]}" -gt 0 ]; then echo ' ' for binary in "${binaries[@]}"; do echo " $(xml_escape "$binary")" done echo ' ' fi echo '
' } > "$target" } # appstreamcli compose (AppStream 1.0+) ignoriert .desktop-Dateien komplett, # wenn keine begleitende .metainfo.xml existiert - anders als das aeltere # appstream-builder, das genau das frueher automatisch synthetisiert hat. # Die meisten unabhaengigen Drittanbieter-RPMs (z.B. jpackage-basierte Apps # wie XPipe) liefern aber nur eine .desktop-Datei mit. Deshalb hier selbst # eine minimale Ersatz-Metainfo aus den Desktop-Datei-Feldern erzeugen, damit # die Anwendung ueberhaupt einen Katalogeintrag in Discover bekommt. Bringt # ein Paket schon eine echte .metainfo.xml mit (die auf dasselbe Desktop-File # verweist), hat diese immer Vorrang - hier wird dann nichts generiert. generate_fallback_metainfo() { local composeroot="$1" pkgmap="$2" local appdir="$composeroot/usr/share/applications" local metadir="$composeroot/usr/share/metainfo" local desktop_file base name summary categories cat pkgname [ -d "$appdir" ] || return mkdir -p "$metadir" for desktop_file in "$appdir"/*.desktop; do [ -e "$desktop_file" ] || continue base=$(basename "$desktop_file") if grep -rlq "type=\"desktop-id\">${base}<" "$metadir" 2>/dev/null; then continue fi pkgname="" [ -f "$pkgmap" ] && pkgname=$(awk -F'\t' -v b="$base" '$1 == b {print $2; exit}' "$pkgmap") [ -z "$pkgname" ] && pkgname="${base%.desktop}" name=$(desktop_get "$desktop_file" "Name") summary=$(desktop_get "$desktop_file" "Comment") categories=$(desktop_get "$desktop_file" "Categories") [ -z "$name" ] && name="${base%.desktop}" [ -z "$summary" ] && summary="$name" { echo '' echo '' echo " ${base}" echo " $(xml_escape "$pkgname")" echo ' CC0-1.0' echo ' LicenseRef-proprietary' echo " $(xml_escape "$name")" echo " $(xml_escape "$summary")" echo " " echo "

$(xml_escape "$summary")

" echo "
" echo " ${base}" if [ -n "$categories" ]; then echo ' ' local IFS=';' for cat in $categories; do [ -z "$cat" ] && continue echo " $(xml_escape "$cat")" done echo ' ' fi echo '
' } > "$metadir/${base%.desktop}.metainfo.xml" done } # Baut den AppStream-Katalog mit `appstreamcli compose` (Paket # appstream-compose). Das Kommando arbeitet nicht auf .rpm-Dateien, sondern auf # einem Verzeichnisbaum wie einem Dateisystem-Root (/usr/share/applications, # /usr/share/metainfo, ...). Wir entpacken deshalb erst die dafuer relevanten # Pfade aus allen Paketen in so einen Baum und rufen dann `appstreamcli compose` # darauf auf. generate_appstream_via_compose() { local id="$1" out="$2" local composeroot="$CACHE/appstream-root/$id" local asout="$CACHE/appstream/$id" local pkgmap="$CACHE/appstream-pkgmap-$id.txt" local rpm rm -rf "$composeroot" mkdir -p "$composeroot/usr/share/applications" "$composeroot/usr/share/metainfo" : > "$pkgmap" for rpm in "$out"/*.rpm; do [ -e "$rpm" ] || continue local pkgname pkgname=$(rpm -qp --qf '%{NAME}' "$rpm" 2>/dev/null) # Icons/Pixmaps liegen so gut wie immer am Standard-FHS-Pfad - dafuer # reicht ein blinder Glob-Extract, eine Paketzuordnung brauchen wir # dafuer nicht. (cd "$composeroot" && rpm2cpio "$rpm" | cpio -idm --quiet \ './usr/share/icons/*' \ './usr/share/pixmaps/*') >>"$CACHE/appstream-$id.log" 2>&1 # .desktop-/AppStream-Dateien koennen dagegen auch ausserhalb der # Standardpfade liegen (u.a. alle jpackage-basierten Java/JavaFX-Apps # wie z.B. XPipe legen sie direkt im eigenen Installationsverzeichnis # ab, z.B. /opt//.desktop, und verschieben sie erst per # %post-Skript - das sehen wir beim reinen Lesen des Pakets nicht). # Deshalb IMMER das ganze Paket nur AUFLISTEN (kein teurer Voll- # Extract, bei z.T. >100 MB grossen JRE-Bundles) und alle # .desktop/.metainfo.xml/.appdata.xml-Dateien gezielt herausgreifen - # dabei merken wir uns, aus welchem RPM-Paket jede .desktop-Datei # stammt, fuer in der (Fallback-)Metainfo weiter unten. local filelist matches filelist=$(mktemp) rpm2cpio "$rpm" 2>/dev/null | cpio -t --quiet > "$filelist" 2>/dev/null mapfile -t matches < <(grep -E '\.desktop$|\.metainfo\.xml$|\.appdata\.xml$' "$filelist") # Reines CLI-Paket, das in console_apps: der packages.yaml steht: aus # dem RPM-Header eine console-application-Metainfo bauen. Bringt das # Paket wider Erwarten doch eigene AppStream-/Desktop-Dateien mit, # haben die Vorrang und wir erzeugen nichts. if [ "${#matches[@]}" -eq 0 ] && is_console_app "$pkgname"; then log "$pkgname: erzeuge Konsolen-Katalogeintrag (kein .desktop/.metainfo.xml im Paket)." generate_console_metainfo "$rpm" "$pkgname" "$composeroot" "$filelist" fi if [ "${#matches[@]}" -gt 0 ]; then local extractroot f base destdir extractroot=$(mktemp -d) (cd "$extractroot" && rpm2cpio "$rpm" | cpio -idm --quiet "${matches[@]}") >>"$CACHE/appstream-$id.log" 2>&1 for f in "${matches[@]}"; do [ -f "$extractroot/$f" ] || continue base=$(basename "$f") case "$base" in *.desktop) destdir="$composeroot/usr/share/applications" printf '%s\t%s\n' "$base" "$pkgname" >> "$pkgmap" ;; *) destdir="$composeroot/usr/share/metainfo" ;; esac # Nicht ueberschreiben, falls bereits ein anderes Paket schon # eine gleichnamige Datei mitgebracht hat. if [ ! -e "$destdir/$base" ]; then cp "$extractroot/$f" "$destdir/$base" # Vom Hersteller mitgelieferte .metainfo.xml/.appdata.xml # haben so gut wie nie ein (das kennt nur die # Distro/das Repo, nicht der Upstream-Entwickler) - ohne # das bleibt die Komponente in Discover trotz gueltiger # Metainfo nicht installierbar. Deshalb hier direkt # ergaenzen, falls es fehlt. if [ "$base" != "${base%.desktop}" ]; then : # ist eine .desktop-Datei, braucht selbst kein elif ! grep -q '' "$destdir/$base" 2>/dev/null; then sed -i "s## $(xml_escape "$pkgname")\n#" "$destdir/$base" fi fi done rm -rf "$extractroot" fi rm -f "$filelist" done if [ -z "$(find "$composeroot" -type f 2>/dev/null)" ]; then log "Keine Desktop-/AppStream-Dateien in den Paketen gefunden und kein Paket in console_apps: - ueberspringe Katalog-Generierung." return fi generate_fallback_metainfo "$composeroot" "$pkgmap" rm -rf "$asout" mkdir -p "$asout" # Exit-Code NICHT sofort als fatal behandeln: appstreamcli compose liefert # bei einzelnen fehlerhaften Komponenten ("Run failed, some data was # ignored") einen Fehler-Exitcode, schreibt aber trotzdem einen Katalog # mit allen UEBRIGEN, gueltigen Komponenten. Nur wenn am Ende wirklich # gar kein Katalog entstanden ist, brechen wir ab. if ! appstreamcli compose --prefix=/usr --origin="$id" --result-root="$asout" "$composeroot" \ >>"$CACHE/appstream-$id.log" 2>&1; then log "WARNUNG: appstreamcli compose meldet Fehler bei einzelnen Komponenten, siehe $CACHE/appstream-$id.log - verwende ggf. trotzdem erzeugten Teil-Katalog." fi local catalog catalog=$(find "$asout" -type f \( -iname '*.xml.gz' -o -iname '*.yml.gz' \) | head -n1) if [ -z "$catalog" ]; then log "WARNUNG: appstreamcli compose hat keinen Katalog erzeugt, siehe $CACHE/appstream-$id.log" return fi # `appstreamcli compose` schreibt den Origin nur in Datei- und # Verzeichnisnamen, NICHT als origin=-Attribut in den Katalog. libappstream # (AsPool) liest den Origin einer Katalogdatei aber genau aus diesem # Attribut und findet ohne ihn die zugehoerigen Icons unter # /var/cache/swcatalog/icons// nicht - die App erscheint in # Discover dann ohne Symbol. Deshalb hier nachtragen. if ! zcat "$catalog" | head -c 400 | grep -q 'origin='; then if zcat "$catalog" | sed "0,/ "$catalog.tmp"; then mv "$catalog.tmp" "$catalog" else log "WARNUNG: konnte origin=\"$id\" nicht in $catalog eintragen - Icons fehlen ggf. in Discover." rm -f "$catalog.tmp" fi fi # --compress-type=gz ist PFLICHT: modifyrepo_c komprimiert die uebergebene # Datei neu und nutzt auf AlmaLinux 10 per Default zstd. Das dort # installierte AppStream 1.0.2 kennt in as_metadata_file_guess_style() aber # nur .xml/.xml.gz/.yml/.yml.gz (.xml.zst erst ab AppStream 1.0.5). Bei # einer .xml.zst lehnt as_utils_install_metadata_file() die Datei mit # "Can not process files of this type" ab, PackageKit bricht daraufhin den # gesamten Repo-Refresh ab und in /var/cache/swcatalog/ landet nie etwas - # Discover und appstreamcli sehen das Repo dann gar nicht. modifyrepo_c --compress-type=gz --mdtype=appstream "$catalog" "$out/repodata" # Icons: as_utils_install_metadata_file() akzeptiert nur *.tar.gz UND # verlangt eine der bekannten Groessen (48x48, 64x64, 128x128, jeweils # optional @2) IM DATEINAMEN - fehlt sie, bricht es mit "Unable to find # valid icon size in icon tarball name" ab und reisst wieder den ganzen # Refresh mit. Entpackt wird flach nach # /var/cache/swcatalog/icons///, der Tarball darf also # nur die PNGs auf oberster Ebene enthalten (nicht //...). # PackageKit holt genau EINEN appstream-icons-Eintrag, wir liefern deshalb # genau eine Groesse aus (64x64 ist das, was Discover/GNOME Software in der # Listenansicht nutzen). local icons_root icons_size icons_src tarball icons_root=$(find "$asout" -type d -name 'icons' | head -n1) for icons_size in 64x64 128x128 48x48; do if [ -n "$icons_root" ] && [ -d "$icons_root/$id/$icons_size" ] \ && [ -n "$(find "$icons_root/$id/$icons_size" -type f 2>/dev/null)" ]; then icons_src="$icons_root/$id/$icons_size" break fi done if [ -n "${icons_src:-}" ]; then tarball="$asout/$id-icons-$icons_size.tar.gz" rm -f "$tarball" if tar -C "$icons_src" -czf "$tarball" . ; then modifyrepo_c --compress-type=gz --mdtype=appstream-icons "$tarball" "$out/repodata" else log "WARNUNG: Icon-Tarball fuer $id konnte nicht gepackt werden - Apps erscheinen in Discover ohne Symbol." fi else log "WARNUNG: appstreamcli compose hat keine Icons erzeugt - Apps erscheinen in Discover ohne Symbol." fi } # Holt + filtert + signiert die Pakete EINES Fremdrepos in einen privaten, # nur intern genutzten Cache ($SIGNED_ROOT/$id) - noch NICHT im oeffentlichen # Ausgabeverzeichnis. Der eigentliche Merge zu einem gemeinsamen Repo passiert # erst danach, einmalig fuer alle Fremdrepos zusammen (siehe unten). # Args: id baseurl gpgkey gpgcheck(0/1) paket1 [paket2 ...] sync_one_repo() { local id="$1" baseurl="$2" gpgkey="$3" gpgcheck="$4" shift 4 local -a packages=("$@") local stage="$CACHE/stage/$id" local signed="$SIGNED_ROOT/$id" local tmp_repofile="$CACHE/tmp-repos/$id.repo" log "== $id: hole ${#packages[@]} Paket(e) aus $baseurl ==" rm -f "$CACHE/tmp-repos"/*.repo { echo "[$id]" echo "name=$id" echo "baseurl=$baseurl" echo "enabled=1" echo "gpgcheck=$gpgcheck" [ -n "$gpgkey" ] && echo "gpgkey=$gpgkey" } > "$tmp_repofile" rm -rf "$stage" mkdir -p "$stage" if ! dnf --setopt="reposdir=$CACHE/tmp-repos" \ --disablerepo='*' --enablerepo="$id" \ --forcearch="$ALMA_BASEARCH" \ download --destdir="$stage" "${packages[@]}" \ >>"$CACHE/download-$id.log" 2>&1; then log "FEHLER: dnf download fuer $id fehlgeschlagen, siehe $CACHE/download-$id.log - ueberspringe (bisheriger Stand von $id bleibt im gemeinsamen Repo erhalten)." return 1 fi log "== $id: filtere Pakete, die Alma bereits bereitstellt ==" local removed=0 kept=0 while IFS= read -r -d '' rpm; do local name name=$(rpm -qp --qf '%{NAME}' "$rpm" 2>/dev/null) if [ -z "$name" ]; then continue fi if grep -qxF "$name" "$CACHE/alma-names.txt" 2>/dev/null && ! is_force_included "$name"; then rm -f "$rpm" removed=$((removed + 1)) else kept=$((kept + 1)) fi done < <(find "$stage" -maxdepth 1 -name '*.rpm' -print0) log "$id: $kept Pakete werden gespiegelt, $removed uebersprungen (bereits ueber Alma verfuegbar)." log "== $id: aktualisiere internen Paketstand und signiere neue/geaenderte Pakete ==" mkdir -p "$signed" local changed_list rsync_out changed_list=$(mktemp) rsync_out=$(mktemp) if ! rsync -ai --delete --include='*.rpm' --exclude='*' "$stage"/ "$signed"/ > "$rsync_out" 2>&1; then log "FEHLER: rsync (interner Paketstand) fuer $id fehlgeschlagen: $(cat "$rsync_out") - ueberspringe $id fuer diesen Lauf." rm -f "$changed_list" "$rsync_out" return 1 fi awk '/^[><]f/{print $2}' "$rsync_out" > "$changed_list" rm -f "$rsync_out" local resign_failed=0 while IFS= read -r f; do [ -z "$f" ] && continue if ! rpmsign --resign \ --define "_gpg_name $GPG_NAME" \ --define "_gpg_path $GNUPGHOME" \ "$signed/$f" >>"$CACHE/rpmsign-$id.log" 2>&1; then log "FEHLER: Signieren von $signed/$f fehlgeschlagen, siehe $CACHE/rpmsign-$id.log" resign_failed=1 fi done < "$changed_list" local changed_count changed_count=$(wc -l < "$changed_list") rm -f "$changed_list" log "$id: $changed_count Pakete neu signiert." if [ "$resign_failed" -ne 0 ]; then log "FEHLER: mind. ein Paket in $id konnte nicht signiert werden - $id wird bei der Zusammenfuehrung uebersprungen." return 1 fi log "== $id: fertig ==" } if ! fetch_packages_yaml; then log "FEHLER: keine packages.yaml verfuegbar, breche Sync-Lauf komplett ab." exit 1 fi mapfile -t FORCE_INCLUDE_NAMES < <(jq -r '.force_include[]? // empty' "$PACKAGES_JSON") mapfile -t CONSOLE_APP_NAMES < <(jq -r '.console_apps[]? // empty' "$PACKAGES_JSON") refresh_alma_names if [ ! -s "$CACHE/alma-names.txt" ]; then log "FEHLER: keine Alma-Baseline-Namen verfuegbar, breche Sync-Lauf ab (Sicherheitsnetz gegen versehentliches Doppel-Spiegeln)." exit 1 fi overall_status=0 all_ids=() repo_count=$(jq '.repos | length' "$PACKAGES_JSON") for ((i = 0; i < repo_count; i++)); do repo_json=$(jq -c ".repos[$i]" "$PACKAGES_JSON") id=$(jq -r '.id // empty' <<<"$repo_json") baseurl=$(jq -r '.baseurl // empty' <<<"$repo_json") gpgkey=$(jq -r '.gpgkey // empty' <<<"$repo_json") gpgcheck=$(jq -r 'if .gpgcheck == false then "0" else "1" end' <<<"$repo_json") mapfile -t packages < <(jq -r '.packages[]? // empty' <<<"$repo_json") if [ -z "$id" ] || [ -z "$baseurl" ] || [ "${#packages[@]}" -eq 0 ]; then log "WARNUNG: Eintrag Nr. $((i + 1)) in packages.yaml unvollstaendig (id/baseurl/packages fehlt), ueberspringe." overall_status=1 continue fi all_ids+=("$id") sync_one_repo "$id" "$baseurl" "$gpgkey" "$gpgcheck" "${packages[@]}" || overall_status=1 done # Internen Paketstand von Fremdrepos aufraeumen, die nicht mehr in # packages.yaml stehen - sonst wuerden deren alte Pakete beim Merge unten # unnoetig im Cache liegen bleiben (sie werden ja schon jetzt nicht mehr in # die Merge-Quellenliste aufgenommen, siehe unten - das hier ist nur Hygiene). if [ -d "$SIGNED_ROOT" ]; then for d in "$SIGNED_ROOT"/*/; do [ -d "$d" ] || continue existing_id=$(basename "$d") keep=0 for id in "${all_ids[@]}"; do [ "$id" = "$existing_id" ] && keep=1 && break done if [ "$keep" -eq 0 ]; then log "Entferne internen Paketstand des nicht mehr in packages.yaml gelisteten Fremdrepos '$existing_id' ..." rm -rf "$d" fi done fi log "== Fuehre alle Fremdrepos zu EINEM gemeinsamen Ausgabe-Repo zusammen ==" merge_sources=() for id in "${all_ids[@]}"; do [ -d "$SIGNED_ROOT/$id" ] && merge_sources+=("$SIGNED_ROOT/$id/") done if [ "${#merge_sources[@]}" -eq 0 ]; then log "FEHLER: kein Fremdrepo hat je erfolgreich Pakete geliefert - gemeinsames Repo bleibt unveraendert/leer." exit 1 fi if ! rsync -ai --delete --include='*.rpm' --exclude='*' "${merge_sources[@]}" "$OUT"/ >>"$CACHE/merge.log" 2>&1; then log "FEHLER: Zusammenfuehren der Fremdrepos zum gemeinsamen Repo fehlgeschlagen, siehe $CACHE/merge.log" exit 1 fi log "== baue Repo-Metadaten fuer das gemeinsame Repo ==" if ! createrepo_c --update "$OUT" >>"$CACHE/createrepo.log" 2>&1; then log "FEHLER: createrepo_c fehlgeschlagen, siehe $CACHE/createrepo.log" exit 1 fi log "== baue AppStream-Metadaten (fuer Discover/GNOME Software) ==" if command -v appstreamcli >/dev/null 2>&1; then generate_appstream_via_compose "$MIRROR_REPO_ID" "$OUT" else log "WARNUNG: appstreamcli nicht installiert - Pakete erscheinen ohne App-Katalogeintrag in Discover." fi log "== signiere repomd.xml ==" gpg --batch --yes --detach-sign --armor --local-user "$GPG_NAME" \ --output "$OUT/repodata/repomd.xml.asc" "$OUT/repodata/repomd.xml" cat > "$OUT/$MIRROR_REPO_ID.repo" <