From 4af879b2e3d33ba42fc4716412fc0384fa65893a Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Thu, 27 Aug 2026 21:05:08 +0200 Subject: [PATCH] =?UTF-8?q?Feat:=20F=C3=BCgt=20Konfiguration=20und=20autom?= =?UTF-8?q?atische=20Erstellung=20eines=20Docker-Containers=20dieses=20Pro?= =?UTF-8?q?jekts=20hinzu.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 7 +++ .gitea/workflows/main.yaml | 31 +++++++++ .gitea/workflows/testing.yaml | 33 ++++++++++ AGENTS.md | 19 +++++- Dockerfile | 37 +++++++++++ README.md | 115 +++++++++++++++++++++++++++++++++- docker-compose.example.yml | 24 +++++++ src/gitea.rs | 10 +-- 8 files changed, 263 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.example.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c43dcc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +target/ +!target/release/mirror-package +!target/x86_64-unknown-linux-gnu/release/mirror-package +.git/ +.github/ +.gitea/ +tests/ diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml index a51f514..c6434ec 100644 --- a/.gitea/workflows/main.yaml +++ b/.gitea/workflows/main.yaml @@ -181,3 +181,34 @@ jobs: "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" echo "Asset ${filename} erfolgreich hochgeladen." done + + - name: Build and Publish Docker Container Image (Stable) + env: + GITEA_URL: ${{ gitea.server_url || github.server_url }} + REPO: ${{ gitea.repository || github.repository }} + REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }} + REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }} + TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }} + run: | + VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" + REGISTRY_HOST=$(echo "${GITEA_URL}" | sed -E 's|^https?://||; s|/.*$||') + IMAGE_NAME=$(echo "${REGISTRY_HOST}/${REPO_OWNER}/${REPO_NAME}" | tr '[:upper:]' '[:lower:]') + + echo "Authentifiziere an der Container Registry ${REGISTRY_HOST}..." + echo "${TOKEN}" | docker login "${REGISTRY_HOST}" -u "${REPO_OWNER}" --password-stdin + + echo "Baue Docker-Container Image (Stable)..." + docker build \ + --build-arg TARGET_BIN=target/x86_64-unknown-linux-gnu/release/mirror-package \ + -t "${IMAGE_NAME}:latest" \ + -t "${IMAGE_NAME}:${VERSION}" \ + -t "${IMAGE_NAME}:v${VERSION}" \ + -t "${IMAGE_NAME}:${VERSION}.${BUILD_NUMBER}" \ + . + + echo "Veröffentliche Docker-Images nach ${IMAGE_NAME}..." + docker push "${IMAGE_NAME}:latest" + docker push "${IMAGE_NAME}:${VERSION}" + docker push "${IMAGE_NAME}:v${VERSION}" + docker push "${IMAGE_NAME}:${VERSION}.${BUILD_NUMBER}" + echo "Docker-Images (Stable & latest) erfolgreich veröffentlicht." diff --git a/.gitea/workflows/testing.yaml b/.gitea/workflows/testing.yaml index eb05f93..47047aa 100644 --- a/.gitea/workflows/testing.yaml +++ b/.gitea/workflows/testing.yaml @@ -181,3 +181,36 @@ jobs: "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}" echo "Asset ${filename} erfolgreich hochgeladen." done + + - name: Build and Publish Docker Container Image (Testing) + env: + GITEA_URL: ${{ gitea.server_url || github.server_url }} + REPO: ${{ gitea.repository || github.repository }} + REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }} + REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }} + TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }} + run: | + VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" + REGISTRY_HOST=$(echo "${GITEA_URL}" | sed -E 's|^https?://||; s|/.*$||') + IMAGE_NAME=$(echo "${REGISTRY_HOST}/${REPO_OWNER}/${REPO_NAME}" | tr '[:upper:]' '[:lower:]') + + echo "Authentifiziere an der Container Registry ${REGISTRY_HOST}..." + echo "${TOKEN}" | docker login "${REGISTRY_HOST}" -u "${REPO_OWNER}" --password-stdin + + echo "Baue Docker-Container Image (Testing / Preview)..." + docker build \ + --build-arg TARGET_BIN=target/x86_64-unknown-linux-gnu/release/mirror-package \ + -t "${IMAGE_NAME}:testing" \ + -t "${IMAGE_NAME}:${VERSION}-preview" \ + -t "${IMAGE_NAME}:${VERSION}-testing" \ + -t "${IMAGE_NAME}:${VERSION}-preview.${BUILD_NUMBER}" \ + -t "${IMAGE_NAME}:testing-${BUILD_NUMBER}" \ + . + + echo "Veröffentliche Docker-Images nach ${IMAGE_NAME}..." + docker push "${IMAGE_NAME}:testing" + docker push "${IMAGE_NAME}:${VERSION}-preview" + docker push "${IMAGE_NAME}:${VERSION}-testing" + docker push "${IMAGE_NAME}:${VERSION}-preview.${BUILD_NUMBER}" + docker push "${IMAGE_NAME}:testing-${BUILD_NUMBER}" + echo "Docker-Images (Testing) erfolgreich veröffentlicht." diff --git a/AGENTS.md b/AGENTS.md index 3cc456d..275cfca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,9 @@ Dieses Dokument dient als technischer Leitfaden und Kontextdokument für KI-Codi │ ├── config_tests.rs # Tests für Konfigurationsverwaltung & Pfade │ ├── gitea_tests.rs # Tests für Gitea Routing & Endpunkte │ └── github_tests.rs # Tests für Paket-Klassifizierung & Repo-Parsing +├── .dockerignore # Ausschlussregeln für Container-Builds +├── Dockerfile # Minimales & gehärtetes Runtime-Container-Image +├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose ├── Cargo.toml # Projekt-Manifest & Metadaten für deb, rpm und arch ├── LICENSE # Lizenztext ├── README.md # Benutzerdokumentation @@ -77,6 +80,16 @@ Bei Änderungen an Binärnamen, Abhängigkeiten oder Beschreibungen müssen die - **Generizität**: Die Skripte dürfen keine hardcodierten Anwendungsnamen, spezifischen Abhängigkeiten oder projektspezifischen URLs enthalten. Alle Werte müssen dynamisch aus `Cargo.toml` oder Umgebungsvariablen (`BUILD_NUMBER`, `GITEA_URL`, `REPO`, `TOKEN`) ermittelt werden. - **Python-Kompatibilität**: Verwende Standard-Python 3 ohne externe PyPI-Abhängigkeiten (nur Standardbibliothek). +### 3.4 Container-Sicherheit & Persistenz +- **Sicherheitsrichtlinien**: + - Unprivilegierter Benutzer: Der Container läuft unter `appuser` (UID `10001`, GID `10001`). + - Read-Only Rootfs: Kompatibel mit `--read-only` bzw. `read_only: true`. + - Berechtigungen: Alle Linux-Capabilities können gedroppt werden (`--cap-drop=ALL`), `no-new-privileges: true`. +- **Persistente Pfade**: + - `/home/appuser/.config/mirror-package`: Speichert Konfiguration (`config.toml`). + - `/home/appuser/.local/state/mirror-package`: Speichert Logdateien & Status. + - `/tmp`: Temporärer Puffer beim Herunterladen von Paketen (idealerweise via `tmpfs`). + --- ## 4. Workflows: Bauen, Testen & Validieren @@ -107,7 +120,7 @@ python3 scripts/package-arch.py --arch x86_64 --pkgrel 1 ## 5. CI/CD-Pipeline Details - **Trigger**: - - `push` auf `main`: Baut Binaries für alle 3 Architekturen, baut `.deb`, `.rpm` und `.pkg.tar.zst`, lädt sie in die Gitea Package Registry hoch und erstellt ein Gitea Release `v`. - - `push` auf `testing`: Baut Binaries & Pakete für den `testing`-Kanal und erstellt ein Pre-Release `v-preview`. + - `push` auf `main`: Baut Binaries für alle 3 Architekturen, baut `.deb`, `.rpm` und `.pkg.tar.zst`, lädt sie in die Gitea Package Registry hoch, erstellt ein Gitea Release `v` und baut/veröffentlicht das Docker-Container-Image mit den Tags `:latest`, `:`, `:v` und `:.`. + - `push` auf `testing`: Baut Binaries & Pakete für den `testing`-Kanal, erstellt ein Pre-Release `v-preview` und baut/veröffentlicht das Docker-Container-Image ausschließlich mit eindeutigen Testing-Tags (`:testing`, `:-preview`, `:-testing`, `:-preview.`, `:testing-`). Der Tag `:latest` ist strikt dem `main`-Workflow vorbehalten. - **Secrets**: - - `PACKAGE_TOKEN` (bzw. Fallback-Token-Namen wie `RELEASE_TOKEN`, `GITEA_TOKEN`) wird für API-Zugriffe auf Gitea Packages und Releases verwendet. + - `PACKAGE_TOKEN` (bzw. Fallback-Token-Namen wie `RELEASE_TOKEN`, `GITEA_TOKEN`) wird für API-Zugriffe auf Gitea Packages, Container Registry und Releases verwendet. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0935023 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# syntax=docker/dockerfile:1 + +# Minimal and secure runtime image +FROM debian:bookworm-slim AS runtime + +# Install CA certificates and minimal runtime dynamic libraries +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + libssl3 \ + zlib1g \ + libzstd1 && \ + rm -rf /var/lib/apt/lists/* /var/cache/apt/* && \ + groupadd -g 10001 appuser && \ + useradd -u 10001 -g appuser -d /home/appuser -m -s /usr/sbin/nologin appuser && \ + mkdir -p /home/appuser/.config/mirror-package \ + /home/appuser/.local/state/mirror-package \ + /home/appuser/.local/share/mirror-package \ + /etc/mirror-package && \ + chown -R appuser:appuser /home/appuser /etc/mirror-package + +# Build argument pointing to the prebuilt binary +ARG TARGET_BIN=target/release/mirror-package +COPY ${TARGET_BIN} /usr/local/bin/mirror-package + +# Set strict executable permissions +RUN chmod 0755 /usr/local/bin/mirror-package + +# Run as unprivileged user +USER 10001:10001 +WORKDIR /home/appuser + +# Expose volume mount points for configuration and persistent state/logs +VOLUME ["/home/appuser/.config/mirror-package", "/home/appuser/.local/state/mirror-package"] + +ENTRYPOINT ["/usr/local/bin/mirror-package"] +CMD ["--help"] diff --git a/README.md b/README.md index 07f8917..7298824 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,114 @@ mirror-package sync --dry-run --- +## Docker & Container Usage + +`mirror-package` is packaged and published as an ultra-secure, lightweight container image to the Gitea Container Registry. + +### 1. Image Registry & Tagging Strategy + +- **Stable Releases (`main` branch)**: + - `//mirror-package:latest` + - `//mirror-package:` + - `//mirror-package:v` + - `//mirror-package:.` +- **Testing & Preview Releases (`testing` branch)**: + - `//mirror-package:testing` + - `//mirror-package:-preview` + - `//mirror-package:-testing` + - `//mirror-package:-preview.` + - `//mirror-package:testing-` + - *(Note: The `:latest` tag is strictly reserved for the stable `main` workflow).* + +### 2. Persistent Storage Paths + +To persist configurations, tracked repository states, and logs across container restarts, mount the following container paths to host directories or Docker volumes: + +| Container Path | Purpose | Recommended Mount Type | +|---|---|---| +| `/home/appuser/.config/mirror-package` | Stores `config.toml` (credentials & repository list) | Host Directory / Volume (rw) | +| `/home/appuser/.local/state/mirror-package` | Persistent application logs & state files | Host Directory / Volume (rw) | +| `/tmp` | Download & stream buffer for package binaries | `tmpfs` (RAM / tempfs, rw, noexec) | + +### 3. Security Hardening + +The container is built with security as the highest priority: +- **Unprivileged User**: Runs as `appuser` (UID `10001`, GID `10001`), never as `root`. +- **Read-Only Root Filesystem**: Fully operational with `--read-only` / `read_only: true`. +- **No Capabilities**: All Linux capabilities can be safely dropped (`--cap-drop=ALL`). +- **No Privilege Escalation**: Enforces `no-new-privileges:true`. +- **Minimal Image Size**: Based on Debian Bookworm slim, containing only CA certificates and necessary shared libraries (~40 MB). + +### 4. Running via Docker CLI + +```bash +# Initialize / configure credentials +docker run --rm \ + --name mirror-package \ + --read-only \ + --cap-drop=ALL \ + --security-opt=no-new-privileges:true \ + --tmpfs /tmp:rw,noexec,nosuid,size=2G \ + -v "./config:/home/appuser/.config/mirror-package" \ + -v "./state:/home/appuser/.local/state/mirror-package" \ + gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \ + config set \ + --gitea-url "https://gitea.creative-dragonslayer.de" \ + --gitea-token "your_token" \ + --registry-owner "Linuxapps" + +# Add repositories +docker run --rm \ + --read-only \ + --cap-drop=ALL \ + --security-opt=no-new-privileges:true \ + --tmpfs /tmp:rw,noexec,nosuid,size=2G \ + -v "./config:/home/appuser/.config/mirror-package" \ + -v "./state:/home/appuser/.local/state/mirror-package" \ + gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \ + add raspberrypi/rpi-imager + +# Run synchronization (can be triggered by a host cronjob or scheduler) +docker run --rm \ + --read-only \ + --cap-drop=ALL \ + --security-opt=no-new-privileges:true \ + --tmpfs /tmp:rw,noexec,nosuid,size=2G \ + -v "./config:/home/appuser/.config/mirror-package" \ + -v "./state:/home/appuser/.local/state/mirror-package" \ + gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \ + sync +``` + +### 5. Docker Compose Example (`docker-compose.yml`) + +```yaml +services: + mirror-package: + image: gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest + container_name: mirror-package + user: "10001:10001" + read_only: true + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + volumes: + - ./config:/home/appuser/.config/mirror-package + - ./state:/home/appuser/.local/state/mirror-package + tmpfs: + - /tmp:rw,noexec,nosuid,size=2G + environment: + - GITEA_URL=https://gitea.creative-dragonslayer.de + - GITEA_TOKEN=your_gitea_api_token + - REGISTRY_OWNER=Linuxapps + - GITHUB_TOKEN=your_optional_github_pat + - LOG_LEVEL=info + command: ["sync"] +``` + +--- + ## Repository Structure ```text @@ -91,8 +199,8 @@ mirror-package sync --dry-run │ └── config.toml # Linker & Cargo configuration ├── .gitea/ │ └── workflows/ -│ ├── main.yaml # CI/CD: Release & Multi-Arch Package Build -│ └── testing.yaml # CI/CD: Preview & Testing Build +│ ├── main.yaml # CI/CD: Release, Packages & Container (Stable) +│ └── testing.yaml # CI/CD: Preview, Packages & Container (Testing) ├── scripts/ │ ├── get-build-number.py # Dynamic build/revision number resolution │ └── package-arch.py # Native Arch Linux package builder @@ -108,6 +216,9 @@ mirror-package sync --dry-run │ ├── config_tests.rs # Tests for configuration management │ ├── gitea_tests.rs # Tests for Gitea upload routing │ └── github_tests.rs # Tests for package classification & parsing +├── .dockerignore # Container build ignore rules +├── Dockerfile # Secure minimal runtime container +├── docker-compose.example.yml # Example Docker Compose configuration ├── Cargo.toml # Project manifest and packaging metadata ├── LICENSE # GPL-3.0-or-later License ├── AGENTS.md # Agent & developer guidelines diff --git a/docker-compose.example.yml b/docker-compose.example.yml new file mode 100644 index 0000000..0fca2da --- /dev/null +++ b/docker-compose.example.yml @@ -0,0 +1,24 @@ +services: + mirror-package: + image: gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest + container_name: mirror-package + # Security: run as unprivileged user, read-only rootfs, drop capabilities + user: "10001:10001" + read_only: true + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + # Persistent storage and temporary stream buffer + volumes: + - ./config:/home/appuser/.config/mirror-package + - ./state:/home/appuser/.local/state/mirror-package + tmpfs: + - /tmp:rw,noexec,nosuid,size=2G + environment: + - GITEA_URL=https://gitea.creative-dragonslayer.de + - GITEA_TOKEN=your_gitea_api_token + - REGISTRY_OWNER=Linuxapps + - GITHUB_TOKEN=your_optional_github_pat + - LOG_LEVEL=info + command: ["sync"] diff --git a/src/gitea.rs b/src/gitea.rs index e51b45a..cf83bf0 100644 --- a/src/gitea.rs +++ b/src/gitea.rs @@ -56,20 +56,14 @@ pub fn get_target_upload_urls( if prerelease { vec![format!("{}/api/packages/{}/rpm/testing/upload", base, owner)] } else { - vec![ - format!("{}/api/packages/{}/rpm/stable/upload", base, owner), - format!("{}/api/packages/{}/rpm/testing/upload", base, owner), - ] + vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)] } } PackageType::Arch => { if prerelease { vec![format!("{}/api/packages/{}/arch/testing", base, owner)] } else { - vec![ - format!("{}/api/packages/{}/arch/stable", base, owner), - format!("{}/api/packages/{}/arch/testing", base, owner), - ] + vec![format!("{}/api/packages/{}/arch/stable", base, owner)] } } }