In diesem Beitrag zeige ich, wie du ein Debian-Paket-Repository für dein Yocto-Image einrichtest. Damit kannst du Anwendungen zentral auf einem Server bereitstellen und von einem laufenden System aus aktualisieren, ohne jedes Mal das Image neu auf eine SD-Karte schreiben zu müssen.
Das Paket-Repository wird Debian-Pakete zur Verfügung stellen, und das Yocto-Image wird um die notwendigen apt-Werkzeuge erweitert. Ich zeige dir, wie du ein Shell-Skript erstellst, das aus der Yocto-Build-Verzeichnisstruktur ein Debian-konformes Repository generiert. Das fertige Repository wird anschließend mit einem Webserver in einem Docker-Container bereitgestellt. Der Docker-Container erkennt zudem Änderungen an den Paketen automatisch und aktualisiert das Repository selbstständig.
Debian-Paketmanagement aktivieren
Als Erstes passen wir die Datei local.conf im Konfigurationsverzeichnis (conf) des Build-Verzeichnisses rpi-build an und erweitern die Variable PACKAGE_CLASSES um das DEB-Paketsystem:
# ~/yocto/rpi-build/conf/local.conf
...
PACKAGE_CLASSES ?= "package_rpm package_deb"
...
Anschließend wird das Image-Rezept raspilab-image.bb erweitert, um das Debian-Paketmanagement und das Tool apt hinzuzufügen:
# ~/yocto/poky-kirkstone/meta-raspilab/recipes-core/images/raspilab-image.bb
...
IMAGE_FEATURES += "package-management splash"
...
CORE_OS = " \
...
apt \
"
Unterschiede zwischen Yocto und Debian Repository-Struktur
Die Verzeichnisstruktur der Debian-Pakete in Yocto unterscheidet sich deutlich von der Struktur eines Debian-konformen Repositories:
# Yocto-Verzeichnisstruktur (tmp/deploy/deb)
tmp/deploy/deb/
├── cortexa72/
│ ├── acl_2.3.1-r0_arm64.deb
│ ├── bash_5.1.16-r0_arm64.deb
│ └── libfoo_1.0.0-r0_arm64.deb
└── raspberrypi4_64/
├── acl_2.3.1-r0_arm64.deb
├── bash_5.1.16-r0_arm64.deb
└── libfoo_1.0.0-r0_arm64.deb
# Debian-konformes APT-Repository (www-root)
www-root/
├── pool/
│ └── main/
│ ├── acl_2.3.1-r0_arm64.deb
│ ├── bash_5.1.16-r0_arm64.deb
│ └── libfoo_1.0.0-r0_arm64.deb
└── dists/
└── stable/
├── Release
└── main/
├── binary-amd64/
│ ├── Packages
│ ├── Packages.gz
│ ├── Packages.bz2
│ └── Release
├── binary-arm64/
│ ├── Packages
│ ├── Packages.gz
│ ├── Packages.bz2
│ └── Release
└── binary-i386/
├── Packages
├── Packages.gz
├── Packages.bz2
└── Release
Während Yocto eine einfache Struktur verwendet, trennt Debian den Paketpool klar von den Indexdateien. Um diese Struktur zu erzeugen, nutzen wir ein spezielles Shell-Skript.
Automatisiertes Repository per Skript erstellen
Zusammen mit ChatGPT habe ich ein Shell-Skript entwickelt, das automatisch die Debian-konforme Repository-Struktur erstellt. Das Skript sowie das passende Dockerfile findest du in meinem GitHub-Repository.
Im Kopf des Skripts werden einige wichtige Variablen gesetzt:
#!/bin/bash
set -e
REPO_BASE="/repo" # Verzeichnis, in dem Yocto seine Pakete ablegt
WEBROOT="/www-root" # Verzeichnis für das APT-Repository
DIST="stable" # Distribution-Name
COMPONENT="main" # Komponentenname
TMPROOT="/tmp/aptrepo" # Temporäres Verzeichnis zur Index-Erstellung
REPO_BASEverweist auf das Verzeichnis mit den Yocto-erzeugten .deb-Dateien. Im Docker-Container zeigt dies meist auf ein gemountetes Volume.WEBROOTist das Zielverzeichnis für das Debian-konforme Repository, aus dem der Webserver die Daten bereitstellt.DISTundCOMPONENTgeben Struktur und Namen des Repositories an, etwa „stable/main“.TMPROOTist ein temporäres Arbeitsverzeichnis, das nach der Index-Erstellung wieder gelöscht wird.
Das Skript erledigt anschließend folgende Schritte:
Vorbereitung des Zielverzeichnisses: Bestehende Daten werden entfernt, um sicherzustellen, dass die Repository-Struktur sauber neu aufgebaut wird.
echo "1) Webroot vorbereiten"
rm -rf "$WEBROOT/dists/$DIST" "$WEBROOT/pool"
mkdir -p "$WEBROOT/pool/$COMPONENT"
mkdir -p "$WEBROOT/dists/$DIST/$COMPONENT"
Symlink-Erstellung: Statt die .deb-Dateien zu kopieren, werden symbolische Links erstellt, um Speicherplatz zu sparen und Zeit zu sparen.
echo "2) Symlinks aller .deb aus Yocto nach pool/$COMPONENT"
for arch_dir in "$REPO_BASE"/*/; do
[ -d "$arch_dir" ] || continue
for deb in "$arch_dir"/*.deb; do
[ -f "$deb" ] || continue
ln -sf "$deb" "$WEBROOT/pool/$COMPONENT/$(basename "$deb")"
done
done
Architektur-Erkennung: Das Skript analysiert die Dateinamen der Pakete, um die Zielarchitekturen zu ermitteln, für die Paketlisten erstellt werden sollen.
echo "3) Architekturen ermitteln"
ARCH_LIST=()
for f in "$WEBROOT/pool/$COMPONENT"/*.deb; do
base=$(basename "$f")
arch="${base##*_}"
arch="${arch%.deb}"
ARCH_LIST+=("$arch")
done
ARCH_LIST=( $(printf "%s\n" "${ARCH_LIST[@]}" | sort -u) )
echo " Gefundene Architekturen: ${ARCH_LIST[*]}"
Index-Erstellung pro Architektur: Dies ist der aufwändigste und wichtigste Teil des Skripts.
Für jede erkannte Zielarchitektur wird ein separates temporäres Verzeichnis erstellt, das die Struktur eines Debian-Paketpools nachbildet. Dort werden symbolische Links zu den passenden .deb-Paketen eingefügt. Anschließend wird mit dpkg-scanpackages eine Paketliste erzeugt, die alle verfügbaren Pakete und deren Metadaten wie Version, Abhängigkeiten, Prüfsummen und vor allem der Pfad zur Datei im Repository enthält.
Dieser Pfad ist entscheidend, da apt genau diesen nutzt, um Pakete vom Server herunterzuladen. Die erstellte Liste wird dann in mehreren komprimierten Formaten (gzip, bzip2, xz, lzma) gespeichert, um maximale Kompatibilität mit verschiedenen Clients zu gewährleisten. Abschließend werden die Paketlisten in das richtige Verzeichnis im Repository verschoben.
echo "4) Erzeuge arch‑spezifische Package‑Indizes"
for arch in "${ARCH_LIST[@]}"; do
echo " → Architektur: $arch"
BIN_DIR="$WEBROOT/dists/$DIST/$COMPONENT/binary-$arch"
mkdir -p "$BIN_DIR"
# Temp‑Verzeichnis mit pool-Layout
TMPPOOL="$TMPROOT/$arch/pool/$COMPONENT"
rm -rf "$TMPROOT/$arch"
mkdir -p "$TMPPOOL"
# nur passende Debs verlinken
PATTERN="*_${arch}.deb"
for deb in "$WEBROOT/pool/$COMPONENT"/$PATTERN; do
[ -f "$deb" ] || continue
ln -sf "$deb" "$TMPPOOL/$(basename "$deb")"
done
# Index erzeugen (im Temp‑Webroot, relativer Pfad pool/...)
pushd "$TMPROOT/$arch" > /dev/null
dpkg-scanpackages "pool/$COMPONENT" > "Packages"
echo " → Packages erzeugt"
# Komprimierte Varianten direkt im BIN_DIR
gzip -c9 "$TMPROOT/$arch/Packages" > "$BIN_DIR/Packages.gz"
bzip2 -c "$TMPROOT/$arch/Packages" > "$BIN_DIR/Packages.bz2"
xz -c "$TMPROOT/$arch/Packages" > "$BIN_DIR/Packages.xz"
lzma -c "$TMPROOT/$arch/Packages" > "$BIN_DIR/Packages.lzma"
# verschiebe
mv "$TMPROOT/$arch/Packages" "$BIN_DIR/Packages"
echo " Packages und Kompression in binary-$arch erstellt"
popd > /dev/null
done
Generierung der Release-Datei: Diese Datei enthält die Metadaten des Repositories, inklusive Prüfsummen.
echo "5) Erzeuge zentrale Release‑Datei"
ARCH_STRING=$(printf "%s\n" "${ARCH_LIST[@]}" | xargs)
cat > "$WEBROOT/dists/$DIST/Release.conf" <<EOF
APT::FTPArchive::Release {
Origin "Raspilab";
Label "Raspilab Repo";
Suite "$DIST";
Codename "$DIST";
Architectures "$ARCH_STRING";
Components "$COMPONENT";
Description "Raspilab DEB repo for Yocto builds";
};
EOF
apt-ftparchive -c "$WEBROOT/dists/$DIST/Release.conf" release "$WEBROOT/dists/$DIST" \
> "$WEBROOT/dists/$DIST/Release"
rm "$WEBROOT/dists/$DIST/Release.conf"
Aufräumen: Temporär angelegte Verzeichnisse werden gelöscht, um das System sauber zu halten.
echo "6) Aufräumen temporäre Verzeichnisse"
rm -rf "$TMPROOT"
echo "Fertig: Repository verfügbar unter $WEBROOT"
Bereitstellung des Paket-Repositories per Python-Webserver
Bevor wir das Repository in einem Docker-Container betreiben, kannst du es auch einfach mit dem in Python integrierten HTTP-Server testen. Dazu wechselst du in das Root-Verzeichnis des Repositories (z. B. /www-root) und startest den Webserver mit folgendem Befehl:
cd /www-root
python3 -m http.server 8000
Das Repository ist nun unter http://localhost:8000 erreichbar und kann für Tests direkt in einem Yocto-System verwendet werden.
Bereitstellung des Paket-Repositories per Docker
Um das Paket-Repository dauerhaft und automatisiert bereitzustellen, empfiehlt sich der Einsatz eines Docker-Containers. Der Container übernimmt nicht nur die Bereitstellung per Webserver, sondern auch die Überwachung auf neue oder veränderte .deb-Pakete. Erkennt der Container Änderungen, wird das Repository automatisch neu aufgebaut.
Startskript für den Container
Das Startskript überwacht das Repository-Verzeichnis mithilfe von inotifywait und führt bei Bedarf das Script zur Repository-Erzeugung erneut aus:
#!/bin/bash
set -e
REPO_DIR=/repo
REBUILD_DELAY=20
# Erstinitialisierung
/usr/local/bin/generate_deb_repo.sh
# Debounce-Logik
trigger_rebuild() {
if [ -f /tmp/rebuild.lock ]; then
# Schon geplant → nicht doppelt starten
return
fi
touch /tmp/rebuild.lock
echo "Rebuild in $REBUILD_DELAY Sekunden geplant..."
(
sleep "$REBUILD_DELAY"
echo "Starte Rebuild..."
/usr/local/bin/generate_deb_repo.sh
rm -f /tmp/rebuild.lock
) &
}
# Watchdog: auf neue .deb-Dateien achten
echo "Beobachte Änderungen an .deb-Dateien unter $REPO_DIR..."
inotifywait -m -e create -e modify -e delete -r --format '%w%f' "$REPO_DIR" --exclude 'Packages(\..+)?|Release(\..+)?|apt-ftparchive.conf' |
while read changed_file; do
[[ "$changed_file" == *.deb ]] && trigger_rebuild
done &
# Webserver starten
echo "Starte HTTP-Server..."
cd /www-root
exec python3 -m http.server 8000
Dockerfile
Das folgende Dockerfile installiert alle notwendigen Tools und kopiert die beiden Skripte in das Image:
FROM python:3.11-slim
# Tools installieren
RUN apt-get update && apt-get install -y \
dpkg-dev apt-utils xz-utils bzip2 lzma inotify-tools \
&& rm -rf /var/lib/apt/lists/*
# Arbeitsverzeichnis im Container
WORKDIR /repo
# Skript in Container kopieren
COPY start-repo.sh /usr/local/bin/start-repo.sh
COPY generate_deb_repo.sh /usr/local/bin/generate_deb_repo.sh
RUN chmod +x /usr/local/bin/start-repo.sh /usr/local/bin/generate_deb_repo.sh
# Startbefehl: Repository generieren und Webserver starten
CMD ["/usr/local/bin/start-repo.sh"]
Docker Compose Datei
Mit dieser Compose-Datei kannst du dein Repository mit einem Befehl starten. Sie bindet das Paketverzeichnis als Volume ein und startet den Webserver unter Port 8000:
services:
repo:
build: .
container_name: raspilab-deb-repo
volumes:
- deb-repo:/repo
ports:
- "8000:8000"
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 5s
retries: 3
volumes:
deb-repo:
driver: local
driver_opts:
type: none
o: bind
device: ~/yocto/rpi-build/tmp/deploy/deb
Mit docker compose up --build -d wird das Repository erstellt und gestartet. Bei Änderungen an .deb-Dateien erfolgt automatisch ein Rebuild der Paketlisten. Das Repository steht nun unter http://localhost:8000 bereit.
Mit Docker Compose kannst du das Repository bequem bereitstellen:
docker compose up -d
Zum Anhalten:
docker compose down
Damit steht dir jederzeit ein aktuelles und automatisiertes Debian-Paket-Repository für dein Yocto-basiertes System zur Verfügung.
