From 52964e28f0b60b6010a6f30b6eb4fd6f20aa3523 Mon Sep 17 00:00:00 2001 From: DragonSlayer_14 Date: Thu, 27 Aug 2026 23:57:56 +0200 Subject: [PATCH] =?UTF-8?q?Feat:=20=C3=84ndert=20Dokumentationssprache=20a?= =?UTF-8?q?uf=20Deutsch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 4 +- Dockerfile | 12 +-- README.md | 193 ++++++++++++++++++------------------- docker-compose.example.yml | 8 +- src/cli.rs | 54 +++++------ src/config.rs | 40 ++++---- src/gitea.rs | 12 +-- src/github.rs | 32 +++--- src/lib.rs | 3 + src/main.rs | 8 +- src/pipeline.rs | 20 ++-- tests/gitea_tests.rs | 12 +-- tests/github_tests.rs | 2 +- 13 files changed, 201 insertions(+), 199 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7207312..a86cfe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ authors = ['DragonSlayer_14'] readme = "README.md" license = "GPL-3.0-or-later" repository = "https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage" -description = "Mirror prebuilt Linux packages from GitHub Releases to Gitea Package Registry" +description = "Spiegelt vorkompilierte Linux-Pakete aus GitHub-Releases in Gitea-Paket-Registries" [dependencies] config-ctdra = { version = "1.0.4", registry = "gitea" } @@ -31,7 +31,7 @@ section = "utils" priority = "optional" depends = "$auto" extended-description = """\ -Mirror prebuilt Linux packages (.deb, .rpm, .pkg.tar.zst) from GitHub Releases into Gitea Package Registries.\ +Spiegelt vorkompilierte Linux-Pakete (.deb, .rpm, .pkg.tar.zst) aus GitHub-Releases in Gitea-Paket-Registries.\ """ assets = [] diff --git a/Dockerfile b/Dockerfile index 0935023..d50ea7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ # syntax=docker/dockerfile:1 -# Minimal and secure runtime image +# Minimales und gehärtetes Runtime-Image FROM debian:bookworm-slim AS runtime -# Install CA certificates and minimal runtime dynamic libraries +# CA-Zertifikate und minimale dynamische Laufzeitbibliotheken installieren RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ @@ -19,18 +19,18 @@ RUN apt-get update && \ /etc/mirror-package && \ chown -R appuser:appuser /home/appuser /etc/mirror-package -# Build argument pointing to the prebuilt binary +# Build-Argument mit Verweis auf die vorkompilierte Binary ARG TARGET_BIN=target/release/mirror-package COPY ${TARGET_BIN} /usr/local/bin/mirror-package -# Set strict executable permissions +# Strikte Ausführungsrechte setzen RUN chmod 0755 /usr/local/bin/mirror-package -# Run as unprivileged user +# Als unprivilegierter Benutzer ausführen USER 10001:10001 WORKDIR /home/appuser -# Expose volume mount points for configuration and persistent state/logs +# Volume-Mount-Punkte für Konfiguration und persistenten Status / Logs exponieren VOLUME ["/home/appuser/.config/mirror-package", "/home/appuser/.local/state/mirror-package"] ENTRYPOINT ["/usr/local/bin/mirror-package"] diff --git a/README.md b/README.md index 0e306a1..9777714 100644 --- a/README.md +++ b/README.md @@ -1,131 +1,131 @@ # mirror-package -Automated tool to extract, download, and mirror prebuilt Linux packages from GitHub Releases into a self-hosted Gitea / Forgejo Package Registry. +Automatisiertes Werkzeug zum Extrahieren, Herunterladen und Spiegeln vorkompilierter Linux-Pakete aus GitHub-Releases in eine selbstgehostete Gitea- / Forgejo-Paket-Registry. -`mirror-package` tracks configured GitHub repositories (such as `raspberrypi/rpi-imager` or `Heroic-Games-Launcher/HeroicGamesLauncher`), identifies precompiled Linux packages (`.deb`, `.rpm`, `.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`), and automatically publishes them into the appropriate Gitea Package Registry distributions according to release stability rules. +`mirror-package` überwacht konfigurierte GitHub-Repositories (wie z. B. `raspberrypi/rpi-imager` oder `Heroic-Games-Launcher/HeroicGamesLauncher`), identifiziert vorkompilierte Linux-Pakete (`.deb`, `.rpm`, `.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`) und veröffentlicht diese anhand von Release-Stabilitätsregeln automatisch in den passenden Distributionen der Gitea-Paket-Registry. --- -## Features +## Funktionen -- **Multi-Distribution Package Support**: +- **Unterstützung mehrerer Distributionen**: - **Debian / Ubuntu** (`.deb`) - **Fedora / RHEL / openSUSE** (`.rpm`) - **Arch Linux** (`.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`) -- **Distribution Routing Rules**: - - **Stable Releases**: Debian packages are published to **both** `stable` and `testing` Debian pools (`pool/stable/main` and `pool/testing/main`); RPM packages to `rpm/stable`; Arch packages to `arch/stable`. - - **Pre-Releases**: Published **only** to testing channels: Debian `pool/testing/main`, RPM `rpm/testing`, and Arch `arch/testing`. -- **Persistent Configuration with `config-ctdra`**: - - Automatically stores settings in user (`~/.config/mirror-package/config.toml`) or system (`/etc/mirror-package/config.toml`) paths. - - Adding or removing repositories via CLI automatically updates the configuration file. -- **Flexible Authentication**: - - GitHub releases can be queried anonymously (no token required). - - Optional GitHub Personal Access Token support for higher API rate limits. - - Gitea instance URL, API token, and registry owner configurable via CLI flags, environment variables, or config file. -- **Diagnostics & Dry-Run Mode**: - - Unified file and console logging powered by `logger-ctdra`. - - `--dry-run` flag to inspect download and upload steps without making remote changes. +- **Distributions-Routing-Regeln**: + - **Stabile Releases**: Debian-Pakete werden **sowohl** im `stable`- als auch im `testing`-Debian-Pool veröffentlicht (`pool/stable/main` und `pool/testing/main`); RPM-Pakete unter `rpm/stable`; Arch-Pakete unter `arch/stable`. + - **Pre-Releases**: Werden **ausschließlich** in Testing-Kanälen veröffentlicht: Debian `pool/testing/main`, RPM `rpm/testing` und Arch `arch/testing`. +- **Persistente Konfiguration über `config-ctdra`**: + - Speichert Einstellungen automatisch im Benutzerpfad (`~/.config/mirror-package/config.toml`) oder Systempfad (`/etc/mirror-package/config.toml`). + - Das Hinzufügen oder Entfernen von Repositories über die CLI aktualisiert automatisch die Konfigurationsdatei. +- **Flexible Authentifizierung**: + - GitHub-Releases können anonym abgefragt werden (kein Token erforderlich). + - Optionale Unterstützung für GitHub Personal Access Tokens zur Vermeidung von API-Rate-Limits. + - Gitea-Instanz-URL, API-Token und Registry-Owner können per CLI-Flags, Umgebungsvariablen oder Konfigurationsdatei festgelegt werden. +- **Diagnose & Dry-Run-Modus**: + - Einheitliches Datei- und Konsolen-Logging über `logger-ctdra`. + - `--dry-run`-Flag zur Simulation von Download- und Upload-Schritten ohne Änderungen an Remote-Systemen vorzunehmen. --- -## CLI Usage & Commands +## CLI-Verwendung & Befehle -### 1. Configuration (`config`) -Set up your Gitea credentials and optional GitHub token: +### 1. Konfiguration (`config`) +Richte deine Gitea-Zugangsdaten und das optionale GitHub-Token ein: ```bash -# Configure Gitea instance and registry owner +# Gitea-Instanz und Registry-Owner konfigurieren mirror-package config set \ --gitea-url "https://gitea.creative-dragonslayer.de" \ - --gitea-token "your_gitea_api_token" \ + --gitea-token "dein_gitea_api_token" \ --registry-owner "Linuxapps" -# Optional: Configure GitHub Token for rate limits +# Optional: GitHub-Token für höhere API-Rate-Limits konfigurieren mirror-package config set --github-token "ghp_xxxxxxxxxxxx" -# Display current configuration +# Aktuelle Konfiguration anzeigen mirror-package config show ``` -Credentials can also be passed via environment variables (`GITEA_URL`, `GITEA_TOKEN`, `REGISTRY_OWNER`, `GITHUB_TOKEN`) or global CLI arguments. +Zugangsdaten können auch über Umgebungsvariablen (`GITEA_URL`, `GITEA_TOKEN`, `REGISTRY_OWNER`, `GITHUB_TOKEN`) oder globale CLI-Argumente übergeben werden. -### 2. Managing Monitored Repositories (`add`, `remove`, `list`) +### 2. Überwachte Repositories verwalten (`add`, `remove`, `list`) ```bash -# Add repositories to persistent configuration +# Repositories zur persistenten Konfiguration hinzufügen mirror-package add raspberrypi/rpi-imager mirror-package add Heroic-Games-Launcher/HeroicGamesLauncher -# Add repository without pre-release syncing +# Repository ohne Synchronisierung von Pre-Releases hinzufügen mirror-package add some-owner/some-repo --no-prereleases -# List configured repositories and their sync status +# Konfigurierte Repositories und deren Sync-Status auflisten mirror-package list -# Remove a repository +# Ein Repository entfernen mirror-package remove raspberrypi/rpi-imager ``` -### 3. Synchronizing Packages (`sync`) +### 3. Pakete synchronisieren (`sync`) ```bash -# Sync all configured repositories (latest stable and latest pre-release) +# Alle konfigurierten Repositories synchronisieren (neueste stabile Version und neuestes Pre-Release) mirror-package sync -# Sync a specific repository +# Ein bestimmtes Repository synchronisieren mirror-package sync raspberrypi/rpi-imager -# Sync all historical releases for a repository +# Alle historischen Releases eines Repositories synchronisieren mirror-package sync raspberrypi/rpi-imager --history -# Dry-run test (simulates without uploading) +# Dry-Run-Test (Simulation ohne Upload) mirror-package sync --dry-run ``` --- -## Docker & Container Usage +## Docker & Container-Nutzung -`mirror-package` is packaged and published as an ultra-secure, lightweight container image to the Gitea Container Registry. +`mirror-package` wird als minimales, gehärtetes Container-Image in der Gitea Container Registry bereitgestellt. -### 1. Image Registry & Tagging Strategy +### 1. Image-Registry & Tagging-Strategie -- **Stable Releases (`main` branch)**: +- **Stabile Releases (`main`-Branch)**: - `//mirror-package:latest` - `//mirror-package:` - `//mirror-package:v` - `//mirror-package:.` -- **Testing & Preview Releases (`testing` branch)**: +- **Testing- & Vorschau-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).* + - *(Hinweis: Der Tag `:latest` ist strikt dem stabilen `main`-Workflow vorbehalten).* -### 2. Persistent Storage Paths +### 2. Persistente Speicherpfade -To persist configurations, tracked repository states, and logs across container restarts, mount the following container paths to host directories or Docker volumes: +Um Konfigurationen, Status und Protokolle über Container-Neustarts hinweg beizubehalten, binde folgende Pfade an Host-Verzeichnisse oder 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) | +| Container-Pfad | Zweck | Empfohlener Mount-Typ | +|---------------------------------------------|-------------------------------------------------------|------------------------------------| +| `/home/appuser/.config/mirror-package` | Speichert `config.toml` (Zugangsdaten & Repo-Liste) | Host-Verzeichnis / Volume (rw) | +| `/home/appuser/.local/state/mirror-package` | Persistente Anwendungsprotokolle & Statusdateien | Host-Verzeichnis / Volume (rw) | +| `/tmp` | Download- & Streaming-Puffer für Paketdateien | `tmpfs` (RAM / tempfs, rw, noexec) | -### 3. Security Hardening +### 3. Sicherheitshärtung -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). +Der Container wurde nach höchsten Sicherheitsstandards aufgebaut: +- **Unprivilegierter Benutzer**: Läuft als `appuser` (UID `10001`, GID `10001`), niemals als `root`. +- **Read-Only Root-Dateisystem**: Voll funktionsfähig mit `--read-only` / `read_only: true`. +- **Keine Capabilities**: Sämtliche Linux-Capabilities können sicher entzogen werden (`--cap-drop=ALL`). +- **Keine Rechteausweitung**: Erzwingt `no-new-privileges:true`. +- **Minimale Image-Größe**: Basiert auf Debian Bookworm Slim und enthält nur CA-Zertifikate und notwendige dynamische Bibliotheken (~40 MB). -### 4. Running via Docker CLI +### 4. Ausführung über Docker CLI ```bash -# Initialize / configure credentials +# Zugangsdaten initialisieren / konfigurieren docker run --rm \ --name mirror-package \ --read-only \ @@ -137,10 +137,10 @@ docker run --rm \ gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \ config set \ --gitea-url "https://gitea.creative-dragonslayer.de" \ - --gitea-token "your_token" \ + --gitea-token "dein_token" \ --registry-owner "Linuxapps" -# Add repositories +# Repositories hinzufügen docker run --rm \ --read-only \ --cap-drop=ALL \ @@ -151,7 +151,7 @@ docker run --rm \ gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \ add raspberrypi/rpi-imager -# Run synchronization (can be triggered by a host cronjob or scheduler) +# Synchronisation ausführen (kann über Cronjob oder Scheduler auf dem Host getriggert werden) docker run --rm \ --read-only \ --cap-drop=ALL \ @@ -163,7 +163,7 @@ docker run --rm \ sync ``` -### 5. Docker Compose Example (`docker-compose.yml`) +### 5. Docker Compose Beispiel (`docker-compose.yml`) ```yaml services: @@ -183,72 +183,71 @@ services: - /tmp:rw,noexec,nosuid,size=2G environment: - GITEA_URL=https://gitea.creative-dragonslayer.de - - GITEA_TOKEN=your_gitea_api_token + - GITEA_TOKEN=dein_gitea_api_token - REGISTRY_OWNER=Linuxapps - - GITHUB_TOKEN=your_optional_github_pat + - GITHUB_TOKEN=dein_optionaler_github_pat - LOG_LEVEL=info command: ["sync"] ``` --- -## Repository Structure +## Repository-Struktur ```text ├── .cargo/ -│ └── config.toml # Linker & Cargo configuration +│ └── config.toml # Linker- & Cargo-Konfiguration ├── .gitea/ │ └── workflows/ -│ ├── main.yaml # CI/CD: Release, Packages & Container (Stable) -│ └── testing.yaml # CI/CD: Preview, Packages & Container (Testing) +│ ├── main.yaml # CI/CD: Release, Pakete & Container (Stable) +│ └── testing.yaml # CI/CD: Preview, Pakete & Container (Testing) ├── scripts/ -│ ├── get-build-number.py # Dynamic build/revision number resolution -│ └── package-arch.py # Native Arch Linux package builder +│ ├── get-build-number.py # Dynamische Ermittlung der Build-/Revisionsnummer +│ └── package-arch.py # Erstellung nativer Arch Linux-Pakete ├── src/ -│ ├── main.rs # CLI entrypoint & subcommand dispatch -│ ├── lib.rs # Library root & module exports -│ ├── cli.rs # Clap CLI arguments & options -│ ├── config.rs # config-ctdra integration & models -│ ├── gitea.rs # Gitea Package Registry upload client -│ ├── github.rs # GitHub API client & package classifier -│ └── pipeline.rs # End-to-end sync workflow & temp storage +│ ├── main.rs # CLI-Einstiegspunkt & Befehlsausführung +│ ├── lib.rs # Bibliotheks-Wurzel & Modulexporte +│ ├── cli.rs # Clap-CLI-Argumente & Optionen +│ ├── config.rs # config-ctdra Anbindung & Konfigurationsmodelle +│ ├── gitea.rs # Gitea-Paket-Registry Upload-Client +│ ├── github.rs # GitHub-API-Client & Paket-Klassifizierung +│ └── pipeline.rs # End-to-End-Synchronisationspipeline & temporärer Speicher ├── tests/ -│ ├── 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 -└── README.md # Documentation +│ ├── config_tests.rs # Tests für die Konfigurationsverwaltung +│ ├── gitea_tests.rs # Tests für das Gitea-Upload-Routing +│ └── github_tests.rs # Tests für Paket-Klassifizierung & Parsing +├── .dockerignore # Ausschlussregeln für Container-Builds +├── Dockerfile # Gehärtetes, minimales Runtime-Container-Image +├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose +├── Cargo.toml # Projekt-Manifest und Paketierungs-Metadaten +├── LICENSE # GPL-3.0-or-later Lizenztext +├── AGENTS.md # Agenten- & Entwickler-Richtlinien +└── README.md # Projektdokumentation ``` --- -## Local Development & Building +## Lokale Entwicklung & Bauen -### Prerequisites -- **Rust & Cargo** (Stable toolchain, Edition 2024 supported) -- **Python 3** (for packaging scripts) +### Voraussetzungen +- **Rust & Cargo** (Stable Toolchain, Edition 2024 unterstützt) +- **Python 3** (für Paketierungsskripte) -### Build & Test Commands +### Befehle zum Bauen & Testen ```bash -# Run syntax and type checks +# Syntax- und Typprüfung ausführen cargo check -# Run unit tests +# Unit-Tests ausführen cargo test -# Build release binary +# Release-Binary kompilieren cargo build --release ``` --- -## License - -This project is licensed under the [GPL-3.0-or-later](LICENSE) license. +## Lizenz +Dieses Projekt ist unter der [GPL-3.0-or-later](LICENSE)-Lizenz lizenziert. diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 0fca2da..709e233 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -2,14 +2,14 @@ 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 + # Sicherheit: Als unprivilegierter Benutzer ausführen, Read-Only-Dateisystem, Capabilities entziehen user: "10001:10001" read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true - # Persistent storage and temporary stream buffer + # Persistenter Speicher und temporärer Streaming-Puffer volumes: - ./config:/home/appuser/.config/mirror-package - ./state:/home/appuser/.local/state/mirror-package @@ -17,8 +17,8 @@ services: - /tmp:rw,noexec,nosuid,size=2G environment: - GITEA_URL=https://gitea.creative-dragonslayer.de - - GITEA_TOKEN=your_gitea_api_token + - GITEA_TOKEN=dein_gitea_api_token - REGISTRY_OWNER=Linuxapps - - GITHUB_TOKEN=your_optional_github_pat + - GITHUB_TOKEN=dein_optionaler_github_pat - LOG_LEVEL=info command: ["sync"] diff --git a/src/cli.rs b/src/cli.rs index d4929c7..3cb120e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,41 +1,41 @@ use clap::{Args, Parser, Subcommand, ValueEnum}; use std::path::PathBuf; -/// Command line parser for mirror-package. +/// Befehlszeilen-Parser für mirror-package. #[derive(Parser, Debug)] #[command( name = "mirror-package", author, version, - about = "Mirror Linux packages (.deb, .rpm, .pkg.tar.zst) from GitHub Releases to Gitea Package Registry", + about = "Spiegelt Linux-Pakete (.deb, .rpm, .pkg.tar.zst) aus GitHub-Releases in Gitea-Paket-Registries", long_about = None )] pub struct Cli { - /// Gitea instance base URL (e.g. https://gitea.creative-dragonslayer.de) + /// Basis-URL der Gitea-Instanz (z. B. https://gitea.creative-dragonslayer.de) #[arg(long, env = "GITEA_URL", global = true)] pub gitea_url: Option, - /// Gitea API Token with package write permissions + /// Gitea-API-Token mit Schreibberechtigung für Pakete #[arg(long, env = "GITEA_TOKEN", global = true)] pub gitea_token: Option, - /// Gitea Package Registry owner / organization + /// Gitea-Paket-Registry-Owner / Organisation #[arg(long, env = "REGISTRY_OWNER", global = true)] pub registry_owner: Option, - /// Optional GitHub Token for API requests (avoids rate limits) + /// Optionales GitHub-Token für API-Anfragen (vermeidet Rate-Limits) #[arg(long, env = "GITHUB_TOKEN", global = true)] pub github_token: Option, - /// Custom path to configuration file + /// Benutzerdefinierter Pfad zur Konfigurationsdatei #[arg(long, global = true)] pub config: Option, - /// Perform a dry run without downloading or uploading packages + /// Führt einen Probelauf ohne Herunterladen oder Hochladen von Paketen durch #[arg(long, global = true)] pub dry_run: bool, - /// Logging level (error, warn, info, debug) + /// Logging-Level (error, warn, info, debug) #[arg(long, value_enum, default_value_t = LogLevelArg::Info, global = true)] pub log_level: LogLevelArg, @@ -64,59 +64,59 @@ impl From for logger_ctdra::LogLevel { #[derive(Subcommand, Debug)] pub enum Commands { - /// Synchronize packages from GitHub Releases to Gitea Package Registry + /// Synchronisiert Pakete aus GitHub-Releases in die Gitea-Paket-Registry Sync(SyncArgs), - /// Add a GitHub repository to the persistent configuration + /// Fügt ein GitHub-Repository zur persistenten Konfiguration hinzu Add(AddArgs), - /// Remove a GitHub repository from the persistent configuration + /// Entfernt ein GitHub-Repository aus der persistenten Konfiguration #[command(alias = "rm")] Remove(RemoveArgs), - /// List configured repositories and their sync status + /// Listet konfigurierte Repositories und deren Sync-Status auf #[command(alias = "ls")] List, - /// View or update application configuration + /// Zeigt die Anwendungskonfiguration an oder aktualisiert sie Config(ConfigArgs), } #[derive(Args, Debug)] pub struct SyncArgs { - /// Specific repository name or URL to sync (e.g. "raspberrypi/rpi-imager"). If omitted, all configured repositories are synced. + /// Bestimmter Repository-Name oder URL zur Synchronisation (z. B. "raspberrypi/rpi-imager"). Wenn weggelassen, werden alle konfigurierten Repositories synchronisiert. pub repo: Option, - /// Sync all configured repositories + /// Alle konfigurierten Repositories synchronisieren #[arg(short = 'a', long)] pub all: bool, - /// Scan historical releases instead of only the latest release(s) + /// Historische Releases scannen anstatt nur die neuesten Release(s) #[arg(long)] pub history: bool, - /// Include pre-releases during this sync run (defaults to repository configuration if not specified) + /// Pre-Releases während dieses Synchronisationslaufs einbeziehen (Standard: Repository-Konfiguration) #[arg(long)] pub prereleases: Option, } #[derive(Args, Debug)] pub struct AddArgs { - /// GitHub repository to mirror (e.g. "raspberrypi/rpi-imager" or full GitHub URL) + /// Zu spiegelndes GitHub-Repository (z. B. "raspberrypi/rpi-imager" oder vollständige GitHub-URL) pub repo: String, - /// Do not mirror pre-releases for this repository + /// Keine Pre-Releases für dieses Repository spiegeln #[arg(long)] pub no_prereleases: bool, - /// Immediately synchronize this repository after adding + /// Dieses Repository nach dem Hinzufügen sofort synchronisieren #[arg(long)] pub sync: bool, } #[derive(Args, Debug)] pub struct RemoveArgs { - /// Repository to remove (e.g. "raspberrypi/rpi-imager") + /// Zu entfernendes Repository (z. B. "raspberrypi/rpi-imager") pub repo: String, } @@ -128,20 +128,20 @@ pub struct ConfigArgs { #[derive(Subcommand, Debug)] pub enum ConfigAction { - /// Show current configuration and configuration file location + /// Aktuelle Konfiguration und Speicherort der Konfigurationsdatei anzeigen Show, - /// Set configuration parameters + /// Konfigurationsparameter setzen Set { - /// Gitea instance base URL + /// Basis-URL der Gitea-Instanz #[arg(long)] gitea_url: Option, - /// Gitea API Token + /// Gitea-API-Token #[arg(long)] gitea_token: Option, - /// Gitea Package Registry owner + /// Gitea-Paket-Registry-Owner #[arg(long)] registry_owner: Option, diff --git a/src/config.rs b/src/config.rs index cec51a8..40a537b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,15 +5,15 @@ fn default_true() -> bool { true } -/// Configuration for an individual repository to mirror. +/// Konfiguration für ein einzelnes zu spiegelndes Repository. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct RepoConfig { - /// Full name of the repository in "owner/repo" format. + /// Vollständiger Name des Repositories im Format "owner/repo". pub name: String, - /// Whether to mirror pre-releases. + /// Gibt an, ob Pre-Releases gespiegelt werden sollen. #[serde(default = "default_true")] pub include_prereleases: bool, - /// Last successfully synced tag name. + /// Zuletzt erfolgreich synchronisierter Tag-Name. #[serde(default, skip_serializing_if = "Option::is_none")] pub last_synced_tag: Option, } @@ -28,28 +28,28 @@ impl RepoConfig { } } -/// Persistent application configuration. +/// Persistente Anwendungskonfiguration. #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] pub struct AppConfig { - /// Gitea instance base URL (e.g. https://gitea.example.com). + /// Basis-URL der Gitea-Instanz (z. B. https://gitea.example.com). #[serde(default, skip_serializing_if = "Option::is_none")] pub gitea_url: Option, - /// Gitea API authentication token. + /// Gitea-API-Authentifizierungstoken. #[serde(default, skip_serializing_if = "Option::is_none")] pub gitea_token: Option, - /// Gitea Package Registry owner/organization name. + /// Name des Gitea-Paket-Registry-Owners bzw. der Organisation. #[serde(default, skip_serializing_if = "Option::is_none")] pub registry_owner: Option, - /// Optional GitHub Personal Access Token for increased rate limits. + /// Optionales GitHub Personal Access Token für höhere Rate-Limits. #[serde(default, skip_serializing_if = "Option::is_none")] pub github_token: Option, - /// Configured repositories to mirror. + /// Konfigurierte Repositories zur Spiegelung. #[serde(default)] pub repositories: Vec, } impl AppConfig { - /// Normalizes repository input, stripping leading/trailing slashes and GitHub URL prefixes. + /// Normalisiert die Repository-Eingabe, indem führende/nachgestellte Schrägstriche und GitHub-URL-Präfixe entfernt werden. pub fn normalize_repo_name(input: &str) -> String { let trimmed = input.trim(); let cleaned = trimmed @@ -61,7 +61,7 @@ impl AppConfig { cleaned.to_string() } - /// Adds or updates a repository in the configuration. + /// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes. pub fn add_or_update_repo(&mut self, repo: RepoConfig) { if let Some(existing) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&repo.name)) { existing.include_prereleases = repo.include_prereleases; @@ -70,7 +70,7 @@ impl AppConfig { } } - /// Removes a repository by name. Returns true if it was found and removed. + /// Entfernt ein Repository anhand des Namens. Gibt true zurück, wenn es gefunden und entfernt wurde. pub fn remove_repo(&mut self, repo_name: &str) -> bool { let normalized = Self::normalize_repo_name(repo_name); let before_len = self.repositories.len(); @@ -78,13 +78,13 @@ impl AppConfig { self.repositories.len() < before_len } - /// Finds a repository by name. + /// Sucht ein Repository anhand des Namens. pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> { let normalized = Self::normalize_repo_name(repo_name); self.repositories.iter().find(|r| r.name.eq_ignore_ascii_case(&normalized)) } - /// Updates the last synced tag for a given repository. + /// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository. pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) { let normalized = Self::normalize_repo_name(repo_name); if let Some(repo) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&normalized)) { @@ -93,7 +93,7 @@ impl AppConfig { } } -/// Sets a custom path for the configuration file if provided. +/// Setzt einen benutzerdefinierten Pfad für die Konfigurationsdatei, falls angegeben. pub fn init_config_path(custom_path: Option<&std::path::Path>) { config_ctdra::set_config_name("config"); if let Some(path) = custom_path { @@ -101,22 +101,22 @@ pub fn init_config_path(custom_path: Option<&std::path::Path>) { } } -/// Returns the resolved path of the configuration file. +/// Gibt den aufgelösten Pfad der Konfigurationsdatei zurück. pub fn get_config_file_path() -> PathBuf { config_ctdra::get_config_path() } -/// Loads the application configuration. Falls back to defaults if not found. +/// Lädt die Anwendungskonfiguration. Verwendet Standardwerte, falls keine Datei vorhanden ist. pub fn load_config() -> AppConfig { config_ctdra::load_config::() } -/// Saves the application configuration to disk. +/// Speichert die Anwendungskonfiguration auf der Festplatte. pub fn save_config(config: &AppConfig) -> Result<(), config_ctdra::ConfyError> { config_ctdra::store(config) } -/// Modifies the application configuration in-place atomically and saves it. +/// Modifiziert die Anwendungskonfiguration atomar vor Ort und speichert sie. pub fn modify_config(f: F) -> Result where F: FnOnce(&mut AppConfig), diff --git a/src/gitea.rs b/src/gitea.rs index cf83bf0..2de5ea3 100644 --- a/src/gitea.rs +++ b/src/gitea.rs @@ -3,7 +3,7 @@ use anyhow::{bail, Context, Result}; use std::path::Path; use tokio::fs::File; -/// Credentials and target registry information for Gitea. +/// Zugangsdaten und Ziel-Registry-Informationen für Gitea. #[derive(Clone, Debug, PartialEq, Eq)] pub struct GiteaConfig { pub base_url: String, @@ -25,7 +25,7 @@ impl GiteaConfig { } } -/// Result of a package upload attempt. +/// Ergebnis eines Paket-Upload-Versuchs. #[derive(Debug, Clone, PartialEq, Eq)] pub enum UploadStatus { Uploaded, @@ -33,7 +33,7 @@ pub enum UploadStatus { SimulatedDryRun, } -/// Computes the target Gitea package registry upload URLs based on package type and release channel. +/// Berechnet die Ziel-Upload-URLs der Gitea-Paket-Registry basierend auf Pakettyp und Release-Kanal. pub fn get_target_upload_urls( base_url: &str, owner: &str, @@ -69,7 +69,7 @@ pub fn get_target_upload_urls( } } -/// Client for uploading packages to a Gitea Package Registry. +/// Client für den Upload von Paketen in eine Gitea-Paket-Registry. #[derive(Clone, Debug)] pub struct GiteaClient { client: reqwest::Client, @@ -77,7 +77,7 @@ pub struct GiteaClient { } impl GiteaClient { - /// Creates a new Gitea client. + /// Erstellt einen neuen Gitea-Client. pub fn new(config: GiteaConfig) -> Result { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( @@ -93,7 +93,7 @@ impl GiteaClient { Ok(Self { client, config }) } - /// Uploads a package file from disk to the specified Gitea endpoint. + /// Lädt eine Paketdatei von der Festplatte zum angegebenen Gitea-Endpunkt hoch. pub async fn upload_file( &self, file_path: &Path, diff --git a/src/github.rs b/src/github.rs index 7f49fa1..03253d4 100644 --- a/src/github.rs +++ b/src/github.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Context, Result}; use serde::{Deserialize, Serialize}; -/// Supported Linux package distribution types. +/// Unterstützte Linux-Paketverteilungstypen. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum PackageType { Debian, @@ -10,10 +10,10 @@ pub enum PackageType { } impl PackageType { - /// Classifies an asset by its filename into a supported PackageType. + /// Klassifiziert ein Asset anhand seines Dateinamens in einen unterstützten PackageType. pub fn from_filename(filename: &str) -> Option { let name = filename.to_ascii_lowercase(); - // Check Arch packages first due to multi-part extensions + // Arch-Pakete aufgrund mehrteiliger Dateiendungen zuerst prüfen if name.ends_with(".pkg.tar.zst") || name.ends_with(".pkg.tar.xz") || name.ends_with(".pkg.tar.gz") @@ -29,7 +29,7 @@ impl PackageType { } } - /// Returns human-readable name of the package type. + /// Gibt den lesbaren Namen des Pakettyps zurück. pub fn name(&self) -> &'static str { match self { PackageType::Debian => "Debian (.deb)", @@ -39,7 +39,7 @@ impl PackageType { } } -/// A classified Linux package asset from a release. +/// Ein klassifiziertes Linux-Paket-Asset aus einem Release. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ReleaseAsset { pub name: String, @@ -48,7 +48,7 @@ pub struct ReleaseAsset { pub package_type: PackageType, } -/// A GitHub release with extracted Linux package assets. +/// Ein GitHub-Release mit extrahierten Linux-Paket-Assets. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct GitHubRelease { pub id: u64, @@ -77,7 +77,7 @@ struct GhApiAsset { size: u64, } -/// Client for interacting with the GitHub API. +/// Client für die Interaktion mit der GitHub-API. #[derive(Clone, Debug)] pub struct GitHubClient { client: reqwest::Client, @@ -85,7 +85,7 @@ pub struct GitHubClient { } impl GitHubClient { - /// Creates a new GitHub API client with optional authentication token. + /// Erstellt einen neuen GitHub-API-Client mit optionalem Authentifizierungstoken. pub fn new(token: Option) -> Result { let mut headers = reqwest::header::HeaderMap::new(); headers.insert( @@ -105,10 +105,10 @@ impl GitHubClient { Ok(Self { client, token }) } - /// Fetches releases for a given repository (format "owner/repo"). + /// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo"). /// - /// If `history` is false, retrieves only the latest release(s). - /// If `include_prereleases` is false, skips pre-releases. + /// Wenn `history` false ist, werden nur die neuesten Releases abgerufen. + /// Wenn `include_prereleases` false ist, werden Pre-Releases übersprungen. pub async fn fetch_releases( &self, repo: &str, @@ -186,9 +186,9 @@ impl GitHubClient { }); } - // If not scanning full history, stop after the first page (or once we found stable/prerelease) + // Wenn kein vollständiger Verlauf gescannt werden soll, nach der ersten Seite stoppen if !history { - // Keep only the most recent stable release and most recent pre-release (if allowed) + // Nur das neueste stabile Release und neueste Pre-Release behalten (falls zulässig) let mut selected = Vec::new(); let latest_stable = releases.iter().find(|r| !r.prerelease); if let Some(stable) = latest_stable { @@ -198,7 +198,7 @@ impl GitHubClient { if include_prereleases { let latest_prerelease = releases.iter().find(|r| r.prerelease); if let Some(prerelease) = latest_prerelease { - // Avoid duplicates if stable and pre-release are identical (rare) + // Duplikate vermeiden, falls Stable und Pre-Release identisch sind if !selected.iter().any(|r| r.id == prerelease.id) { selected.push(prerelease.clone()); } @@ -213,7 +213,7 @@ impl GitHubClient { Ok(releases) } - /// Downloads an asset from the given download URL as a streaming response. + /// Lädt ein Asset von der angegebenen Download-URL als Streaming-Response herunter. pub async fn download_asset_stream(&self, download_url: &str) -> Result { let mut req = self.client.get(download_url); if let Some(token) = &self.token { @@ -237,7 +237,7 @@ impl GitHubClient { } } -/// Helper to parse "owner/repo" from repository string. +/// Hilfsfunktion zum Parsen von "owner/repo" aus einer Repository-Zeichenkette. pub fn parse_repo_owner_name(repo: &str) -> Result<(&str, &str)> { let cleaned = repo .trim() diff --git a/src/lib.rs b/src/lib.rs index d9bc105..4d04728 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +//! `mirror-package` ist eine Anwendung zum Spiegeln vorkompilierter Linux-Pakete +//! (.deb, .rpm, .pkg.tar.zst) aus GitHub-Releases in Gitea- / Forgejo-Paket-Registries. + pub mod cli; pub mod config; pub mod gitea; diff --git a/src/main.rs b/src/main.rs index 46c7aac..7c8176e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,16 +11,16 @@ use mirror_package::pipeline::{sync_single_repository, SyncOptions, SyncReport}; async fn main() -> Result<()> { let cli = Cli::parse(); - // Initialize global logger level + // Globalen Logging-Level initialisieren set_log_level(cli.log_level.into()); - // Initialize custom config path if specified + // Benutzerdefinierten Konfigurationspfad initialisieren, falls angegeben init_config_path(cli.config.as_deref()); - // Load persistent configuration + // Persistente Konfiguration laden let mut app_config = load_config(); - // CLI flags override configuration values + // CLI-Flags überschreiben Konfigurationswerte if let Some(url) = cli.gitea_url { app_config.gitea_url = Some(url); } diff --git a/src/pipeline.rs b/src/pipeline.rs index 8f5e4d9..9b0e0d2 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -8,7 +8,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use tokio::fs::{remove_file, File}; use tokio::io::AsyncWriteExt; -/// Summary statistics of a synchronization operation. +/// Zusammenfassende Statistiken eines Synchronisationsvorgangs. #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct SyncReport { pub repositories_processed: usize, @@ -18,7 +18,7 @@ pub struct SyncReport { pub packages_simulated: usize, } -/// Options controlling a repository sync run. +/// Optionen zur Steuerung eines Repository-Synchronisationslaufs. #[derive(Debug, Clone)] pub struct SyncOptions { pub history: bool, @@ -26,7 +26,7 @@ pub struct SyncOptions { pub dry_run: bool, } -/// Executes the synchronization pipeline for a given repository. +/// Führt die Synchronisations-Pipeline für ein bestimmtes Repository aus. pub async fn sync_single_repository( repo_config: &RepoConfig, app_config: &AppConfig, @@ -47,7 +47,7 @@ pub async fn sync_single_repository( LogLevel::Info, ); - // Resolve Gitea credentials + // Gitea-Zugangsdaten auflösen let gitea_url = app_config.gitea_url.as_deref().unwrap_or_default(); let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default(); let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default(); @@ -155,12 +155,12 @@ pub async fn sync_single_repository( continue; } - // Download asset to temporary file + // Asset in temporäre Datei herunterladen let temp_file_path = download_to_temp_file(&github_client, &asset.download_url, &asset.name) .await .with_context(|| format!("Failed to download asset '{}'", asset.name))?; - // Upload asset to all target distribution URLs + // Asset auf alle Ziel-Distributions-URLs hochladen for url in &target_urls { log( "upload", @@ -197,14 +197,14 @@ pub async fn sync_single_repository( &format!("Failed to upload '{}' to {}: {:#}", asset.name, url, e), LogLevel::Error, ); - // Cleanup temp file before returning error + // Temporäre Datei vor Fehlerrückgabe bereinigen let _ = remove_file(&temp_file_path).await; return Err(e); } } } - // Cleanup temp file + // Temporäre Datei bereinigen if let Err(e) = remove_file(&temp_file_path).await { log( "sync", @@ -219,7 +219,7 @@ pub async fn sync_single_repository( } } - // Persist last synced tag if not dry-run + // Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run if !options.dry_run { if let Some(tag) = latest_synced_tag { let name_copy = repo_name.to_string(); @@ -233,7 +233,7 @@ pub async fn sync_single_repository( Ok(()) } -/// Downloads a GitHub release asset into a unique temporary file. +/// Lädt ein GitHub-Release-Asset in eine eindeutige temporäre Datei herunter. async fn download_to_temp_file( github_client: &GitHubClient, download_url: &str, diff --git a/tests/gitea_tests.rs b/tests/gitea_tests.rs index c65f544..06b8b86 100644 --- a/tests/gitea_tests.rs +++ b/tests/gitea_tests.rs @@ -6,7 +6,7 @@ fn test_debian_routing() { let base_url = "https://gitea.example.com"; let owner = "test-owner"; - // Stable Debian goes to both stable and testing pools + // Stabile Debian-Pakete werden in stable und testing Pools hochgeladen let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Debian, false); assert_eq!( stable_urls, @@ -16,7 +16,7 @@ fn test_debian_routing() { ] ); - // Pre-release Debian goes only to testing pool + // Pre-Release Debian-Pakete gehen nur in den testing Pool let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Debian, true); assert_eq!( prerelease_urls, @@ -29,14 +29,14 @@ fn test_rpm_routing() { let base_url = "https://gitea.example.com/"; let owner = "test-owner"; - // Stable RPM + // Stabiles RPM let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Rpm, false); assert_eq!( stable_urls, vec!["https://gitea.example.com/api/packages/test-owner/rpm/stable/upload"] ); - // Pre-release RPM + // Pre-Release RPM let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Rpm, true); assert_eq!( prerelease_urls, @@ -49,14 +49,14 @@ fn test_arch_routing() { let base_url = "https://gitea.example.com"; let owner = "test-owner"; - // Stable Arch + // Stabiles Arch let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Arch, false); assert_eq!( stable_urls, vec!["https://gitea.example.com/api/packages/test-owner/arch/stable"] ); - // Pre-release Arch + // Pre-Release Arch let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Arch, true); assert_eq!( prerelease_urls, diff --git a/tests/github_tests.rs b/tests/github_tests.rs index 21d474e..1a3b4f4 100644 --- a/tests/github_tests.rs +++ b/tests/github_tests.rs @@ -40,7 +40,7 @@ fn test_package_classification() { Some(PackageType::Arch) ); - // Non-package formats should be ignored + // Nicht unterstützte Paketformate sollten ignoriert werden assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.AppImage"), None); assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None); assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);