Merge pull request 'Merge dev in testing: Feat: Passt assets an und aktualisiert Paket-Abhängigkeiten.' (#5) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 9m35s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 9m35s
Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
Generated
+1
-1
@@ -866,7 +866,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mirror-package"
|
name = "mirror-package"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
+16
-8
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mirror-package"
|
name = "mirror-package"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ['DragonSlayer_14']
|
authors = ['DragonSlayer_14']
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "GPL-3.0-or-later"
|
license = "GPL-3.0-or-later"
|
||||||
repository = "https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage"
|
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]
|
[dependencies]
|
||||||
config-ctdra = { version = "1.0.4", registry = "gitea" }
|
config-ctdra = { version = "1.0.4", registry = "gitea" }
|
||||||
@@ -29,18 +29,26 @@ maintainer = "DragonSlayer_14"
|
|||||||
copyright = "2026 DragonSlayer_14"
|
copyright = "2026 DragonSlayer_14"
|
||||||
section = "utils"
|
section = "utils"
|
||||||
priority = "optional"
|
priority = "optional"
|
||||||
depends = "$auto"
|
depends = "$auto, ca-certificates"
|
||||||
extended-description = """\
|
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 = []
|
assets = [
|
||||||
|
["target/release/mirror-package", "usr/bin/mirror-package", "755"],
|
||||||
|
["README.md", "usr/share/doc/mirror-package/README.md", "644"],
|
||||||
|
["LICENSE", "usr/share/doc/mirror-package/copyright", "644"],
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.generate-rpm]
|
[package.metadata.generate-rpm]
|
||||||
assets = []
|
assets = [
|
||||||
requires = { }
|
{ source = "target/release/mirror-package", dest = "/usr/bin/mirror-package", mode = "755" },
|
||||||
|
{ source = "README.md", dest = "/usr/share/doc/mirror-package/README.md", mode = "644", doc = true },
|
||||||
|
{ source = "LICENSE", dest = "/usr/share/licenses/mirror-package/LICENSE", mode = "644", license = true },
|
||||||
|
]
|
||||||
|
requires = { "ca-certificates" = "*", "glibc" = "*" }
|
||||||
|
|
||||||
[package.metadata.arch]
|
[package.metadata.arch]
|
||||||
pkgrel = "1"
|
pkgrel = "1"
|
||||||
arch = "x86_64"
|
arch = "x86_64"
|
||||||
depends = ["gcc-libs", "glibc"]
|
depends = ["gcc-libs", "glibc", "ca-certificates"]
|
||||||
optdepends = []
|
optdepends = []
|
||||||
|
|||||||
+6
-6
@@ -1,9 +1,9 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
# Minimal and secure runtime image
|
# Minimales und gehärtetes Runtime-Image
|
||||||
FROM debian:bookworm-slim AS runtime
|
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 && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@@ -19,18 +19,18 @@ RUN apt-get update && \
|
|||||||
/etc/mirror-package && \
|
/etc/mirror-package && \
|
||||||
chown -R appuser:appuser /home/appuser /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
|
ARG TARGET_BIN=target/release/mirror-package
|
||||||
COPY ${TARGET_BIN} /usr/local/bin/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 chmod 0755 /usr/local/bin/mirror-package
|
||||||
|
|
||||||
# Run as unprivileged user
|
# Als unprivilegierter Benutzer ausführen
|
||||||
USER 10001:10001
|
USER 10001:10001
|
||||||
WORKDIR /home/appuser
|
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"]
|
VOLUME ["/home/appuser/.config/mirror-package", "/home/appuser/.local/state/mirror-package"]
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/mirror-package"]
|
ENTRYPOINT ["/usr/local/bin/mirror-package"]
|
||||||
|
|||||||
@@ -1,131 +1,131 @@
|
|||||||
# mirror-package
|
# 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`)
|
- **Debian / Ubuntu** (`.deb`)
|
||||||
- **Fedora / RHEL / openSUSE** (`.rpm`)
|
- **Fedora / RHEL / openSUSE** (`.rpm`)
|
||||||
- **Arch Linux** (`.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`)
|
- **Arch Linux** (`.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`)
|
||||||
- **Distribution Routing Rules**:
|
- **Distributions-Routing-Regeln**:
|
||||||
- **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`.
|
- **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**: Published **only** to testing channels: Debian `pool/testing/main`, RPM `rpm/testing`, and Arch `arch/testing`.
|
- **Pre-Releases**: Werden **ausschließlich** in Testing-Kanälen veröffentlicht: Debian `pool/testing/main`, RPM `rpm/testing` und Arch `arch/testing`.
|
||||||
- **Persistent Configuration with `config-ctdra`**:
|
- **Persistente Konfiguration über `config-ctdra`**:
|
||||||
- Automatically stores settings in user (`~/.config/mirror-package/config.toml`) or system (`/etc/mirror-package/config.toml`) paths.
|
- Speichert Einstellungen automatisch im Benutzerpfad (`~/.config/mirror-package/config.toml`) oder Systempfad (`/etc/mirror-package/config.toml`).
|
||||||
- Adding or removing repositories via CLI automatically updates the configuration file.
|
- Das Hinzufügen oder Entfernen von Repositories über die CLI aktualisiert automatisch die Konfigurationsdatei.
|
||||||
- **Flexible Authentication**:
|
- **Flexible Authentifizierung**:
|
||||||
- GitHub releases can be queried anonymously (no token required).
|
- GitHub-Releases können anonym abgefragt werden (kein Token erforderlich).
|
||||||
- Optional GitHub Personal Access Token support for higher API rate limits.
|
- Optionale Unterstützung für GitHub Personal Access Tokens zur Vermeidung von API-Rate-Limits.
|
||||||
- Gitea instance URL, API token, and registry owner configurable via CLI flags, environment variables, or config file.
|
- Gitea-Instanz-URL, API-Token und Registry-Owner können per CLI-Flags, Umgebungsvariablen oder Konfigurationsdatei festgelegt werden.
|
||||||
- **Diagnostics & Dry-Run Mode**:
|
- **Diagnose & Dry-Run-Modus**:
|
||||||
- Unified file and console logging powered by `logger-ctdra`.
|
- Einheitliches Datei- und Konsolen-Logging über `logger-ctdra`.
|
||||||
- `--dry-run` flag to inspect download and upload steps without making remote changes.
|
- `--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`)
|
### 1. Konfiguration (`config`)
|
||||||
Set up your Gitea credentials and optional GitHub token:
|
Richte deine Gitea-Zugangsdaten und das optionale GitHub-Token ein:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Configure Gitea instance and registry owner
|
# Gitea-Instanz und Registry-Owner konfigurieren
|
||||||
mirror-package config set \
|
mirror-package config set \
|
||||||
--gitea-url "https://gitea.creative-dragonslayer.de" \
|
--gitea-url "https://gitea.creative-dragonslayer.de" \
|
||||||
--gitea-token "your_gitea_api_token" \
|
--gitea-token "dein_gitea_api_token" \
|
||||||
--registry-owner "Linuxapps"
|
--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"
|
mirror-package config set --github-token "ghp_xxxxxxxxxxxx"
|
||||||
|
|
||||||
# Display current configuration
|
# Aktuelle Konfiguration anzeigen
|
||||||
mirror-package config show
|
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
|
```bash
|
||||||
# Add repositories to persistent configuration
|
# Repositories zur persistenten Konfiguration hinzufügen
|
||||||
mirror-package add raspberrypi/rpi-imager
|
mirror-package add raspberrypi/rpi-imager
|
||||||
mirror-package add Heroic-Games-Launcher/HeroicGamesLauncher
|
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
|
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
|
mirror-package list
|
||||||
|
|
||||||
# Remove a repository
|
# Ein Repository entfernen
|
||||||
mirror-package remove raspberrypi/rpi-imager
|
mirror-package remove raspberrypi/rpi-imager
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Synchronizing Packages (`sync`)
|
### 3. Pakete synchronisieren (`sync`)
|
||||||
|
|
||||||
```bash
|
```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
|
mirror-package sync
|
||||||
|
|
||||||
# Sync a specific repository
|
# Ein bestimmtes Repository synchronisieren
|
||||||
mirror-package sync raspberrypi/rpi-imager
|
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
|
mirror-package sync raspberrypi/rpi-imager --history
|
||||||
|
|
||||||
# Dry-run test (simulates without uploading)
|
# Dry-Run-Test (Simulation ohne Upload)
|
||||||
mirror-package sync --dry-run
|
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)**:
|
||||||
- `<registry>/<owner>/mirror-package:latest`
|
- `<registry>/<owner>/mirror-package:latest`
|
||||||
- `<registry>/<owner>/mirror-package:<version>`
|
- `<registry>/<owner>/mirror-package:<version>`
|
||||||
- `<registry>/<owner>/mirror-package:v<version>`
|
- `<registry>/<owner>/mirror-package:v<version>`
|
||||||
- `<registry>/<owner>/mirror-package:<version>.<build_number>`
|
- `<registry>/<owner>/mirror-package:<version>.<build_number>`
|
||||||
- **Testing & Preview Releases (`testing` branch)**:
|
- **Testing- & Vorschau-Releases (`testing`-Branch)**:
|
||||||
- `<registry>/<owner>/mirror-package:testing`
|
- `<registry>/<owner>/mirror-package:testing`
|
||||||
- `<registry>/<owner>/mirror-package:<version>-preview`
|
- `<registry>/<owner>/mirror-package:<version>-preview`
|
||||||
- `<registry>/<owner>/mirror-package:<version>-testing`
|
- `<registry>/<owner>/mirror-package:<version>-testing`
|
||||||
- `<registry>/<owner>/mirror-package:<version>-preview.<build_number>`
|
- `<registry>/<owner>/mirror-package:<version>-preview.<build_number>`
|
||||||
- `<registry>/<owner>/mirror-package:testing-<build_number>`
|
- `<registry>/<owner>/mirror-package:testing-<build_number>`
|
||||||
- *(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 |
|
| Container-Pfad | Zweck | Empfohlener Mount-Typ |
|
||||||
|---|---|---|
|
|---------------------------------------------|-------------------------------------------------------|------------------------------------|
|
||||||
| `/home/appuser/.config/mirror-package` | Stores `config.toml` (credentials & repository list) | Host Directory / Volume (rw) |
|
| `/home/appuser/.config/mirror-package` | Speichert `config.toml` (Zugangsdaten & Repo-Liste) | Host-Verzeichnis / Volume (rw) |
|
||||||
| `/home/appuser/.local/state/mirror-package` | Persistent application logs & state files | Host Directory / Volume (rw) |
|
| `/home/appuser/.local/state/mirror-package` | Persistente Anwendungsprotokolle & Statusdateien | Host-Verzeichnis / Volume (rw) |
|
||||||
| `/tmp` | Download & stream buffer for package binaries | `tmpfs` (RAM / tempfs, rw, noexec) |
|
| `/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:
|
Der Container wurde nach höchsten Sicherheitsstandards aufgebaut:
|
||||||
- **Unprivileged User**: Runs as `appuser` (UID `10001`, GID `10001`), never as `root`.
|
- **Unprivilegierter Benutzer**: Läuft als `appuser` (UID `10001`, GID `10001`), niemals als `root`.
|
||||||
- **Read-Only Root Filesystem**: Fully operational with `--read-only` / `read_only: true`.
|
- **Read-Only Root-Dateisystem**: Voll funktionsfähig mit `--read-only` / `read_only: true`.
|
||||||
- **No Capabilities**: All Linux capabilities can be safely dropped (`--cap-drop=ALL`).
|
- **Keine Capabilities**: Sämtliche Linux-Capabilities können sicher entzogen werden (`--cap-drop=ALL`).
|
||||||
- **No Privilege Escalation**: Enforces `no-new-privileges:true`.
|
- **Keine Rechteausweitung**: Erzwingt `no-new-privileges:true`.
|
||||||
- **Minimal Image Size**: Based on Debian Bookworm slim, containing only CA certificates and necessary shared libraries (~40 MB).
|
- **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
|
```bash
|
||||||
# Initialize / configure credentials
|
# Zugangsdaten initialisieren / konfigurieren
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--name mirror-package \
|
--name mirror-package \
|
||||||
--read-only \
|
--read-only \
|
||||||
@@ -137,10 +137,10 @@ docker run --rm \
|
|||||||
gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \
|
gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \
|
||||||
config set \
|
config set \
|
||||||
--gitea-url "https://gitea.creative-dragonslayer.de" \
|
--gitea-url "https://gitea.creative-dragonslayer.de" \
|
||||||
--gitea-token "your_token" \
|
--gitea-token "dein_token" \
|
||||||
--registry-owner "Linuxapps"
|
--registry-owner "Linuxapps"
|
||||||
|
|
||||||
# Add repositories
|
# Repositories hinzufügen
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
--read-only \
|
--read-only \
|
||||||
--cap-drop=ALL \
|
--cap-drop=ALL \
|
||||||
@@ -151,7 +151,7 @@ docker run --rm \
|
|||||||
gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \
|
gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest \
|
||||||
add raspberrypi/rpi-imager
|
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 \
|
docker run --rm \
|
||||||
--read-only \
|
--read-only \
|
||||||
--cap-drop=ALL \
|
--cap-drop=ALL \
|
||||||
@@ -163,7 +163,7 @@ docker run --rm \
|
|||||||
sync
|
sync
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Docker Compose Example (`docker-compose.yml`)
|
### 5. Docker Compose Beispiel (`docker-compose.yml`)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
@@ -183,72 +183,71 @@ services:
|
|||||||
- /tmp:rw,noexec,nosuid,size=2G
|
- /tmp:rw,noexec,nosuid,size=2G
|
||||||
environment:
|
environment:
|
||||||
- GITEA_URL=https://gitea.creative-dragonslayer.de
|
- GITEA_URL=https://gitea.creative-dragonslayer.de
|
||||||
- GITEA_TOKEN=your_gitea_api_token
|
- GITEA_TOKEN=dein_gitea_api_token
|
||||||
- REGISTRY_OWNER=Linuxapps
|
- REGISTRY_OWNER=Linuxapps
|
||||||
- GITHUB_TOKEN=your_optional_github_pat
|
- GITHUB_TOKEN=dein_optionaler_github_pat
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
command: ["sync"]
|
command: ["sync"]
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Repository Structure
|
## Repository-Struktur
|
||||||
|
|
||||||
```text
|
```text
|
||||||
├── .cargo/
|
├── .cargo/
|
||||||
│ └── config.toml # Linker & Cargo configuration
|
│ └── config.toml # Linker- & Cargo-Konfiguration
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ ├── main.yaml # CI/CD: Release, Packages & Container (Stable)
|
│ ├── main.yaml # CI/CD: Release, Pakete & Container (Stable)
|
||||||
│ └── testing.yaml # CI/CD: Preview, Packages & Container (Testing)
|
│ └── testing.yaml # CI/CD: Preview, Pakete & Container (Testing)
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── get-build-number.py # Dynamic build/revision number resolution
|
│ ├── get-build-number.py # Dynamische Ermittlung der Build-/Revisionsnummer
|
||||||
│ └── package-arch.py # Native Arch Linux package builder
|
│ └── package-arch.py # Erstellung nativer Arch Linux-Pakete
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── main.rs # CLI entrypoint & subcommand dispatch
|
│ ├── main.rs # CLI-Einstiegspunkt & Befehlsausführung
|
||||||
│ ├── lib.rs # Library root & module exports
|
│ ├── lib.rs # Bibliotheks-Wurzel & Modulexporte
|
||||||
│ ├── cli.rs # Clap CLI arguments & options
|
│ ├── cli.rs # Clap-CLI-Argumente & Optionen
|
||||||
│ ├── config.rs # config-ctdra integration & models
|
│ ├── config.rs # config-ctdra Anbindung & Konfigurationsmodelle
|
||||||
│ ├── gitea.rs # Gitea Package Registry upload client
|
│ ├── gitea.rs # Gitea-Paket-Registry Upload-Client
|
||||||
│ ├── github.rs # GitHub API client & package classifier
|
│ ├── github.rs # GitHub-API-Client & Paket-Klassifizierung
|
||||||
│ └── pipeline.rs # End-to-end sync workflow & temp storage
|
│ └── pipeline.rs # End-to-End-Synchronisationspipeline & temporärer Speicher
|
||||||
├── tests/
|
├── tests/
|
||||||
│ ├── config_tests.rs # Tests for configuration management
|
│ ├── config_tests.rs # Tests für die Konfigurationsverwaltung
|
||||||
│ ├── gitea_tests.rs # Tests for Gitea upload routing
|
│ ├── gitea_tests.rs # Tests für das Gitea-Upload-Routing
|
||||||
│ └── github_tests.rs # Tests for package classification & parsing
|
│ └── github_tests.rs # Tests für Paket-Klassifizierung & Parsing
|
||||||
├── .dockerignore # Container build ignore rules
|
├── .dockerignore # Ausschlussregeln für Container-Builds
|
||||||
├── Dockerfile # Secure minimal runtime container
|
├── Dockerfile # Gehärtetes, minimales Runtime-Container-Image
|
||||||
├── docker-compose.example.yml # Example Docker Compose configuration
|
├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose
|
||||||
├── Cargo.toml # Project manifest and packaging metadata
|
├── Cargo.toml # Projekt-Manifest und Paketierungs-Metadaten
|
||||||
├── LICENSE # GPL-3.0-or-later License
|
├── LICENSE # GPL-3.0-or-later Lizenztext
|
||||||
├── AGENTS.md # Agent & developer guidelines
|
├── AGENTS.md # Agenten- & Entwickler-Richtlinien
|
||||||
└── README.md # Documentation
|
└── README.md # Projektdokumentation
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Local Development & Building
|
## Lokale Entwicklung & Bauen
|
||||||
|
|
||||||
### Prerequisites
|
### Voraussetzungen
|
||||||
- **Rust & Cargo** (Stable toolchain, Edition 2024 supported)
|
- **Rust & Cargo** (Stable Toolchain, Edition 2024 unterstützt)
|
||||||
- **Python 3** (for packaging scripts)
|
- **Python 3** (für Paketierungsskripte)
|
||||||
|
|
||||||
### Build & Test Commands
|
### Befehle zum Bauen & Testen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run syntax and type checks
|
# Syntax- und Typprüfung ausführen
|
||||||
cargo check
|
cargo check
|
||||||
|
|
||||||
# Run unit tests
|
# Unit-Tests ausführen
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
# Build release binary
|
# Release-Binary kompilieren
|
||||||
cargo build --release
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## Lizenz
|
||||||
|
|
||||||
This project is licensed under the [GPL-3.0-or-later](LICENSE) license.
|
|
||||||
|
|
||||||
|
Dieses Projekt ist unter der [GPL-3.0-or-later](LICENSE)-Lizenz lizenziert.
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ services:
|
|||||||
mirror-package:
|
mirror-package:
|
||||||
image: gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest
|
image: gitea.creative-dragonslayer.de/linuxapps/mirror-package:latest
|
||||||
container_name: mirror-package
|
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"
|
user: "10001:10001"
|
||||||
read_only: true
|
read_only: true
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
# Persistent storage and temporary stream buffer
|
# Persistenter Speicher und temporärer Streaming-Puffer
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/home/appuser/.config/mirror-package
|
- ./config:/home/appuser/.config/mirror-package
|
||||||
- ./state:/home/appuser/.local/state/mirror-package
|
- ./state:/home/appuser/.local/state/mirror-package
|
||||||
@@ -17,8 +17,8 @@ services:
|
|||||||
- /tmp:rw,noexec,nosuid,size=2G
|
- /tmp:rw,noexec,nosuid,size=2G
|
||||||
environment:
|
environment:
|
||||||
- GITEA_URL=https://gitea.creative-dragonslayer.de
|
- GITEA_URL=https://gitea.creative-dragonslayer.de
|
||||||
- GITEA_TOKEN=your_gitea_api_token
|
- GITEA_TOKEN=dein_gitea_api_token
|
||||||
- REGISTRY_OWNER=Linuxapps
|
- REGISTRY_OWNER=Linuxapps
|
||||||
- GITHUB_TOKEN=your_optional_github_pat
|
- GITHUB_TOKEN=dein_optionaler_github_pat
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
command: ["sync"]
|
command: ["sync"]
|
||||||
|
|||||||
+27
-27
@@ -1,41 +1,41 @@
|
|||||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
/// Command line parser for mirror-package.
|
/// Befehlszeilen-Parser für mirror-package.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(
|
#[command(
|
||||||
name = "mirror-package",
|
name = "mirror-package",
|
||||||
author,
|
author,
|
||||||
version,
|
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
|
long_about = None
|
||||||
)]
|
)]
|
||||||
pub struct Cli {
|
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)]
|
#[arg(long, env = "GITEA_URL", global = true)]
|
||||||
pub gitea_url: Option<String>,
|
pub gitea_url: Option<String>,
|
||||||
|
|
||||||
/// Gitea API Token with package write permissions
|
/// Gitea-API-Token mit Schreibberechtigung für Pakete
|
||||||
#[arg(long, env = "GITEA_TOKEN", global = true)]
|
#[arg(long, env = "GITEA_TOKEN", global = true)]
|
||||||
pub gitea_token: Option<String>,
|
pub gitea_token: Option<String>,
|
||||||
|
|
||||||
/// Gitea Package Registry owner / organization
|
/// Gitea-Paket-Registry-Owner / Organisation
|
||||||
#[arg(long, env = "REGISTRY_OWNER", global = true)]
|
#[arg(long, env = "REGISTRY_OWNER", global = true)]
|
||||||
pub registry_owner: Option<String>,
|
pub registry_owner: Option<String>,
|
||||||
|
|
||||||
/// 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)]
|
#[arg(long, env = "GITHUB_TOKEN", global = true)]
|
||||||
pub github_token: Option<String>,
|
pub github_token: Option<String>,
|
||||||
|
|
||||||
/// Custom path to configuration file
|
/// Benutzerdefinierter Pfad zur Konfigurationsdatei
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
pub config: Option<PathBuf>,
|
pub config: Option<PathBuf>,
|
||||||
|
|
||||||
/// Perform a dry run without downloading or uploading packages
|
/// Führt einen Probelauf ohne Herunterladen oder Hochladen von Paketen durch
|
||||||
#[arg(long, global = true)]
|
#[arg(long, global = true)]
|
||||||
pub dry_run: bool,
|
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)]
|
#[arg(long, value_enum, default_value_t = LogLevelArg::Info, global = true)]
|
||||||
pub log_level: LogLevelArg,
|
pub log_level: LogLevelArg,
|
||||||
|
|
||||||
@@ -64,59 +64,59 @@ impl From<LogLevelArg> for logger_ctdra::LogLevel {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Synchronize packages from GitHub Releases to Gitea Package Registry
|
/// Synchronisiert Pakete aus GitHub-Releases in die Gitea-Paket-Registry
|
||||||
Sync(SyncArgs),
|
Sync(SyncArgs),
|
||||||
|
|
||||||
/// Add a GitHub repository to the persistent configuration
|
/// Fügt ein GitHub-Repository zur persistenten Konfiguration hinzu
|
||||||
Add(AddArgs),
|
Add(AddArgs),
|
||||||
|
|
||||||
/// Remove a GitHub repository from the persistent configuration
|
/// Entfernt ein GitHub-Repository aus der persistenten Konfiguration
|
||||||
#[command(alias = "rm")]
|
#[command(alias = "rm")]
|
||||||
Remove(RemoveArgs),
|
Remove(RemoveArgs),
|
||||||
|
|
||||||
/// List configured repositories and their sync status
|
/// Listet konfigurierte Repositories und deren Sync-Status auf
|
||||||
#[command(alias = "ls")]
|
#[command(alias = "ls")]
|
||||||
List,
|
List,
|
||||||
|
|
||||||
/// View or update application configuration
|
/// Zeigt die Anwendungskonfiguration an oder aktualisiert sie
|
||||||
Config(ConfigArgs),
|
Config(ConfigArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct SyncArgs {
|
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<String>,
|
pub repo: Option<String>,
|
||||||
|
|
||||||
/// Sync all configured repositories
|
/// Alle konfigurierten Repositories synchronisieren
|
||||||
#[arg(short = 'a', long)]
|
#[arg(short = 'a', long)]
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
|
|
||||||
/// Scan historical releases instead of only the latest release(s)
|
/// Historische Releases scannen anstatt nur die neuesten Release(s)
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub history: bool,
|
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)]
|
#[arg(long)]
|
||||||
pub prereleases: Option<bool>,
|
pub prereleases: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct AddArgs {
|
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,
|
pub repo: String,
|
||||||
|
|
||||||
/// Do not mirror pre-releases for this repository
|
/// Keine Pre-Releases für dieses Repository spiegeln
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub no_prereleases: bool,
|
pub no_prereleases: bool,
|
||||||
|
|
||||||
/// Immediately synchronize this repository after adding
|
/// Dieses Repository nach dem Hinzufügen sofort synchronisieren
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub sync: bool,
|
pub sync: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct RemoveArgs {
|
pub struct RemoveArgs {
|
||||||
/// Repository to remove (e.g. "raspberrypi/rpi-imager")
|
/// Zu entfernendes Repository (z. B. "raspberrypi/rpi-imager")
|
||||||
pub repo: String,
|
pub repo: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,20 +128,20 @@ pub struct ConfigArgs {
|
|||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum ConfigAction {
|
pub enum ConfigAction {
|
||||||
/// Show current configuration and configuration file location
|
/// Aktuelle Konfiguration und Speicherort der Konfigurationsdatei anzeigen
|
||||||
Show,
|
Show,
|
||||||
|
|
||||||
/// Set configuration parameters
|
/// Konfigurationsparameter setzen
|
||||||
Set {
|
Set {
|
||||||
/// Gitea instance base URL
|
/// Basis-URL der Gitea-Instanz
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
gitea_url: Option<String>,
|
gitea_url: Option<String>,
|
||||||
|
|
||||||
/// Gitea API Token
|
/// Gitea-API-Token
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
gitea_token: Option<String>,
|
gitea_token: Option<String>,
|
||||||
|
|
||||||
/// Gitea Package Registry owner
|
/// Gitea-Paket-Registry-Owner
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
registry_owner: Option<String>,
|
registry_owner: Option<String>,
|
||||||
|
|
||||||
|
|||||||
+20
-20
@@ -5,15 +5,15 @@ fn default_true() -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for an individual repository to mirror.
|
/// Konfiguration für ein einzelnes zu spiegelndes Repository.
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||||
pub struct RepoConfig {
|
pub struct RepoConfig {
|
||||||
/// Full name of the repository in "owner/repo" format.
|
/// Vollständiger Name des Repositories im Format "owner/repo".
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Whether to mirror pre-releases.
|
/// Gibt an, ob Pre-Releases gespiegelt werden sollen.
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub include_prereleases: bool,
|
pub include_prereleases: bool,
|
||||||
/// Last successfully synced tag name.
|
/// Zuletzt erfolgreich synchronisierter Tag-Name.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub last_synced_tag: Option<String>,
|
pub last_synced_tag: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -28,28 +28,28 @@ impl RepoConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persistent application configuration.
|
/// Persistente Anwendungskonfiguration.
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
|
||||||
pub struct AppConfig {
|
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")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub gitea_url: Option<String>,
|
pub gitea_url: Option<String>,
|
||||||
/// Gitea API authentication token.
|
/// Gitea-API-Authentifizierungstoken.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub gitea_token: Option<String>,
|
pub gitea_token: Option<String>,
|
||||||
/// Gitea Package Registry owner/organization name.
|
/// Name des Gitea-Paket-Registry-Owners bzw. der Organisation.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub registry_owner: Option<String>,
|
pub registry_owner: Option<String>,
|
||||||
/// 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")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub github_token: Option<String>,
|
pub github_token: Option<String>,
|
||||||
/// Configured repositories to mirror.
|
/// Konfigurierte Repositories zur Spiegelung.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub repositories: Vec<RepoConfig>,
|
pub repositories: Vec<RepoConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
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 {
|
pub fn normalize_repo_name(input: &str) -> String {
|
||||||
let trimmed = input.trim();
|
let trimmed = input.trim();
|
||||||
let cleaned = trimmed
|
let cleaned = trimmed
|
||||||
@@ -61,7 +61,7 @@ impl AppConfig {
|
|||||||
cleaned.to_string()
|
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) {
|
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)) {
|
if let Some(existing) = self.repositories.iter_mut().find(|r| r.name.eq_ignore_ascii_case(&repo.name)) {
|
||||||
existing.include_prereleases = repo.include_prereleases;
|
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 {
|
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
let before_len = self.repositories.len();
|
let before_len = self.repositories.len();
|
||||||
@@ -78,13 +78,13 @@ impl AppConfig {
|
|||||||
self.repositories.len() < before_len
|
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> {
|
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
self.repositories.iter().find(|r| r.name.eq_ignore_ascii_case(&normalized))
|
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) {
|
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
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)) {
|
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>) {
|
pub fn init_config_path(custom_path: Option<&std::path::Path>) {
|
||||||
config_ctdra::set_config_name("config");
|
config_ctdra::set_config_name("config");
|
||||||
if let Some(path) = custom_path {
|
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 {
|
pub fn get_config_file_path() -> PathBuf {
|
||||||
config_ctdra::get_config_path()
|
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 {
|
pub fn load_config() -> AppConfig {
|
||||||
config_ctdra::load_config::<AppConfig>()
|
config_ctdra::load_config::<AppConfig>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves the application configuration to disk.
|
/// Speichert die Anwendungskonfiguration auf der Festplatte.
|
||||||
pub fn save_config(config: &AppConfig) -> Result<(), config_ctdra::ConfyError> {
|
pub fn save_config(config: &AppConfig) -> Result<(), config_ctdra::ConfyError> {
|
||||||
config_ctdra::store(config)
|
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: F) -> Result<AppConfig, config_ctdra::ConfyError>
|
pub fn modify_config<F>(f: F) -> Result<AppConfig, config_ctdra::ConfyError>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut AppConfig),
|
F: FnOnce(&mut AppConfig),
|
||||||
|
|||||||
+6
-6
@@ -3,7 +3,7 @@ use anyhow::{bail, Context, Result};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
|
|
||||||
/// Credentials and target registry information for Gitea.
|
/// Zugangsdaten und Ziel-Registry-Informationen für Gitea.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct GiteaConfig {
|
pub struct GiteaConfig {
|
||||||
pub base_url: String,
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum UploadStatus {
|
pub enum UploadStatus {
|
||||||
Uploaded,
|
Uploaded,
|
||||||
@@ -33,7 +33,7 @@ pub enum UploadStatus {
|
|||||||
SimulatedDryRun,
|
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(
|
pub fn get_target_upload_urls(
|
||||||
base_url: &str,
|
base_url: &str,
|
||||||
owner: &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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GiteaClient {
|
pub struct GiteaClient {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
@@ -77,7 +77,7 @@ pub struct GiteaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GiteaClient {
|
impl GiteaClient {
|
||||||
/// Creates a new Gitea client.
|
/// Erstellt einen neuen Gitea-Client.
|
||||||
pub fn new(config: GiteaConfig) -> Result<Self> {
|
pub fn new(config: GiteaConfig) -> Result<Self> {
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
@@ -93,7 +93,7 @@ impl GiteaClient {
|
|||||||
Ok(Self { client, config })
|
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(
|
pub async fn upload_file(
|
||||||
&self,
|
&self,
|
||||||
file_path: &Path,
|
file_path: &Path,
|
||||||
|
|||||||
+16
-16
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Supported Linux package distribution types.
|
/// Unterstützte Linux-Paketverteilungstypen.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum PackageType {
|
pub enum PackageType {
|
||||||
Debian,
|
Debian,
|
||||||
@@ -10,10 +10,10 @@ pub enum PackageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<Self> {
|
pub fn from_filename(filename: &str) -> Option<Self> {
|
||||||
let name = filename.to_ascii_lowercase();
|
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")
|
if name.ends_with(".pkg.tar.zst")
|
||||||
|| name.ends_with(".pkg.tar.xz")
|
|| name.ends_with(".pkg.tar.xz")
|
||||||
|| name.ends_with(".pkg.tar.gz")
|
|| 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 {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
PackageType::Debian => "Debian (.deb)",
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct ReleaseAsset {
|
pub struct ReleaseAsset {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -48,7 +48,7 @@ pub struct ReleaseAsset {
|
|||||||
pub package_type: PackageType,
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct GitHubRelease {
|
pub struct GitHubRelease {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
@@ -77,7 +77,7 @@ struct GhApiAsset {
|
|||||||
size: u64,
|
size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Client for interacting with the GitHub API.
|
/// Client für die Interaktion mit der GitHub-API.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GitHubClient {
|
pub struct GitHubClient {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
@@ -85,7 +85,7 @@ pub struct GitHubClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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<String>) -> Result<Self> {
|
pub fn new(token: Option<String>) -> Result<Self> {
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
@@ -105,10 +105,10 @@ impl GitHubClient {
|
|||||||
Ok(Self { client, token })
|
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).
|
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
||||||
/// If `include_prereleases` is false, skips pre-releases.
|
/// Wenn `include_prereleases` false ist, werden Pre-Releases übersprungen.
|
||||||
pub async fn fetch_releases(
|
pub async fn fetch_releases(
|
||||||
&self,
|
&self,
|
||||||
repo: &str,
|
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 {
|
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 mut selected = Vec::new();
|
||||||
let latest_stable = releases.iter().find(|r| !r.prerelease);
|
let latest_stable = releases.iter().find(|r| !r.prerelease);
|
||||||
if let Some(stable) = latest_stable {
|
if let Some(stable) = latest_stable {
|
||||||
@@ -198,7 +198,7 @@ impl GitHubClient {
|
|||||||
if include_prereleases {
|
if include_prereleases {
|
||||||
let latest_prerelease = releases.iter().find(|r| r.prerelease);
|
let latest_prerelease = releases.iter().find(|r| r.prerelease);
|
||||||
if let Some(prerelease) = latest_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) {
|
if !selected.iter().any(|r| r.id == prerelease.id) {
|
||||||
selected.push(prerelease.clone());
|
selected.push(prerelease.clone());
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ impl GitHubClient {
|
|||||||
Ok(releases)
|
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<reqwest::Response> {
|
pub async fn download_asset_stream(&self, download_url: &str) -> Result<reqwest::Response> {
|
||||||
let mut req = self.client.get(download_url);
|
let mut req = self.client.get(download_url);
|
||||||
if let Some(token) = &self.token {
|
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)> {
|
pub fn parse_repo_owner_name(repo: &str) -> Result<(&str, &str)> {
|
||||||
let cleaned = repo
|
let cleaned = repo
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -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 cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod gitea;
|
pub mod gitea;
|
||||||
|
|||||||
+4
-4
@@ -11,16 +11,16 @@ use mirror_package::pipeline::{sync_single_repository, SyncOptions, SyncReport};
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
// Initialize global logger level
|
// Globalen Logging-Level initialisieren
|
||||||
set_log_level(cli.log_level.into());
|
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());
|
init_config_path(cli.config.as_deref());
|
||||||
|
|
||||||
// Load persistent configuration
|
// Persistente Konfiguration laden
|
||||||
let mut app_config = load_config();
|
let mut app_config = load_config();
|
||||||
|
|
||||||
// CLI flags override configuration values
|
// CLI-Flags überschreiben Konfigurationswerte
|
||||||
if let Some(url) = cli.gitea_url {
|
if let Some(url) = cli.gitea_url {
|
||||||
app_config.gitea_url = Some(url);
|
app_config.gitea_url = Some(url);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -8,7 +8,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||||||
use tokio::fs::{remove_file, File};
|
use tokio::fs::{remove_file, File};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
/// Summary statistics of a synchronization operation.
|
/// Zusammenfassende Statistiken eines Synchronisationsvorgangs.
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SyncReport {
|
pub struct SyncReport {
|
||||||
pub repositories_processed: usize,
|
pub repositories_processed: usize,
|
||||||
@@ -18,7 +18,7 @@ pub struct SyncReport {
|
|||||||
pub packages_simulated: usize,
|
pub packages_simulated: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options controlling a repository sync run.
|
/// Optionen zur Steuerung eines Repository-Synchronisationslaufs.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SyncOptions {
|
pub struct SyncOptions {
|
||||||
pub history: bool,
|
pub history: bool,
|
||||||
@@ -26,7 +26,7 @@ pub struct SyncOptions {
|
|||||||
pub dry_run: bool,
|
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(
|
pub async fn sync_single_repository(
|
||||||
repo_config: &RepoConfig,
|
repo_config: &RepoConfig,
|
||||||
app_config: &AppConfig,
|
app_config: &AppConfig,
|
||||||
@@ -47,7 +47,7 @@ pub async fn sync_single_repository(
|
|||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolve Gitea credentials
|
// Gitea-Zugangsdaten auflösen
|
||||||
let gitea_url = app_config.gitea_url.as_deref().unwrap_or_default();
|
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 gitea_token = app_config.gitea_token.as_deref().unwrap_or_default();
|
||||||
let registry_owner = app_config.registry_owner.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;
|
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)
|
let temp_file_path = download_to_temp_file(&github_client, &asset.download_url, &asset.name)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to download asset '{}'", asset.name))?;
|
.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 {
|
for url in &target_urls {
|
||||||
log(
|
log(
|
||||||
"upload",
|
"upload",
|
||||||
@@ -197,14 +197,14 @@ pub async fn sync_single_repository(
|
|||||||
&format!("Failed to upload '{}' to {}: {:#}", asset.name, url, e),
|
&format!("Failed to upload '{}' to {}: {:#}", asset.name, url, e),
|
||||||
LogLevel::Error,
|
LogLevel::Error,
|
||||||
);
|
);
|
||||||
// Cleanup temp file before returning error
|
// Temporäre Datei vor Fehlerrückgabe bereinigen
|
||||||
let _ = remove_file(&temp_file_path).await;
|
let _ = remove_file(&temp_file_path).await;
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup temp file
|
// Temporäre Datei bereinigen
|
||||||
if let Err(e) = remove_file(&temp_file_path).await {
|
if let Err(e) = remove_file(&temp_file_path).await {
|
||||||
log(
|
log(
|
||||||
"sync",
|
"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 !options.dry_run {
|
||||||
if let Some(tag) = latest_synced_tag {
|
if let Some(tag) = latest_synced_tag {
|
||||||
let name_copy = repo_name.to_string();
|
let name_copy = repo_name.to_string();
|
||||||
@@ -233,7 +233,7 @@ pub async fn sync_single_repository(
|
|||||||
Ok(())
|
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(
|
async fn download_to_temp_file(
|
||||||
github_client: &GitHubClient,
|
github_client: &GitHubClient,
|
||||||
download_url: &str,
|
download_url: &str,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ fn test_debian_routing() {
|
|||||||
let base_url = "https://gitea.example.com";
|
let base_url = "https://gitea.example.com";
|
||||||
let owner = "test-owner";
|
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);
|
let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Debian, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stable_urls,
|
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);
|
let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Debian, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
prerelease_urls,
|
prerelease_urls,
|
||||||
@@ -29,14 +29,14 @@ fn test_rpm_routing() {
|
|||||||
let base_url = "https://gitea.example.com/";
|
let base_url = "https://gitea.example.com/";
|
||||||
let owner = "test-owner";
|
let owner = "test-owner";
|
||||||
|
|
||||||
// Stable RPM
|
// Stabiles RPM
|
||||||
let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Rpm, false);
|
let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Rpm, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stable_urls,
|
stable_urls,
|
||||||
vec!["https://gitea.example.com/api/packages/test-owner/rpm/stable/upload"]
|
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);
|
let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Rpm, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
prerelease_urls,
|
prerelease_urls,
|
||||||
@@ -49,14 +49,14 @@ fn test_arch_routing() {
|
|||||||
let base_url = "https://gitea.example.com";
|
let base_url = "https://gitea.example.com";
|
||||||
let owner = "test-owner";
|
let owner = "test-owner";
|
||||||
|
|
||||||
// Stable Arch
|
// Stabiles Arch
|
||||||
let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Arch, false);
|
let stable_urls = get_target_upload_urls(base_url, owner, PackageType::Arch, false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stable_urls,
|
stable_urls,
|
||||||
vec!["https://gitea.example.com/api/packages/test-owner/arch/stable"]
|
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);
|
let prerelease_urls = get_target_upload_urls(base_url, owner, PackageType::Arch, true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
prerelease_urls,
|
prerelease_urls,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ fn test_package_classification() {
|
|||||||
Some(PackageType::Arch)
|
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.AppImage"), None);
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), 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);
|
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);
|
||||||
|
|||||||
Reference in New Issue
Block a user