Debian-Pakete hosten

Aus /usr/space Wiki
Zur Navigation springen Zur Suche springen
Dieser Artikel ist work-in-progress und sollte bis zum Vortrag am 30.8.23 fertiggestellt sein. Auch wenn im Text erwähnt werden sollte, dass die Vorgehensweise getestet wurde, trifft das erst dann zu, wenn dieser Hinweis entfernt wurde!

In diesem Artikel beschreibe ich, wie ich selbst ein Repo betreibe. Ich bin kein Experte auf dem Gebiet, deswegen erfolgt das nachmachen dieser Anleitung selbstverständlich auf eigene Gefahr. Außerdem behandeln wir hier das Thema TLS/SSL nicht.

Voraussetzungen

Betriebssystem

Ein Debian-Repository kann auf einer Vielzahl von Systemen gehostet werden, aber die naheliegendste Wahl ist natürlich Debian oder ein Debian-basiertes System zu verwenden. Ich benutze dafür Ubuntu Server, welches auch das System ist, mit dem folgende Anleitung getestet wurde.

Software

Hier werden nur Programme aufgelistet, welche in einer Standardinstallation von Ubuntu Server (22.04 als LXC) nicht enthalten sind.

  • nginx: eine Webserver-Software
  • gpg: GNU Privacy Guard - erzeugen und prüfen elektronischer Signaturen.
  • bzip2: ein Komprimierungsprogramm
  • tree: rekursives Directorylisting - nicht wirklich essenziell, aber wir können uns schön eine Verzeichnisstruktur anzeigen lassen
  • dpkg-dev: Debian package development tools (wir verwenden im Speziellen dpkg-architecture)

Um alles zu installieren, führe folegnden Befehl aus:

sudo apt install nginx gpg bzip2 tree dpkg-dev

Repo vorbereiten (als root)

Unprivilegierten Benutzer erstellen

Hier wird der Benutzername repo verwendet, der natürlich gegen jeden anderen Namen getauscht werden kann - jedenfalls ist der Text so copy-and-paste-freundlich.

adduser

Die einfachste Art, einen neuen Benutzer anzulegen, ist mit dem Befehl adduser. Dabei wird der neue Account interaktiv eingerichtet.

sudo adduser repo

useradd

Alternativ gibt es auch den sehr ähnlichen Befehl useradd, dieser ist im Gegensatz zu adduser nicht interaktiv.

sudo useradd -m --shell /bin/bash repo
sudo passwd repo

Wurzelverzeichnis des Repos anlegen

Das hier gewählte Verzeichnis kann wieder frei benannt werden, aber repo bietet sich natürlich wieder gut an.

sudo mkdir /var/www/repo
sudo chown repo:www-data /var/www/repo
sudo chmod 755 /var/www/repo

Principle of Least Privilege

Wir haben uns in den letzten zwei Schritten an das Principle of least privilege gehalten, da der repo-User kein Mitglied der Gruppe www-data ist, und die Gruppe www-data keine Schreibrechte in unserem Repo-Wurzelverzeichnis hat. Außerdem ist der repo-User kein Mitglied der sudo-Gruppe.

Falls diese Vorgehensweise für deinen speziellen Fall nicht angebracht ist, musst du die Rechte bei dir anders vergeben. Wenn du z.B. den repo-User der Gruppe www-data hinzufügen willst, kannst du das mit sudo usermod -aG www-data repo machen.

nginx einrichten

Wir erstellen eine Konfigurationsdatei für nginx, die wir wieder mal repo nennen. Außerdem disablen wir die Default-Welcome-Page von nginx (vorher bitte checken, ob diese Seite auch angezeigt wurde - dann wissen wir zumindest, dass nginx soweit funktioniert).

sudo rm /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/repo

Folgenden Text in die Datei einfügen:

server {
    listen 80 default_server;
    server_name _;
    # besser die tatsächliche Domain angeben und nicht default_server verwenden
    # listen 80;
    # server_name repo.example.com;

    location / {
        # das Wurzelverzeichnis (bei Bedarf anpassen)
        root /var/www/repo;
        # autoindex on;: Erlaubt das Auflisten von Verzeichnissen.
        autoindex on;
        # Content-Disposition "inline" stellt sicher, dass die Inhalte im Browser angezeigt werden können.
        add_header Content-Disposition "inline";
        # Der Cache-Control-Header teilt dem Client und den Proxies mit, dass die
        # Inhalte nicht zwischengespeichert oder wiederverwendet werden sollen.
        add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
        # expires_off setzt den HTTP Expires-Header auf eine Zeit in der Vergangenheit,
        # was bedeutet, dass der Inhalt bereits als abgelaufen betrachtet wird.
        expires off;
        # default_type text/plain;: Setzt den Standardinhaltstyp auf text/plain.
        default_type text/plain;
    }
}

