#!/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 # 2. Pakete rauswerfen, die AlmaLinux (BaseOS/AppStream/Extras/CRB/EPEL) # sowieso schon bereitstellt (ausser force_include-Ausnahmen) # 3. verbleibende Pakete mit dem eigenen Mirror-Key umsignieren # 4. Repo-Metadaten (createrepo_c) + AppStream-Metadaten (fuer Discover) bauen # 5. 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" : "${GPG_NAME:?}" : "${MIRROR_DOMAIN:?}" : "${SOURCES_GIT_URL:?SOURCES_GIT_URL muss auf das Git-Repo mit packages.yaml zeigen}" : "${SOURCES_GIT_REF:=main}" : "${ALMA_RELEASEVER:=9}" : "${ALMA_BASEARCH:=x86_64}" : "${ALMA_BASELINE_REPOIDS:=baseos,appstream,extras,crb,epel}" mkdir -p "$CACHE/stage" "$CACHE/appstream-cache" "$CACHE/tmp-repos" "$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", []) 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 } # 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 out="$OUT/$id" local tmp_repofile="$CACHE/tmp-repos/$id.repo" log "== $id: hole ${#packages[@]} Paket(e) aus $baseurl ==" rm -rf "$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." 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: gleiche mit oeffentlichem Ausgabeverzeichnis ab und signiere neue/geaenderte Pakete ==" mkdir -p "$out" local changed_list changed_list=$(mktemp) rsync -ai --delete --include='*.rpm' --exclude='*' "$stage"/ "$out"/ \ | awk '/^[><]f/{print $2}' > "$changed_list" 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" \ "$out/$f" >>"$CACHE/rpmsign-$id.log" 2>&1; then log "FEHLER: Signieren von $out/$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 - ueberspringe Metadaten-Neubau fuer $id." return 1 fi log "== $id: baue Repo-Metadaten ==" if ! createrepo_c --update "$out" >>"$CACHE/createrepo-$id.log" 2>&1; then log "FEHLER: createrepo_c fuer $id fehlgeschlagen, siehe $CACHE/createrepo-$id.log" return 1 fi if command -v appstream-builder >/dev/null 2>&1; then log "== $id: baue AppStream-Metadaten (fuer Discover/GNOME Software) ==" local asout="$CACHE/appstream/$id" mkdir -p "$asout" if appstream-builder \ --origin="$id" --basename="$id" \ --cache-dir="$CACHE/appstream-cache/$id" \ --packages-dir="$out" \ --output-dir="$asout" \ --min-icon-size=48 --enable-hidpi \ >>"$CACHE/appstream-$id.log" 2>&1; then [ -f "$asout/$id.xml.gz" ] && modifyrepo_c --mdtype=appstream "$asout/$id.xml.gz" "$out/repodata" [ -f "$asout/$id-icons.tar.gz" ] && modifyrepo_c --mdtype=appstream-icons "$asout/$id-icons.tar.gz" "$out/repodata" else log "WARNUNG: appstream-builder fuer $id fehlgeschlagen (nicht fatal), siehe $CACHE/appstream-$id.log" fi else log "WARNUNG: appstream-builder nicht installiert - $id erscheint ohne App-Katalogeintrag in Discover." fi log "== $id: 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/$id.repo" <