Als nächstes verlinken wir die Datei in das sites-enabled Verzeichnis, testen ob die Konfiguration nicht fehlerhaft ist lassen nginx die Konfiguration ünernehmen (oder wir starten nginx gleich neu - reload durch restart ersetzen):

cd /etc/nginx/sites-enabled/
sudo ln -s ../sites-available/repo
sudo nginx -t
sudo systemctl reload nginx

Repo vorbereiten (als unprivilegierter Repo-User)

GPG-Schlüssel

Hier wird nur das Erstellen eines neuen Schlüssels behandelt. Trotzdem sei hier kurz erwähnt: Falls ein bestehender privater Schlüssel existiert, lässt sich dieser mit gpg --import <DATEI> importieren.

Einen neuen GPG-Schlüssel erstellen

Führe folgenden Befehl aus und folge den Anweisungen:

gpg --full-gen-key

Folgende Auswahl ist nicht verkehrt:

  • Schlüsseltyp: 1 - RSA and RSA (default)
  • Schlüssellänge: 4096
  • Ablaufdatum: 0 - kein Ablaufdatum (default)
  • Benutzerinformationen: Diese Informationen sind im Schlüssel enthalten und helfen anderen, den Schlüssel zu identifizieren.
    • Name: Ein Name aus mindestens 5 Zeichen
    • E-Mail-Adresse: Optional
    • Kommentar: Optional
  • Passwort: Wähle ein sicheres Passwort für deinen Schlüssel. Du wirst es benötigen, wenn du den Schlüssel zum Signieren von Dateien verwendest.

Falls es ein Berechtigungsproblem gibt: Falls ein Fehler wie gpg: agent_genkey failed: Permission denied ausgegeben wird, könnte das damit zusammenhängen, dass der Benutzer nicht Eigentümer der Terminal-Datei ist (z.B. /dev/pts/5). Wenn ihr euch über SSH direkt mit dem Benutzer anmeldet, sollte das nicht passieren. Wenn ihr jedoch den Benutzer mit z.B. sudo su - repo wechselt, wird das passieren. Wenn ihr den SSH-Zugang für euren Repo-User nicht gesperrt habt, meldet euch per SSH direkt an. Ansonsten hier die Hack-Lösung: Mit dem Befehl tty ermittelt ihr die entsprechende Terminaldatei für den Repo-User. Dann legt ihr als root (bzw als ein privilegierter sudo-User) den Repo-User temporär als Eigentümer fest: sudo chown repo:tty /dev/pts/<N>. Wenn ihr mit gpg fertig seid, den Eigentümer einfach wieder auf den ursprünglichen Eigentümer zurücksetzen.

Privaten GPG-Schlüssel sichern

Wenn man sein Repo länger betreiben will, sollte man den privaten GPG-Schlüssel exportieren und sicher speichern (z.B. in einem CryFS). Mit dem folgenden Befehl lässt sich der Schlüssel im Binärformat exportieren:

gpg --output dateiname.gpg --export-secret-key <name oder ID>

Wer den Key oldschool auf Papier sichern will, kann den Key auch im Textformat exportieren

gpg --armor --output dateiname.asc --export-secret-key <name oder ID>

Verzeichnisstruktur

Ich weiche mit meiner gewählten Verzeichnisstruktur deutlich von den Debian-Standards ab, aber das ist ja das gute bei seinem eigenen Repo: Wir können es organisieren wie wir wollen - Naja, zumindest einen Teil davon ;-)

In unserem Wurzelverzeichnis legen wir uns für das eigentliche Repo ein eigenes Unterverzeichnis an, in diesem Fall deb. Somit können wir bei Bedarf unter anderen Pfaden noch andere Resourcen anbieten, die nicht mit diesem Repo zusammenhängen.

Hier gibt es dann zwei weitere Unterverzeichnisse, wobei eines doch bestimmten Regeln folgen muss, um vom Paketmanager gefunden werden zu können.

/var/www/repo/deb/dists

Die Struktur dieses Verzeichnisses ist essenziell und hat folgendes Schema:

/var/www/repo/deb/dists/
 ├── distro (z.B. kinetic)/
 │   ├── component (z.B. stable oder main)
 │   │   ├── binary-all
 │   │   ├── z.B. binary-amd64
 │   │   ├── z.B. binary-i386
 │   │   ├── ev. andere architekturen
 │   │   └── source
 │   ├── component2/ (z.B. testing)
 │   │   └── ...
 │   └── weitere-komponenten
 ├── distro2 (z.B. lunar)/
 │   └── distro2-komponenten
 │       └── ...
 └── weitere-distros

Wer schon mal in eine sources.list-Datei gesehen hat, dem dürfte dieses Schema bekannt vorkommen. Hier ein einfaches Beispiel:

deb http://repo.example.com/deb/ lunar main testing
  • deb: Diese Quelle enthält Binärpakete (im Gegensatz zu Quelltextpaketen, die mit deb-src gekennzeichnet sind).
  • http://repo.example.com/deb/: Die URL des Repositories.
  • lunar: Die Distribution, für welche dieses Repository vorgesehen ist.
  • main testing: Die Komponenten innerhalb des Repositories. Zum Beispiel könnte main die stabilen Pakete enthalten, während testing Pakete enthält, die noch getestet werden.

/var/www/repo/deb/pool

Die Struktur dieses Verzeichnisses kann frei gewählt werden. Ich habe mich für folgendes Schema entschieden:

/var/www/repo/deb/pool/
 ├── component1 (z.B. main)/
 │   ├── distro1 (z.B. kinetic)
 │   │   ├── paket1
 │   │   │   ├── version1
 │   │   │   ├── version2
 │   │   │   └── ...
 │   │   ├── paket2
 │   │   │   └── ...
 │   │   └── ...
 │   ├── distro2 (z.B. lunar)
 │   │   └── ...
 │   ├── ev. andere distros ...
 │   │   └── ...
 │   ├── all (plattformunabhängige Binärpakete)
 │   │   └── ...
 │   ├── source-all (plattformunabhängige Quellpakete)
 │   │   └── ...
 │   └── source-bin (plattformabhängige Quellpakete)
 │       └── ...
 ├── component2/ (z.B. testing)
 │   ├── distro1 (z.B. kinetic)
 │   │   └── ...
 │   └── weitere-distros
 │       └── ...
 └── weitere-komponenten
     └── weitere-distros
         └── ...

Skripte erstellen

Folgende Dateien einfach im home-Verzeichnis des repo-Users erstellen und die entsprechenden Texte einfügen.

repo_config.sh

In dieser Datei legen wir die wichtigen Konstanten fest, welche von den anderen Skripten zur Ausführung benötigt werden.

Dieses Skript muss nicht ausführbar sein, da wir es nur in andere Skripte einbinden.

# Konstanten, die von scanrepo.sh benutzt werden

# Pfad zum Wurzelverzeichnis des Repo-Webservers
readonly REPO_PATH="/var/www/repo/deb"

# Pfad zur Config-Datei, die die gemeinsame Config für apt-ftparchive enthält
readonly APTFTP_CONF="/home/repo/aptftp-common.conf"

# Der Name der *.list- und *.asc-Datei ohne Endung für das Zielsystem
# ("foobar" -> /etc/apt/sources.list.d/foobar.list)
# ("foobar" -> /etc/apt/trusted.gpg.d/foobar-deb-repo.asc)
readonly TARGET_APT_LIST_NAME="foobar"

# Die URL, unter der das Repository erreichbar ist (inkl Protokoll, z.B. http://)
readonly REPO_URL="http://repo.example.com/deb"

# Die ID des Schlüssels aus dem GPG-Schlüsselbund
# readonly GPG_KEY_ID="$(cat /home/repo/key_id.txt)"
readonly GPG_KEY_ID="<ID>"

# Durch Leerzeichen getrennte Liste. Es kann natürlich auch nur ein Wert angegeben
# werden. distribute-setup-repo-scripts.sh verwendet den ersten Wert
readonly COMPONENTS="main testing"

# durch die Option '-r' ist auch dieses Mapping readonly
declare -rA DISTRO_ARCH_MAP=(
    # Debian 11
    ["bullseye"]="arm64 armhf"
    # Ubuntu 22.10
    ["kinetic"]="amd64 i386"
    # Ubuntu 23.04
    ["lunar"]="amd64 i386"
)

check_repo_config.sh

Dateien, die repo_config.sh benutzen, prüfen mit diesem Skript, ob die Konfiguration keine groben Fehler enthält.

Dieses Skript muss nicht ausführbar sein, da wir es nur in andere Skripte einbinden.

###############################################
# Überprüfung der Konstanten aus repo_config.sh
###############################################

readonly CONFIG_HINT=" Bitte überprüfe repo_config.sh."

# Nicht gesetzte Variablen zulassen, um unsere eigenen Tests durchzuführen
set +u

if [ -z "$REPO_PATH" ]; then
    echo "Fehler: REPO_PATH ist nicht gesetzt.$CONFIG_HINT"
    exit 1
elif [ ! -d "$REPO_PATH" ]; then
    echo "Fehler: Es existiert kein Verzeichnis $REPO_PATH (REPO_PATH).$CONFIG_HINT"
    exit 1
fi

if [ -z "$APTFTP_CONF" ]; then
    echo "Fehler: APTFTP_CONF ist nicht gesetzt.$CONFIG_HINT"
    exit 1
elif [ ! -f "$APTFTP_CONF" ]; then
    echo "Fehler: Es existiert keine Datei $APTFTP_CONF (APTFTP_CONF).$CONFIG_HINT"
    exit 1
fi

if [ -z "$TARGET_APT_LIST_NAME" ]; then
    echo "Fehler: TARGET_APT_LIST_NAME ist nicht gesetzt.$CONFIG_HINT"
    exit 1
fi

if [ -z "$REPO_URL" ]; then
    echo "Fehler: REPO_URL ist nicht gesetzt.$CONFIG_HINT"
    exit 1
fi

if [ -z "$GPG_KEY_ID" ]; then
    echo "Fehler: GPG_KEY_ID ist nicht gesetzt.$CONFIG_HINT"
    exit 1
fi

if ! gpg --list-keys "$GPG_KEY_ID" &>/dev/null; then
    echo "Fehler: Der GPG-Schlüssel mit der ID $GPG_KEY_ID konnte nicht gefunden werden."
    exit 1
fi

if [ -z "$COMPONENTS" ]; then
    echo "Fehler: COMPONENTS ist nicht gesetzt.$CONFIG_HINT"
    exit 1
fi

if [[ "$(declare -p DISTRO_ARCH_MAP 2>/dev/null)" != "declare -A"* ]]; then
    echo "Fehler: DISTRO_ARCH_MAP ist kein gültiges assoziatives Array.$CONFIG_HINT"
    exit 1
fi

if [[ ${#DISTRO_ARCH_MAP[@]} -eq 0 ]]; then
    echo "Fehler: DISTRO_ARCH_MAP ist leer.$CONFIG_HINT"
    exit 1
fi

for key in "${!DISTRO_ARCH_MAP[@]}"; do
    if [[ -z "${DISTRO_ARCH_MAP[$key]}" ]]; then
        echo "Fehler: Keine Architektur(en) für die Distribution $key definiert.$CONFIG_HINT"
        exit 1
    fi
done

# Eine durch Leerzeichen getrennte Liste der gültigen Architekturen erstellen
ALL_ARCHS=$(dpkg-architecture --list-known | tr "\n" " ")
# Überprüfe die Architekturen
for distro in "${!DISTRO_ARCH_MAP[@]}"; do
    for arch in ${DISTRO_ARCH_MAP[$distro]}; do
        if [[ ! " $ALL_ARCHS " =~ " $arch " ]]; then
            echo "Fehler: Architektur $arch für Distribution $distro ist nicht gültig."
            exit 1
        fi
    done
done

# Nicht gesetzte Variablen sollen ab jetzt wieder einen Fehler auslösen
set -u

scanrepo.sh

Dieses Skript sollte ausführbar sein, dass wir es bequemer ausführen können (chmod 700 scanrepo.sh).

#!/bin/bash

# -e: Beende das Skript bei einem Fehler
# -u: Beende das Skript, wenn eine nicht gesetzte Variable benutzt wird
# -o pipefail: Beende das Skript, wenn ein Kommando in einer Pipeline fehlschlägt
set -euo pipefail

error_handler() {
    local exit_code="$?"
    echo "Das Skript wurde durch einen Fehler in Zeile $1 mit dem Fehlercode $exit_code beendet"
    exit $exit_code
}

# Wenn das Skript durch einen Fehler abgebrochen wird, führe error_handler($LINENO) aus
trap 'error_handler $LINENO' ERR

# Einbinden der Konfigurationsdatei
source ./repo_config.sh

# Überprüfung der Konstanten aus repo_config.sh
source ./check_repo_config.sh

# Funktion, die ähnliche Aufrufe für apt-ftparchive abstrahiert
do_apt_ftparchive_common() {
    local append="$1"
    local dist="$2"
    local comp="$3"
    local archs="$4"
    local search_in="$5"
    local out_file="$6"
    local cmd="$7"

    local extra_options=()
    if [ "$#" -ge 8 ]; then
        # Alle zusätzlichen Argumente ab dem 8. Parameter, falls vorhanden
        extra_options=("${@:8}")
    fi

    # Optionen, die immer gesetzt werden
    local all_options=(
        -c "$APTFTP_CONF"
        -o "APT::FTPArchive::Release::Codename=$dist"
        -o "APT::FTPArchive::Release::Suite=$comp"
        -o "APT::FTPArchive::Release::Architectures=$archs"
        -o "APT::FTPArchive::Release::Components=$comp"
    )

    # Füge extra_options hinzu, falls vorhanden
    for opt in "${extra_options[@]}"; do
        all_options+=(-o "$opt")
    done

    if [[ "$append" == true ]]; then
        # füge die Ausgabe von apt-ftparchive and out_file an
        apt-ftparchive "${all_options[@]}" "$cmd" "$search_in" >> "$out_file"
    else
        # Überschreiben/neu anlegen der Datei
        apt-ftparchive "${all_options[@]}" "$cmd" "$search_in" > "$out_file"
    fi
}

# Erstelle eine Packages-Index-Datei
do_packages() {
    do_apt_ftparchive_common false "$@" packages
}

# Erstelle eine Sources-Index-Datei
do_sources() {
    do_apt_ftparchive_common true "$@" sources "APT::FTPArchive::Release::SourceComponent=$comp"
}

# Erstelle die Release-Datei
do_release() {
    do_apt_ftparchive_common false "$@" release
}

# Erstelle komprimierte Varianten einer Datei
do_compress() {
    local file="$1"
    gzip -c "$file" > "$file.gz"
    bzip2 -c "$file" > "$file.bz2"
}

# Das pool-Verzeichnis für die entsprechende Distro nach Paketen durchsuchen
do_scan_repo() {
    local dist="$1"
    local dist_dir="dists/$dist"
    local dist_archs=${DISTRO_ARCH_MAP["$dist"]}
    # Suche Pakete für die entsprechende Repo-Komponente
    for comp in $COMPONENTS; do
        dist_comp_bin_all_dir="$dist_dir/$comp/binary-all"
        dist_comp_src_dir="$dist_dir/$comp/source"
        deb_comp_all_dir="pool/$comp/all"
        src_all_comp_dir="pool/$comp/source-all"
        src_bin_comp_dir="pool/$comp/source-bin"
        dist_comp_bin_all_file="$dist_comp_bin_all_dir/Packages"
        dist_comp_src_file="$dist_comp_src_dir/Sources"
        mkdir -p "$dist_comp_bin_all_dir"
        mkdir -p "$dist_comp_src_dir"
        mkdir -p "$deb_comp_all_dir"
        mkdir -p "$src_all_comp_dir"
        mkdir -p "$src_bin_comp_dir"
        # Erstelle die Packages-Index-Datei für plattformunabhängige Pakete
        do_packages "$dist" "$comp" "all" "$deb_comp_all_dir" "$dist_comp_bin_all_file"
        # Erstelle komprimierte Varianten der Sources-Index-Datei
        do_compress "$dist_comp_bin_all_file"
        # Lösche den Inhalt der Sources-Index-Datei (oder erstelle die Datei)
        echo "" > "$dist_comp_src_file"
        # Hänge die Quellen für plattformunabhängige Pakete an die Sources-Index-Datei an
        do_sources "$dist" "$comp" "all" "$src_all_comp_dir" "$dist_comp_src_file"
        # Hänge die Quellen für plattformabhängige Pakete an die Sources-Index-Datei an
        do_sources "$dist" "$comp" "$dist_archs" "$src_bin_comp_dir" "$dist_comp_src_file"
        # Erstelle komprimierte Varianten der Sources-Index-Datei
        do_compress "$dist_comp_src_file"
        # Suche nach Paketen für eine bestimmte Architektur
        for arch in $dist_archs; do
            dist_comp_bin_arch_dir="$dist_dir/$comp/binary-$arch"
            dist_comp_bin_arch_file="$dist_comp_bin_arch_dir/Packages"
            deb_comp_dist_arch_dir="pool/$comp/$dist/$arch"
            mkdir -p "$dist_comp_bin_arch_dir"
            mkdir -p "$deb_comp_dist_arch_dir"
            # Erstelle die Packages-Index-Datei für die entsprechende Architektur
            do_packages "$dist" "$comp" "$arch" "$deb_comp_dist_arch_dir" "$dist_comp_bin_arch_file"
            # Erstelle komprimierte Varianten der Packages-Index-Datei
            do_compress "$dist_comp_bin_arch_file"
        done
    done
    # Erstelle die Release-Datei
    do_release "$dist" "$comp" "$dist_archs all" "$dist_dir" "$dist_dir/Release"
    # GPG (GNU Privacy Guard) wird verwendet, um die Release-Datei zu signieren.
    # --default-key $GPG_KEY_ID: Verwendet den Schlüssel mit der angegebenen ID zum Signieren.
    # --yes: Automatische Bestätigung aller Abfragen (überspringt Benutzereingabeaufforderungen).
    # -b: Erstellt eine abgetrennte Signatur (die Signatur wird in einer separaten Datei gespeichert).
    # -a: Erstellt eine ASCII-armored-Signatur (die Ausgabe wird textfreundlich gemacht).
    # -o "$dist_dir/Release.gpg": Gibt an, wo die Signatur gespeichert werden soll.
    # "$dist_dir/Release": Die Datei, die signiert werden soll.
    gpg --default-key $GPG_KEY_ID --yes -bao "$dist_dir/Release.gpg" "$dist_dir/Release"
    # GPG wird verwendet, um die Release-Datei mit einer klaren Signatur zu signieren.
    # --default-key $GPG_KEY_ID: Verwendet den Schlüssel mit der angegebenen ID zum Signieren.
    # --yes: Automatische Bestätigung aller Abfragen.
    # --clear-sign: Erstellt eine klare Signatur (die signierte Datei wird zusammen mit der Signatur gespeichert).
    # --output "$dist_dir/InRelease": Gibt an, wo die signierte Datei gespeichert werden soll.
    # "$dist_dir/Release": Die Datei, die signiert werden soll.
    gpg --default-key $GPG_KEY_ID --yes --clear-sign --output "$dist_dir/InRelease" "$dist_dir/Release"
}

# Wechsle in das Wurzelverzeichnis des Repo-Webservers
cd "$REPO_PATH"

# Suche nach Paketen für die konfigurierten Distros
for distro in "${!DISTRO_ARCH_MAP[@]}"; do
    do_scan_repo $distro
done

make_repo_tree.sh

Dieses Skript sollte ausführbar sein, dass wir es bequemer ausführen können (chmod 700 scanrepo.sh).

#!/bin/bash

# -e: Beende das Skript bei einem Fehler
# -u: Beende das Skript, wenn eine nicht gesetzte Variable benutzt wird
# -o pipefail: Beende das Skript, wenn ein Kommando in einer Pipeline fehlschlägt
set -euo pipefail

error_handler() {
    local exit_code="$?"
    echo "Das Skript wurde durch einen Fehler in Zeile $1 mit dem Fehlercode $exit_code beendet"
    exit $exit_code
}

# Wenn das Skript durch einen Fehler abgebrochen wird, führe error_handler($LINENO) aus
trap 'error_handler $LINENO' ERR

# Einbinden der Konfigurationsdatei
source ./repo_config.sh

# Überprüfung der Konstanten aus repo_config.sh
source ./check_repo_config.sh

make_repo_tree() {
    local distro_archs
    for comp in $COMPONENTS; do
        for distro in "${!DISTRO_ARCH_MAP[@]}"; do
            distro_archs="${DISTRO_ARCH_MAP[$distro]}"
            mkdir -p "$REPO_PATH/pool/$comp/$distro"
            mkdir -p "$REPO_PATH/pool/$comp/all"
            mkdir -p "$REPO_PATH/pool/$comp/source-all"
            mkdir -p "$REPO_PATH/pool/$comp/source-bin"
            mkdir -p "$REPO_PATH/dists/$distro/$comp/binary-all"
            mkdir -p "$REPO_PATH/dists/$distro/$comp/source"
            for arch in $distro_archs; do
                mkdir -p "$REPO_PATH/dists/$distro/$comp/binary-$arch"
            done
        done
    done
}

make_repo_tree

Pakete hochladen

scp

sftp