Compare commits

...
91 Commits
Author SHA1 Message Date
DragonSlayer_14 66546f93e4 Merge pull request 'Merge testing in main: Release 1.2.2' (#28) from testing into main
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
Main Release & Publish / Erkenne relevante Code-Änderungen (push) Successful in 8s
Security Scans / Trivy & OSV-Scanner (push) Successful in 40s
Main Release & Publish / Build, Publish Packages (Stable) & Create Release (push) Successful in 4m20s
TruffleHog Secret Scan / TruffleHog (push) Successful in 9s
Reviewed-on: #28
2026-09-13 10:28:44 +00:00
DragonSlayer_14 50d5e48d22 Merge pull request 'Merge dev in testing: Passt Workflows, README und Dependencys an' (#27) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 6m59s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 29s
Security Scans / Trivy & OSV-Scanner (push) Successful in 49s
TruffleHog Secret Scan / TruffleHog (push) Successful in 15s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 53s
Unit-Tests / Unit-Tests (pull_request) Skipped
Testing Build, Publish & Preview Release / Erkenne relevante Code-Änderungen (push) Successful in 12s
Reviewed-on: #27
2026-09-13 10:17:13 +00:00
DragonSlayer_14 0d8af424c3 FEat: Hebt Version auf 1.2.2 an
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 17s
Security Scans / Trivy & OSV-Scanner (push) Successful in 32s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 46s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 24s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 36s
Unit-Tests / Unit-Tests (pull_request) Successful in 51s
2026-09-13 12:12:46 +02:00
DragonSlayer_14andClaude Sonnet 5 167825aa81 Feat: Baut Pakete nur bei tatsächlichen Code-Änderungen
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 22s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m1s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m25s
Fügt einen vorgeschalteten detect-changes-Job ein, der mittels
dorny/paths-filter@v4 prüft, ob sich Programmcode (src/, Cargo.toml,
Cargo.lock, scripts/, .cargo/ bzw. die jeweilige Workflow-Datei selbst)
geändert hat. Der Build-/Publish-/Release-Job wird nur noch ausgeführt,
wenn das der Fall ist; bei reinen Config- oder Workflow-Änderungen
schließt der Workflow erfolgreich ohne Build/Release ab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcFzG54jW33nvp1TawCZJV
2026-09-13 11:39:21 +02:00
DragonSlayer_14andClaude Sonnet 5 4fb1731020 Fix: Verschiebt allowCustomCrateRegistries in die globale Renovate-Config
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 25s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 56s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m1s
Die Option ist repo-only nicht erlaubt und wurde in der Renovate-Workflow-
Umgebung (RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES) gesetzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0156cWd2mGWNQU1kBbBPWpD7
2026-09-13 01:17:03 +02:00
DragonSlayer_14 70059896a4 Doc: Ergänzt Badges für Workflows in README.md
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 31s
Security Scans / Trivy & OSV-Scanner (push) Successful in 35s
2026-09-13 01:01:54 +02:00
Gitea-Bot c4ed24558f Style: Automatische Formatierung & Clippy-Fixes
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 32s
Security Scans / Trivy & OSV-Scanner (push) Successful in 34s
2026-09-12 22:24:35 +00:00
Gitea-Bot 28991dc52a Style: Automatische Formatierung & Clippy-Fixes
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 23s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 36s
Security Scans / Trivy & OSV-Scanner (push) Successful in 42s
2026-09-12 22:24:00 +00:00
DragonSlayer_14 8c6ec81664 Feat: Fügt code-quality-Workflow für automatische Formatierung und fixes hinzu
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 25s
Security Scans / Trivy & OSV-Scanner (push) Successful in 51s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m16s
- Introduced `code-quality.yaml` to automate Rust code formatting and Clippy fixes in the `dev` branch.
- Updated `jsonSchemas.xml` to include schema paths for new Gitea workflows.
2026-09-13 00:22:50 +02:00
DragonSlayer_14 899a70a189 Merge remote-tracking branch 'origin/main' into dev 2026-09-13 00:22:46 +02:00
DragonSlayer_14 1d441cc83a Merge pull request 'chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.82.3' (#26) from renovate/gitea-actions into dev
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 11s
Security Scans / Trivy & OSV-Scanner (push) Successful in 17s
Reviewed-on: #26
2026-09-12 11:08:21 +00:00
DragonSlayer_14 08b9ba74c6 Merge pull request 'chore(deps): update rust crate serde-saphyr to v1.2.0' (#25) from renovate/cargo-dependencies into dev
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 11s
Security Scans / Trivy & OSV-Scanner (push) Successful in 16s
Reviewed-on: #25
2026-09-12 11:07:37 +00:00
Renovate-Bot c72122e20e chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.82.3
Unit-Tests / Unit-Tests (pull_request) Skipped
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 30s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 17s
2026-09-12 09:00:36 +00:00
DragonSlayer_14 677aef8e78 Fix: Aktiviert allowCustomCrateRegistries in renovate.json
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 12s
Security Scans / Trivy & OSV-Scanner (push) Successful in 17s
2026-09-11 23:22:54 +02:00
Renovate-Bot a465718f41 chore(deps): update rust crate serde-saphyr to v1.2.0
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
Unit-Tests / Unit-Tests (pull_request) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 25s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 27s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 34s
2026-09-11 21:07:40 +00:00
DragonSlayer_14 a8dd84045e Merge pull request 'Mereg testing in main: Release 1.2.1' (#23) from testing into main
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 29s
Main Release & Publish / Build, Publish Packages (Stable) & Create Release (push) Successful in 4m53s
TruffleHog Secret Scan / TruffleHog (push) Successful in 14s
Reviewed-on: #23
2026-09-11 20:51:28 +00:00
DragonSlayer_14 a4f9f4894a Merge pull request 'Merge dev in testing: Feat: Aktualisiert Projekt und Workflows' (#22) from dev into testing
Security Scans / Trivy & OSV-Scanner (push) Successful in 42s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 5m23s
TruffleHog Secret Scan / TruffleHog (push) Successful in 17s
Unit-Tests / Unit-Tests (pull_request) Skipped
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 11s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 17s
Reviewed-on: #22
2026-09-11 20:30:29 +00:00
DragonSlayer_14 31cfc8e460 Feat: Aktualisiert Projekt und Workflows
TruffleHog Secret Scan / TruffleHog (push) Successful in 14s
Security Scans / Trivy & OSV-Scanner (push) Successful in 27s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 26s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 44s
Unit-Tests / Unit-Tests (pull_request) Successful in 1m23s
2026-09-11 22:26:31 +02:00
DragonSlayer_14 f890c89f1c Merge pull request 'Merge testing in main: Release 1.2.0' (#21) from testing into main
Main Release & Publish / Build, Publish Packages (Stable) & Create Release (push) Successful in 2m37s
Reviewed-on: #21
2026-08-26 18:43:08 +00:00
DragonSlayer_14 0dbe24e7d9 Merge pull request 'Merge dev in testing: Feat: Ersetzt program.rs, sudo.rs und config.rs durch entsprechende Rust-Crates. Lagert Unit-Tests in eigenen Ordner aus.' (#20) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 2m31s
Reviewed-on: #20
2026-08-26 18:35:12 +00:00
DragonSlayer_14 51a800802d Feat: Ersetzt program.rs, sudo.rs und config.rs durch entsprechende Rust-Crates. Lagert Unit-Tests in eigenen Ordner aus. 2026-08-26 20:34:12 +02:00
DragonSlayer_14 9819dfce5b Merge remote-tracking branch 'origin/main' into dev 2026-08-26 20:34:05 +02:00
DragonSlayer_14 d974f1b7ac Merge pull request 'Merge dev in testing: Ändert serde_yaml auf serde-saphyr' (#19) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 2m33s
Reviewed-on: #19
2026-08-24 19:42:07 +00:00
DragonSlayer_14 9858ee959d Merge branch 'testing' into dev 2026-08-24 19:41:59 +00:00
DragonSlayer_14 d726bab972 Feat: Dependency-Updates mit Versionierung und Deprecation von serde_yaml 2026-08-24 21:41:13 +02:00
DragonSlayer_14 851cfaeda8 Merge remote-tracking branch 'origin/dev' into dev 2026-08-24 21:40:33 +02:00
DragonSlayer_14 9c4a1e1c95 Feat: Dependency auf serde-saphyr aktualisiert und Version auf 1.1.0 erhöht 2026-08-24 21:40:28 +02:00
DragonSlayer_14 fc798b39a3 Feat: Dependency-Updates mit Versionierung und Deprecation von serde_yaml
Aktualisierung von time, serde und libc sowie Hinweis zur Deprecation von serde_yaml
2026-08-24 21:31:37 +02:00
DragonSlayer_14 2bde2baf66 Merge pull request 'Merge dev in testing: Feat: Einführung eines Skripts zur dynamischen Bestimmung der Build-Nummer und Integration in die Build-Prozesse' (#18) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 1m52s
Reviewed-on: #18
2026-08-24 19:21:13 +00:00
DragonSlayer_14 24f127c629 Merge branch 'testing' into dev 2026-08-24 19:21:04 +00:00
DragonSlayer_14 b4bf33ebd9 Merge remote-tracking branch 'origin/dev' into dev 2026-08-24 21:19:22 +02:00
DragonSlayer_14 83ae3ad3cc Feat: Einführung eines Skripts zur dynamischen Bestimmung der Build-Nummer und Integration in die Build-Prozesse 2026-08-24 21:19:14 +02:00
DragonSlayer_14 b525a6c954 Merge pull request 'Merge testing in main: 1.0.2 Release' (#17) from testing into main
Main Release & Publish / Build, Publish Packages (Stable) & Create Release (push) Successful in 1m54s
Reviewed-on: #17
2026-08-24 19:06:54 +00:00
DragonSlayer_14 18854200c0 Merge branch 'main' into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 1m50s
2026-08-24 19:04:37 +00:00
DragonSlayer_14 7e644e52c6 Merge pull request 'Merge dev in testing: Behebt Fehler bei RPM-Paketgenerierung' (#16) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 1m57s
Reviewed-on: #16
2026-08-24 19:01:07 +00:00
DragonSlayer_14 fd0ddf6aeb Merge branch 'testing' into dev 2026-08-24 19:00:58 +00:00
DragonSlayer_14 c0ad5987df Merge remote-tracking branch 'origin/dev' into dev 2026-08-24 21:00:13 +02:00
DragonSlayer_14 52d9bb6880 Feat: Version auf 1.0.2 aktualisiert und Cross-Compilation-Anleitung hinzugefügt 2026-08-24 21:00:08 +02:00
DragonSlayer_14 d8ce9849b8 Merge pull request 'Merge dev into testing: Fügt Unterstützung für die Architekturen arm64 und i368 hinzu' (#15) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 1m51s
Reviewed-on: #15
2026-08-24 18:53:03 +00:00
DragonSlayer_14 f04663803c Merge branch 'testing' into dev 2026-08-24 18:53:00 +00:00
DragonSlayer_14 a1fac95722 Feat: Hebt Version auf 1.0.1 an 2026-08-24 20:51:56 +02:00
DragonSlayer_14 020e2f3b1a Merge remote-tracking branch 'origin/dev' into dev 2026-08-24 20:51:11 +02:00
DragonSlayer_14 aa9e1da74f Feat: Unterstützung für Cross-Compilation und mehrere Architekturen hinzugefügt
Aktualisierung der Build-Prozesse für aarch64, i686 und arm64 mit flexibler Zielangabe im Skript
2026-08-24 20:51:06 +02:00
DragonSlayer_14 cc1643cb8b Merge pull request 'Merge testing in main: 1.0.0 Release' (#14) from testing into main
Main Release & Publish / Build, Publish Packages (Stable) & Create Release (push) Successful in 52s
Reviewed-on: #14
2026-08-24 18:03:33 +00:00
DragonSlayer_14 c2d21e93c8 Merge pull request 'Merge dev in testing: Testing-Version 1.0.0' (#13) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 55s
Reviewed-on: #13
2026-08-24 18:01:13 +00:00
DragonSlayer_14 c02644b48d Merge branch 'testing' into dev 2026-08-24 18:01:05 +00:00
DragonSlayer_14 a1d22ce71f Merge remote-tracking branch 'origin/dev' into dev 2026-08-24 19:59:39 +02:00
DragonSlayer_14 10dc412566 Feat: Hebt Version auf 1.0.0 an 2026-08-24 19:59:33 +02:00
DragonSlayer_14 68c1b07b23 Feat: Fügt Anweisung für Arch fürs hinzufügen des Repo-GPG-Keys hinzu 2026-08-24 19:59:06 +02:00
DragonSlayer_14 25e076ba18 Merge pull request 'Merge dev in testing: Feat: Hebt Version auf 0.1.3 an' (#12) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 1m2s
Reviewed-on: #12
2026-08-24 17:48:03 +00:00
DragonSlayer_14 a0ffe731bf Merge branch 'testing' into dev 2026-08-24 17:47:51 +00:00
DragonSlayer_14 9a2a1730dd Feat: Hebt Version auf 0.1.3 an 2026-08-24 19:47:16 +02:00
DragonSlayer_14 851c863b24 Merge pull request 'Merge dev in testing: Passt Arch-Url an und behebt Fehler, wodurch das Repo zu testing/upload wurde' (#11) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 54s
Reviewed-on: #11
2026-08-24 17:45:33 +00:00
DragonSlayer_14 0d811cfb75 Fix: Entfernt /upload von Arch-Package-Upload-URL 2026-08-24 19:44:25 +02:00
DragonSlayer_14 bd374b95fc Fix: Arch-Repository-Namen in README.md aktualisiert 2026-08-24 19:37:18 +02:00
DragonSlayer_14 223ed09d94 Merge pull request 'Merge dev in testing: Passt Abhöngigkeiten des Pakets an' (#10) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 57s
Reviewed-on: #10
2026-08-24 17:29:27 +00:00
DragonSlayer_14 54f74a2f2a Feat: Passt Version auf 0.1.2 an. 2026-08-24 19:28:48 +02:00
DragonSlayer_14 736dc1f2e5 Feat: Passt Abhängigkeiten an 2026-08-24 19:20:51 +02:00
DragonSlayer_14 2c232ec953 Merge pull request 'Merge dev in testing: Passt Paketierungsworkflow an' (#9) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 56s
Reviewed-on: #9
2026-08-23 16:55:05 +00:00
DragonSlayer_14 451abe83c0 Ref: Version auf 0.1.1 aktualisiert 2026-08-23 18:54:22 +02:00
DragonSlayer_14 8ef3ad28a0 Ref: Arch-Paketierung in separates Skript auslagern und Workflows aktualisieren 2026-08-23 18:52:38 +02:00
DragonSlayer_14 24955f75b5 Ref: Packaging-Definitionen in Cargo.toml verschieben und alte Dateien entfernen 2026-08-23 18:46:33 +02:00
DragonSlayer_14 d44930de7e Ref: Aktualisiere Anweisungen in der README.md 2026-08-23 18:26:20 +02:00
DragonSlayer_14 819772d00d Merge pull request 'Merge dev in testing: Anpassung Fallback auf dokcer-compose und Fehlerbehanldung bei sudo-Ausführung verbessert' (#8) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Successful in 56s
Reviewed-on: #8
2026-08-23 16:14:18 +00:00
DragonSlayer_14 d873619a6f Fix: Fallback auf docker-compose bei Nichtverfügbarkeit in updater.rs 2026-08-23 18:13:32 +02:00
DragonSlayer_14 62e50748da Fix: Fehlerbehandlung bei sudo-Ausführung verbessert 2026-08-23 18:12:54 +02:00
DragonSlayer_14 66279df701 Merge pull request 'Aktualisiere Authentifizierungsmethode: Verwendung von Authorization-Header statt --user und Entfernen von ACTOR-Variable' (#7) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 53s
Reviewed-on: #7
2026-08-23 15:56:32 +00:00
DragonSlayer_14 a88d7d7723 Aktualisiere Authentifizierungsmethode: Verwendung von Authorization-Header statt --user und Entfernen von ACTOR-Variable 2026-08-23 17:56:09 +02:00
DragonSlayer_14 5923b9f6a4 Merge pull request 'Merge dev in testing: Action-Anpassung von dev auf testing Branch' (#6) from dev into testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 56s
Reviewed-on: #6
2026-08-23 15:49:57 +00:00
DragonSlayer_14 2e852a2a48 Refactor: 127-Status-Prüfung entfernen und Bedingung vereinfachen 2026-08-23 17:49:02 +02:00
DragonSlayer_14 c2da58c74e Refactor: Entferne Terminal-Überprüfung und vereinfache Ausgabehandling 2026-08-23 17:48:50 +02:00
DragonSlayer_14 d042137d36 Fix: Branch-Name von dev zu testing aktualisiert und dev.yml in testing.yaml umbenannt 2026-08-23 17:44:41 +02:00
DragonSlayer_14 b7a2e60790 Fix: Workflow-Konfiguration für Testing-Branch aktualisiert und dev.yml in testing.yaml umbenannt 2026-08-23 17:41:22 +02:00
DragonSlayer_14 0aa20c7362 Merge pull request 'Merge dev in testing: Anpassung der Actions' (#5) from dev into testing
Dev Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 53s
Reviewed-on: #5
2026-08-23 15:33:22 +00:00
DragonSlayer_14 ad6de96d14 Merge pull request 'Feature/gitea actions' (#4) from feature/gitea-actions into dev
Reviewed-on: #4
2026-08-23 15:32:43 +00:00
DragonSlayer_14 e3e12afa0d Aktualisiere Authentifizierungsmethode und erweitere TOKEN-Definition in CI/CD-Workflows 2026-08-23 17:32:09 +02:00
DragonSlayer_14 8595977d90 Fix: Packaging-Tools-Installation über Prebuilt-Binaries in CI/CD-Workflows
Ersetzt `cargo install` durch direkte Binärdateien von cargo-binstall in beiden Workflows.
2026-08-23 17:11:01 +02:00
DragonSlayer_14 abdd2f5df8 Merge pull request 'Fix: Branch-Name-Anpassung und Tool-Version-Update in CI/CD-Workflows' (#3) from dev into testing
Dev Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Failing after 5m27s
Reviewed-on: #3
2026-08-23 14:58:10 +00:00
DragonSlayer_14 52377f71f8 Merge pull request 'Fix: Branch-Name-Anpassung und Tool-Version-Update in CI/CD-Workflows' (#2) from feature/gitea-actions into dev
Reviewed-on: #2
2026-08-23 14:56:58 +00:00
DragonSlayer_14 a2fb03197a Fix: Branch-Name-Anpassung und Tool-Version-Update in CI/CD-Workflows
Aktualisiere Branch-Name von `dev` zu `testing` und erhöhe `actions/checkout`-Version auf v7 mit Cache-Deaktivierung in beiden Workflows.
2026-08-23 16:43:44 +02:00
DragonSlayer_14 f99e621d3b Merge pull request 'Merge Feature/gitea-actions in dev' (#1) from feature/gitea-actions into dev
Dev Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Canceled after 9m3s
Reviewed-on: #1
2026-08-23 14:35:57 +00:00
DragonSlayer_14 896800da6a Feat: Dokumentation zur Installation über Gitea Paket-Registry für Debian/Ubuntu, Fedora/RHEL und Arch ergänzt 2026-08-23 13:26:31 +02:00
DragonSlayer_14 a34e5520d0 Feat: CI/CD Workflows und Paketierungsskripte für Arch/Fedora ergänzt, Dokumentation aktualisiert 2026-08-23 13:26:16 +02:00
DragonSlayer_14 589d0f6be1 Refactor: Verwendung von jq zur präziseren ID-Extraktion aus JSON-Responses mit Fallback auf grep 2026-08-23 13:09:27 +02:00
DragonSlayer_14 837eab18b0 Feat: Docker-Images bereinigen und in Update-Process integrieren 2026-08-23 13:08:49 +02:00
DragonSlayer_14 c6a70180fe Feat: Ersetzt Preview-Release durch Pre-Release im Dev-Workflow 2026-08-23 13:00:40 +02:00
DragonSlayer_14 071bd70a72 Feat: Fügt Erstellung von Preview-Release und Upload von Assets im Dev-Workflow hinzu 2026-08-23 12:56:33 +02:00
DragonSlayer_14 d07bb324d8 Feat: Fügt automatisches bauen der Pakete und Release via Actions hinzu 2026-08-23 12:52:17 +02:00
DragonSlayer_14 a84e248e52 Feat: Fügt AGENTS.md hinzu und aktualisiert Dokumentation 2026-08-23 00:39:20 +02:00
DragonSlayer_14 3e2c7af0cb Feat: Fügt Paketdefinitionen für Fedora/RPM und Arch/Makepkg hinzu und aktualisiert Dokumentation 2026-08-23 00:39:09 +02:00
DragonSlayer_14 739877c24b Feat: Fügt Projektstruktur und docker-update-Funktionalität hinzu 2026-08-23 00:24:13 +02:00
31 changed files with 3318 additions and 2 deletions
+15
View File
@@ -0,0 +1,15 @@
[registry]
default = "gitea"
[registries.gitea]
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index
# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git
[net]
git-fetch-with-cli = true
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.i686-unknown-linux-gnu]
linker = "i686-linux-gnu-gcc"
+54
View File
@@ -0,0 +1,54 @@
name: Code Quality (Auto-Format & Clippy-Fix)
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
fix:
name: Formatierung & Clippy automatisch beheben
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
ref: ${{ gitea.ref_name || github.ref_name }}
token: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
components: clippy, rustfmt
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Formatierung automatisch beheben
run: cargo fmt
- name: Clippy-Fixes automatisch anwenden
run: cargo clippy --fix --allow-dirty --allow-staged --all-targets
- name: Änderungen committen & pushen
run: |
if [ -n "$(git status --porcelain)" ]; then
git config user.name "Gitea-Bot"
git config user.email "no-reply@creativedragonslayer.de"
git add -A
git commit -m "Style: Automatische Formatierung & Clippy-Fixes"
git push origin HEAD:${{ gitea.ref_name || github.ref_name }}
else
echo "Keine Formatierungs- oder Clippy-Änderungen."
fi
+254
View File
@@ -0,0 +1,254 @@
name: Main Release & Publish
on:
push:
branches:
- main
jobs:
detect-changes:
name: Erkenne relevante Code-Änderungen
runs-on: ubuntu-latest
outputs:
code_changed: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Prüfe auf Änderungen am Programmcode
uses: dorny/paths-filter@v4
id: filter
with:
filters: |
code:
- 'src/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'scripts/**'
- '.cargo/**'
- '.gitea/workflows/main.yaml'
release-and-publish:
name: Build, Publish Packages (Stable) & Create Release
needs: detect-changes
if: needs.detect-changes.outputs.code_changed == 'true'
runs-on: ubuntu-latest
env:
CARGO_BINSTALL_VERSION: "1.23.0"
CARGO_DEB_VERSION: "3.8.0"
CARGO_GENERATE_RPM_VERSION: "0.21.0"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Alte Paketierungs-Ausgaben aus dem Cache entfernen
run: rm -rf target/debian target/generate-rpm target/arch
- name: Install Cross-Compilation Toolchains (apt)
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
- name: Cache Rustup Cross-Compilation-Targets
id: cache-rustup-targets
uses: actions/cache@v6
with:
path: |
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
- name: Add Rust Cross-Compilation Targets
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
- name: PATH um Cargo-bin-Verzeichnis ergänzen
run: |
mkdir -p ~/.cargo/bin
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Cache Packaging-Tools (cargo-binstall, cargo-deb, cargo-generate-rpm)
id: cache-packaging-tools
uses: actions/cache@v6
with:
path: |
~/.cargo/bin/cargo-binstall
~/.cargo/bin/cargo-deb
~/.cargo/bin/cargo-generate-rpm
key: packaging-tools-${{ runner.os }}-${{ env.CARGO_BINSTALL_VERSION }}-${{ env.CARGO_DEB_VERSION }}-${{ env.CARGO_GENERATE_RPM_VERSION }}
- name: Install Packaging Tools (Prebuilt Binaries)
if: steps.cache-packaging-tools.outputs.cache-hit != 'true'
run: |
curl -fsSL "https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-x86_64-unknown-linux-musl.tgz" | tar -xz -C ~/.cargo/bin
~/.cargo/bin/cargo-binstall -y --no-symlinks "cargo-deb@${CARGO_DEB_VERSION}" "cargo-generate-rpm@${CARGO_GENERATE_RPM_VERSION}"
- name: Run Tests
run: |
cargo test
- name: Build Release Binaries
run: |
cargo build --release --target x86_64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu
cargo build --release --target i686-unknown-linux-gnu
- name: Determine Build Number
id: build_num
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
BUILD_NUM=$(python3 scripts/get-build-number.py)
echo "build_number=${BUILD_NUM}" >> $GITHUB_OUTPUT
echo "BUILD_NUMBER=${BUILD_NUM}" >> $GITHUB_ENV
echo "Ermittelte Build-Nummer: ${BUILD_NUM}"
- name: Build Debian Packages (.deb)
run: |
cargo deb --target x86_64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
cargo deb --target aarch64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
cargo deb --target i686-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
- name: Build Fedora / RPM Packages (.rpm)
run: |
mkdir -p target/generate-rpm
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
- name: Build Arch Linux Packages (.pkg.tar.zst)
run: |
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
- name: Publish Packages to Gitea Package Registry
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
echo "Veröffentliche Debian-Paket (Distribution: stable, Component: main)..."
for deb in target/debian/*.deb; do
[ -f "$deb" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$deb" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/debian/pool/stable/main/upload"
done
echo "Veröffentliche Fedora/RPM-Paket (Gruppe: stable)..."
for rpm in target/generate-rpm/*.rpm; do
[ -f "$rpm" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$rpm" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/rpm/stable/upload"
done
echo "Veröffentliche Arch Linux-Paket (Repository: stable)..."
for pkg in target/arch/*.pkg.tar.zst; do
[ -f "$pkg" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$pkg" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/stable"
done
- name: Create Gitea Release and Upload Assets
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
TAG_NAME="v${VERSION}"
RELEASE_TITLE="Release ${TAG_NAME}"
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} ${VERSION}."
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
GET_RESP=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${TAG_NAME}")
HTTP_CODE=$(echo "$GET_RESP" | tail -n1)
BODY=$(echo "$GET_RESP" | sed '$d')
RELEASE_ID=""
if [ "$HTTP_CODE" -eq 200 ]; then
RELEASE_ID=$(echo "$BODY" | jq -r '.id // empty' 2>/dev/null || echo "$BODY" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)
echo "Bestehendes Release gefunden (ID: ${RELEASE_ID})."
else
echo "Erstelle neues Release ${TAG_NAME}..."
CREATE_PAYLOAD=$(cat <<EOF
{
"tag_name": "${TAG_NAME}",
"target_commitish": "main",
"name": "${RELEASE_TITLE}",
"body": "${RELEASE_NOTES}",
"draft": false,
"prerelease": false
}
EOF
)
CREATE_RESP=$(curl -f -s -S -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$CREATE_PAYLOAD" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases")
RELEASE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty' 2>/dev/null || echo "$CREATE_RESP" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)
echo "Neues Release erstellt (ID: ${RELEASE_ID})."
fi
if [ -z "$RELEASE_ID" ]; then
echo "Fehler: Release-ID konnte nicht ermittelt werden!"
exit 1
fi
EXISTING_ASSETS_JSON=$(curl -s \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" || echo "[]")
for file in target/debian/*.deb target/generate-rpm/*.rpm target/arch/*.pkg.tar.zst; do
[ -f "$file" ] || continue
filename="$(basename "$file")"
echo "Lade Release-Asset hoch: $filename"
ASSET_ID=$(echo "$EXISTING_ASSETS_JSON" | jq -r --arg name "$filename" '.[]? | select(.name == $name) | .id' 2>/dev/null | head -n1 || true)
if [ -n "$ASSET_ID" ] && [ "$ASSET_ID" != "null" ]; then
echo "Lösche altes Asset mit ID ${ASSET_ID}..."
curl -s -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}" || true
fi
curl -f -s -S -X POST \
-H "Authorization: token ${TOKEN}" \
-F "attachment=@${file}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}"
echo "Asset ${filename} erfolgreich hochgeladen."
done
+27
View File
@@ -0,0 +1,27 @@
name: Renovate
on:
schedule:
- cron: "0 * * * *"
workflow_dispatch:
jobs:
renovate:
name: Dependency-Updates prüfen & Pull Requests erstellen
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:44.82.3
steps:
- name: Renovate ausführen
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ gitea.server_url || github.server_url }}/api/v1/
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
RENOVATE_AUTODISCOVER: "false"
RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES: "true"
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creativedragonslayer.de>"
RENOVATE_HOST_RULES: >-
[{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}]
GITHUB_COM_TOKEN: ${{ secrets.GH_RENOVATE_TOKEN }}
LOG_LEVEL: info
+95
View File
@@ -0,0 +1,95 @@
name: Security Scans
on:
push:
branches:
- main
- testing
- dev
pull_request:
schedule:
- cron: "0 5 * * 1"
workflow_dispatch:
jobs:
security-scan:
name: Trivy & OSV-Scanner
runs-on: ubuntu-latest
env:
TRIVY_VERSION: "0.74.0"
OSV_SCANNER_VERSION: "2.5.1"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache Trivy-Binary
id: cache-trivy
uses: actions/cache@v6
with:
path: ~/.local/bin/trivy
key: trivy-bin-${{ runner.os }}-${{ env.TRIVY_VERSION }}
- name: Install Trivy
if: steps.cache-trivy.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trivy.tar.gz \
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
tar -xzf trivy.tar.gz trivy
chmod +x trivy
mv trivy "$HOME/.local/bin/trivy"
rm -f trivy.tar.gz
- name: Ermittle Cache-Datum für Trivy-DB
run: echo "CACHE_DATE=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV"
- name: Cache Trivy-Schwachstellen-Datenbank
uses: actions/cache@v6
with:
path: ~/.cache/trivy
key: trivy-db-${{ runner.os }}-${{ env.CACHE_DATE }}
restore-keys: |
trivy-db-${{ runner.os }}-
- name: Run Trivy Scanner
run: |
trivy fs \
--scanners vuln,secret,misconfig \
--severity CRITICAL,HIGH \
--format json \
--output trivy-results.json \
--exit-code 0 \
.
- name: Cache OSV-Scanner-Binary
id: cache-osv-scanner
uses: actions/cache@v6
with:
path: ~/.local/bin/osv-scanner
key: osv-scanner-bin-${{ runner.os }}-${{ env.OSV_SCANNER_VERSION }}
- name: Install OSV-Scanner
if: steps.cache-osv-scanner.outputs.cache-hit != 'true'
run: |
curl -fsSL -o "$HOME/.local/bin/osv-scanner" \
"https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
chmod +x "$HOME/.local/bin/osv-scanner"
- name: Run OSV-Scanner
run: |
set +e
osv-scanner scan source --recursive --format json --output-file osv-results.json .
echo "OSV_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
run: |
python3 scripts/report-security-issue.py trivy-results.json osv-results.json
+254
View File
@@ -0,0 +1,254 @@
name: Testing Build, Publish & Preview Release
on:
push:
branches:
- testing
jobs:
detect-changes:
name: Erkenne relevante Code-Änderungen
runs-on: ubuntu-latest
outputs:
code_changed: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Prüfe auf Änderungen am Programmcode
uses: dorny/paths-filter@v4
id: filter
with:
filters: |
code:
- 'src/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'scripts/**'
- '.cargo/**'
- '.gitea/workflows/testing.yaml'
build-and-publish:
name: Build, Publish Packages (Testing) & Create Preview Release
needs: detect-changes
if: needs.detect-changes.outputs.code_changed == 'true'
runs-on: ubuntu-latest
env:
CARGO_BINSTALL_VERSION: "1.23.0"
CARGO_DEB_VERSION: "3.8.0"
CARGO_GENERATE_RPM_VERSION: "0.21.0"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Alte Paketierungs-Ausgaben aus dem Cache entfernen
run: rm -rf target/debian target/generate-rpm target/arch
- name: Install Cross-Compilation Toolchains (apt)
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
- name: Cache Rustup Cross-Compilation-Targets
id: cache-rustup-targets
uses: actions/cache@v6
with:
path: |
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
- name: Add Rust Cross-Compilation Targets
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
- name: PATH um Cargo-bin-Verzeichnis ergänzen
run: |
mkdir -p ~/.cargo/bin
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: Cache Packaging-Tools (cargo-binstall, cargo-deb, cargo-generate-rpm)
id: cache-packaging-tools
uses: actions/cache@v6
with:
path: |
~/.cargo/bin/cargo-binstall
~/.cargo/bin/cargo-deb
~/.cargo/bin/cargo-generate-rpm
key: packaging-tools-${{ runner.os }}-${{ env.CARGO_BINSTALL_VERSION }}-${{ env.CARGO_DEB_VERSION }}-${{ env.CARGO_GENERATE_RPM_VERSION }}
- name: Install Packaging Tools (Prebuilt Binaries)
if: steps.cache-packaging-tools.outputs.cache-hit != 'true'
run: |
curl -fsSL "https://github.com/cargo-bins/cargo-binstall/releases/download/v${CARGO_BINSTALL_VERSION}/cargo-binstall-x86_64-unknown-linux-musl.tgz" | tar -xz -C ~/.cargo/bin
~/.cargo/bin/cargo-binstall -y --no-symlinks "cargo-deb@${CARGO_DEB_VERSION}" "cargo-generate-rpm@${CARGO_GENERATE_RPM_VERSION}"
- name: Run Tests
run: |
cargo test
- name: Build Release Binaries
run: |
cargo build --release --target x86_64-unknown-linux-gnu
cargo build --release --target aarch64-unknown-linux-gnu
cargo build --release --target i686-unknown-linux-gnu
- name: Determine Build Number
id: build_num
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
BUILD_NUM=$(python3 scripts/get-build-number.py)
echo "build_number=${BUILD_NUM}" >> $GITHUB_OUTPUT
echo "BUILD_NUMBER=${BUILD_NUM}" >> $GITHUB_ENV
echo "Ermittelte Build-Nummer: ${BUILD_NUM}"
- name: Build Debian Packages (.deb)
run: |
cargo deb --target x86_64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
cargo deb --target aarch64-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
cargo deb --target i686-unknown-linux-gnu --deb-revision "${BUILD_NUMBER}" --no-build
- name: Build Fedora / RPM Packages (.rpm)
run: |
mkdir -p target/generate-rpm
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
- name: Build Arch Linux Packages (.pkg.tar.zst)
run: |
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
- name: Publish Packages to Gitea Package Registry
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
echo "Veröffentliche Debian-Paket (Distribution: testing, Component: main)..."
for deb in target/debian/*.deb; do
[ -f "$deb" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$deb" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/debian/pool/testing/main/upload"
done
echo "Veröffentliche Fedora/RPM-Paket (Gruppe: testing)..."
for rpm in target/generate-rpm/*.rpm; do
[ -f "$rpm" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$rpm" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/rpm/testing/upload"
done
echo "Veröffentliche Arch Linux-Paket (Repository: testing)..."
for pkg in target/arch/*.pkg.tar.zst; do
[ -f "$pkg" ] || continue
curl -f -s -S -X PUT \
-H "Authorization: token ${TOKEN}" \
--upload-file "$pkg" \
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/testing"
done
- name: Create Gitea Pre-Release and Upload Assets
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
TAG_NAME="v${VERSION}-preview"
RELEASE_TITLE="Preview Release ${TAG_NAME}"
RELEASE_NOTES="Automatisches Preview-Release für ${REPO_NAME} ${VERSION} (Branch: Testing)."
echo "Erstelle oder hole Preview-Release für Tag ${TAG_NAME} in ${REPO}..."
GET_RESP=$(curl -s -w "\n%{http_code}" \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/tags/${TAG_NAME}")
HTTP_CODE=$(echo "$GET_RESP" | tail -n1)
BODY=$(echo "$GET_RESP" | sed '$d')
RELEASE_ID=""
if [ "$HTTP_CODE" -eq 200 ]; then
RELEASE_ID=$(echo "$BODY" | jq -r '.id // empty' 2>/dev/null || echo "$BODY" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)
echo "Bestehendes Release gefunden (ID: ${RELEASE_ID})."
else
echo "Erstelle neues Preview-Release ${TAG_NAME}..."
CREATE_PAYLOAD=$(cat <<EOF
{
"tag_name": "${TAG_NAME}",
"target_commitish": "testing",
"name": "${RELEASE_TITLE}",
"body": "${RELEASE_NOTES}",
"draft": false,
"prerelease": true
}
EOF
)
CREATE_RESP=$(curl -f -s -S -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$CREATE_PAYLOAD" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases")
RELEASE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty' 2>/dev/null || echo "$CREATE_RESP" | grep -o '"id":[0-9]*' | head -n1 | cut -d: -f2)
echo "Neues Preview-Release erstellt (ID: ${RELEASE_ID})."
fi
if [ -z "$RELEASE_ID" ]; then
echo "Fehler: Release-ID konnte nicht ermittelt werden!"
exit 1
fi
EXISTING_ASSETS_JSON=$(curl -s \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" || echo "[]")
for file in target/debian/*.deb target/generate-rpm/*.rpm target/arch/*.pkg.tar.zst; do
[ -f "$file" ] || continue
filename="$(basename "$file")"
echo "Lade Release-Asset hoch: $filename"
ASSET_ID=$(echo "$EXISTING_ASSETS_JSON" | jq -r --arg name "$filename" '.[]? | select(.name == $name) | .id' 2>/dev/null | head -n1 || true)
if [ -n "$ASSET_ID" ] && [ "$ASSET_ID" != "null" ]; then
echo "Lösche altes Asset mit ID ${ASSET_ID}..."
curl -s -X DELETE \
-H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}" || true
fi
curl -f -s -S -X POST \
-H "Authorization: token ${TOKEN}" \
-F "attachment=@${file}" \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${filename}"
echo "Asset ${filename} erfolgreich hochgeladen."
done
+59
View File
@@ -0,0 +1,59 @@
name: TruffleHog Secret Scan
on:
push:
pull_request:
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
jobs:
trufflehog-scan:
name: TruffleHog
runs-on: ubuntu-latest
env:
TRUFFLEHOG_VERSION: "3.97.4"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache TruffleHog-Binary
id: cache-trufflehog
uses: actions/cache@v6
with:
path: ~/.local/bin/trufflehog
key: trufflehog-bin-${{ runner.os }}-${{ env.TRUFFLEHOG_VERSION }}
- name: Install TruffleHog
if: steps.cache-trufflehog.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trufflehog.tar.gz \
"https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
tar -xzf trufflehog.tar.gz trufflehog
chmod +x trufflehog
mv trufflehog "$HOME/.local/bin/trufflehog"
rm trufflehog.tar.gz
- name: Run TruffleHog Scanner
run: |
set +e
trufflehog git file://. --results=verified,unknown --fail --json > trufflehog-results.json
echo "TRUFFLEHOG_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
ISSUE_TITLE: "Security-Scan: TruffleHog Secrets"
ISSUE_LABEL: "security-scan-trufflehog"
run: |
python3 scripts/report-security-issue.py "" "" trufflehog-results.json
+38
View File
@@ -0,0 +1,38 @@
name: Unit-Tests
on:
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- testing
jobs:
test:
name: Unit-Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: cargo test
+1
View File
@@ -109,3 +109,4 @@ fabric.properties
# Built Visual Studio Code Extensions # Built Visual Studio Code Extensions
*.vsix *.vsix
.junie/plans
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="GitHub Workflow">
<value>
<SchemaInfo>
<option name="name" value="GitHub Workflow" />
<option name="relativePathToSchema" value="https://www.schemastore.org/github-workflow.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value=".gitea/workflows/testing.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/main.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/renovate.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/security-scan.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/trufflehog-scan.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/unit-tests.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/code-quality.yaml" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GHAISettings">{}</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/DockerUpdater.iml" filepath="$PROJECT_DIR$/.idea/DockerUpdater.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RsVcsConfiguration">
<option name="rustFmt" value="true" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+126
View File
@@ -0,0 +1,126 @@
# AGENTS.md — Entwickler- und Agenten-Dokumentation für DockerUpdate
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis von
`docker-update` arbeiten.
---
## 1. Projektübersicht
`docker-update` ist ein schlankes CLI-Werkzeug für **Linux-Server** (Rust 2024 Edition). Es durchsucht Verzeichnisse mit
Docker-Compose-Dateien (standardmäßig `/var/apps`), prüft definierte Services mit `:latest`-Images auf neuere Versionen,
lädt diese herunter, startet betroffene Services neu und bereinigt ungenutzte Zwischen-Images.
### Wichtige Rahmenbedingungen & Prinzipien
- **Reines Linux-Projekt**: Die Anwendung ist ausschließlich für Linux vorgesehen. Es dürfen keine Windows-spezifischen
Konstrukte oder bedingten Nicht-Linux-Kompilierungen (`#[cfg(target_os = ...)]`) hinzugefügt werden.
- **Root-Rechte**: Die Anwendung erfordert Root-Rechte zur Verwaltung von Docker und Compose-Services. Fehlen diese,
eskaliert sie via `sudo`.
- **Keine Unicode-Emojis im Logging**: Das Logging-Modul (`logger-ctdra`) verwendet einheitliche ASCII-Präfixe (`[!]`
Error, `[?]` Warn, `[i]` Info, `[d]` Debug).
- **Lizenz**: GNU General Public License v3.0 or later (`GPL-3.0-or-later`).
---
## 2. Modul- und Codestruktur
```text
DockerUpdate/
├── .gitea/
│ └── workflows/
│ ├── testing.yaml # CI/CD für testing-Branch (Tests, Testing-Packages & Preview-Release)
│ └── main.yaml # CI/CD für main-Branch (Tests, Stable-Packages & Release)
├── scripts/
│ ├── get-build-number.py # Ermittelt dynamisch die nächste Build-Nummer für Pakete
│ └── package-arch.py # Hilfsskript zur Arch-Linux-Paketerstellung (.pkg.tar.zst)
├── Cargo.toml # Rust-Manifest, Abhängigkeiten und Metadaten für deb / generate-rpm / arch
├── Cargo.lock
├── LICENSE # GPL-3.0 Lizenztext
├── README.md # Projektdokumentation für Endanwender und Administratoren
├── AGENTS.md # Richtlinien und Dokumentation für Agenten
├── tests/
│ ├── config.rs # Tests für Konfigurationsdatenstrukturen
│ └── updater.rs # Tests für Updater-Hilfsfunktionen und Compose-Parsing
└── src/
├── main.rs # Einstiegspunkt, Root-Prüfung, Ausführung
├── lib.rs # Bibliotheksexporte für Module und Tests
├── config.rs # Konfigurationsdatenstrukturen (config-ctdra / TOML)
└── updater.rs # Scan-, Pull-, Restart- und Bereinigungslogik für Compose-Apps
```
### Modulverantwortlichkeiten
- **`src/main.rs`**:
Initialer Startpunkt. Prüft mit `sudo_ctdra::is_run_as_root()`, ob Root-Rechte vorliegen. Wenn nicht, wird mit
`sudo_ctdra::run_as_root()` neu gestartet. Initialisiert das Logging über `logger-ctdra` anhand der Konfiguration und
ruft anschließend `updater::run_updates()` auf.
- **`src/updater.rs`**:
- Sucht in Unterverzeichnissen von `apps_dir` nach Compose-Dateien (`docker-compose.yaml`, `docker-compose.yml`,
`compose.yaml`, `compose.yml`).
- Parst Services via `serde-saphyr` und filtert mit `is_latest_image()` nach Services mit `:latest`-Images oder ohne
expliziten Tag.
- Führt `docker pull` für jedes gefundene Image aus und vergleicht Vorher/Nachher-Image-IDs via
`docker image inspect`.
- Startet bei Änderungen Services neu (`docker compose up -d` mit Fallback auf `docker-compose up -d`).
- Führt nach Updates `docker image prune -f` aus.
- **`src/config.rs`**:
- Definiert die Konfigurationsdatenstrukturen (`AppConfig`, `General` mit `apps_dir` und `log_level`).
- Lädt und verwaltet die Konfiguration über `config-ctdra` (Standardpfade: `/etc/docker-update/config.toml` bzw.
`~/.config/docker-update/config.toml`).
- **`.gitea/workflows/`**:
- `testing.yaml`: Führt bei Push auf `testing` Tests und Builds durch, veröffentlicht Pakete in der Gitea Package
Registry
(`distribution: testing`, `component: main` bzw. Gruppe/Repo `testing`) und erstellt ein Gitea Preview-Release mit
den Paketen als Assets.
- `main.yaml`: Führt bei Push auf `main` Tests und Builds durch, veröffentlicht Pakete in der Gitea Package Registry
(`distribution: stable`, `component: main` bzw. Gruppe/Repo `stable`) und erstellt ein Gitea Release mit den
Paketen als Assets.
---
## 3. Build-, Test- und Prüfbefehle
Vor jedem Commit oder Abschluss einer Aufgabe müssen folgende Befehle erfolgreich durchlaufen:
```bash
# 1. Compiler- und Typ-Prüfung für alle Targets
cargo check --all-targets
# 2. Ausführen aller Unit-Tests
cargo test
# 3. Release-Build kompilieren
cargo build --release
```
### Optionale Paketierungsbefehle (lokale Validierung)
```bash
# Debian-Paket (.deb) erstellen (benötigt cargo-deb)
cargo deb
# Fedora / RPM-Paket (.rpm) erstellen (benötigt cargo-generate-rpm)
cargo generate-rpm
# Arch Linux-Paket (.pkg.tar.zst) erstellen
python3 scripts/package-arch.py
```
---
## 4. Richtlinien für Änderungen
1. **Abwärtskompatibilität & Pfade**:
Der Standard-Apps-Pfad `/var/apps` und die Konfigurationspfade dürfen nicht ohne triftigen Grund geändert werden.
2. **Paketierungsdefinitionen in Cargo.toml pflegen**:
Alle Paketierungsdefinitionen für Debian, RPM und Arch Linux werden zentral in `Cargo.toml` verwaltet
(`[package.metadata.deb]`, `[package.metadata.generate-rpm]`, `[package.metadata.arch]`). Es wird kein separater
`packaging/`-Ordner benötigt.
3. **Automatisierung**:
Die empfohlene Automatisierungsmethode ist **Cron** (`/etc/cron.d/docker-update` oder `crontab -e`).
4. **Keine Mocking-Bypässe**:
Tests dürfen nicht gelöscht, ignoriert oder durch leere Dummy-Assertions ersetzt werden.
5. **CI/CD & Release-Konsistenz**:
Workflows in `.gitea/workflows/` müssen bei strukturellen Paket- oder Release-Anpassungen konsistent zu `Cargo.toml`
gehalten werden.
Generated
+541
View File
@@ -0,0 +1,541 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "annotate-snippets"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1"
dependencies = [
"anstyle",
"memchr",
"unicode-width",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "config-ctdra"
version = "1.0.4"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "f7b3e25b95d4cdb6fc856f2af0106d4558b882c2f10798db46181e51827749a4"
dependencies = [
"confy",
"program-ctdra",
"serde",
"sudo-ctdra",
]
[[package]]
name = "confy"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8807c397789cbe02bbdb1a27ea5f345584132808697b2a3f957c829829ee4814"
dependencies = [
"etcetera",
"lazy_static",
"serde",
"thiserror",
"toml",
]
[[package]]
name = "deranged"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
[[package]]
name = "docker-update"
version = "1.2.2"
dependencies = [
"config-ctdra",
"logger-ctdra",
"serde",
"serde-saphyr",
"sudo-ctdra",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "encoding_rs_io"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22"
dependencies = [
"encoding_rs",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "etcetera"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c7b13d0780cb82722fd59f6f57f925e143427e4a75313a6c77243bf5326ae6"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.59.0",
]
[[package]]
name = "granit-parser"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aa83cc6ac4dc610adf5501a5392b4bcc543b2c1f24eaf77469a96e31ec9abe"
dependencies = [
"arraydeque",
"smallvec",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libc"
version = "1.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0f24f33af482526a4e3f9b47f0abb2c6377a1713c8aa4a8106994689a4cfa5"
[[package]]
name = "logger-ctdra"
version = "1.0.3"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "af9ea239d80163c80b12cae5f74c0c2824199921dedafba74678e96a11a8b15e"
dependencies = [
"program-ctdra",
"time",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "num-conv"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc 0.2.189",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "program-ctdra"
version = "1.0.0"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "54f20af67f90bf6d10697dd77bb4beb41dafad7fe0fb46aacfc81ad45a8e48bd"
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde-saphyr"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3afb591f9cdb6223c88ba39269aff895620c7f0716dc42b705b5733d5c7c0823"
dependencies = [
"annotate-snippets",
"base64",
"encoding_rs_io",
"granit-parser",
"nohash-hasher",
"num-traits",
"serde_core",
"smallvec",
"zmij",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "sudo-ctdra"
version = "1.0.0"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "2c2746122bbe36f821de70db3ca4ad24edfb77118fc6a6bc7693082b50d23e5f"
dependencies = [
"libc 1.0.0-alpha.4",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [
"deranged",
"libc 0.2.189",
"num-conv",
"num_threads",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]]
name = "time-macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "toml"
version = "0.9.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow 0.7.15",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow 1.0.4",
]
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
[[package]]
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "docker-update"
version = "1.2.2"
edition = "2024"
authors = ['DragonSlayer_14']
readme = "README.md"
license = "GPL-3.0-or-later"
repository = "https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate"
description = "Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images"
[dependencies]
serde = { version = "1.0.229", features = ["derive"] }
serde-saphyr = "1.1.0"
logger-ctdra = { version = "1.0.3", registry = "gitea" }
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
config-ctdra = { version = "1.0.4", registry = "gitea" }
[profile.release]
debug = "none"
[package.metadata.deb]
name = "docker-update"
maintainer = "DragonSlayer_14"
copyright = "2026 Linuxapps"
section = "utils"
priority = "optional"
depends = "$auto, sudo, docker-ce-cli, docker-compose-plugin"
extended-description = """\
DockerUpdate durchsucht Verzeichnisse (standardmäßig in /var/apps) nach
Docker-Compose-Dateien mit ':latest'-Images, prüft auf neuere Images,
lädt diese herunter und startet die betroffenen Container automatisch neu.\
"""
assets = [
["target/release/docker-update", "usr/bin/docker-update", "755"],
["README.md", "usr/share/doc/docker-update/README.md", "644"],
["LICENSE", "usr/share/doc/docker-update/copyright", "644"],
]
[package.metadata.generate-rpm]
assets = [
{ source = "target/release/docker-update", dest = "/usr/bin/docker-update", mode = "755" },
{ source = "README.md", dest = "/usr/share/doc/docker-update/README.md", mode = "644", doc = true },
{ source = "LICENSE", dest = "/usr/share/licenses/docker-update/LICENSE", mode = "644", license = true },
]
requires = { "sudo" = "*", "docker" = "*" }
[package.metadata.arch]
pkgrel = "1"
arch = "x86_64"
depends = ["gcc-libs", "glibc", "sudo", "docker-ce-cli", "docker-compose-plugin"]
optdepends = ["docker-compose: Unterstützung für Docker Compose v1"]
+353 -2
View File
@@ -1,3 +1,354 @@
# DockerUpdater # DockerUpdate (`docker-update`)
Hilft dabei installierte Anwendungen über docker-compose.yaml in /var/apps aktuell zu halten. [![Main Release & Publish](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/actions/workflows/main.yaml/badge.svg?branch=main)](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/actions?workflow=main.yaml)
[![Testing Build, Publish & Preview Release](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/actions/workflows/testing.yaml/badge.svg?branch=testing)](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/actions?workflow=testing.yaml)
`docker-update` ist ein schlankes CLI-Werkzeug für Linux-Server, das Docker-Compose-basierte Applikationen automatisch
auf neuere Versionen prüft, aktualisiert und neu startet.
## Funktionsweise
1. **Durchsuchen von Applikationsverzeichnissen**:
Standardmäßig scannt `docker-update` alle Unterverzeichnisse in `/var/apps/` (konfigurierbar) nach gültigen
Compose-Dateien (`docker-compose.yaml`, `docker-compose.yml`, `compose.yaml`, `compose.yml`).
2. **Erkennung von `:latest`-Images**:
Services in den Compose-Dateien werden analysiert. Es werden gezielt Services berücksichtigt, deren Image das
`:latest`-Tag nutzt oder implizit auf `latest` verweist (kein Tag angegeben).
3. **Prüfung auf neuere Images & Download**:
Das Programm führt für jedes betroffene Image einen `docker pull` durch und vergleicht die Image-IDs vor und nach dem
Pull.
4. **Automatischer Neustart**:
Wurde ein neues Image heruntergeladen, wird die Applikation im jeweiligen Verzeichnis über `docker compose up -d`
(mit Fallback auf `docker-compose up -d`) aktualisiert und neu gestartet.
5. **Bereinigung**:
Nach erfolgreichen Aktualisierungen werden ungenutzte Zwischen-Images automatisch mittels `docker image prune -f`
bereinigt.
6. **Berechtigungen & Logging**:
Wird das Programm ohne Root-Rechte gestartet, startet es sich automatisch über `sudo` mit erhöhten Rechten neu.
Ausgaben werden auf der Konsole formatiert und zusätzlich tagesbasiert im System-Temp-Verzeichnis protokolliert (z.
B. `/tmp/docker-update-.../log-YYYY-MM-DD.log`).
---
## Voraussetzungen
- **Betriebssystem**: Linux (amd64 / x86_64, arm64 / aarch64, i386 / i686)
- **Laufzeit-Abhängigkeiten**:
- `docker` (inkl. Docker Compose Plugin `docker compose` oder Standalone `docker-compose`)
- `sudo`
- **Build-Abhängigkeiten** (nur zum Kompilieren/Paketieren):
- Rust & Cargo (Rust 2024 Edition / 1.85+)
---
## Installation aus der Gitea-Paket-Registry
Über die CI/CD-Pipelines werden bei jedem Push auf den `main`-Branch (Release / Stable) und `testing`-Branch (Preview /
Testing) automatisch Pakete für **Debian/Ubuntu**, **Fedora/RHEL** und **Arch Linux** in der Gitea-Paket-Registry
bereitgestellt.
### 1. Debian / Ubuntu (`apt`)
#### Stable (Produktiv)
Füge das Repository zu deinen APT-Quellen hinzu:
```bash
# Keyring installieren
sudo curl https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/debian/repository.key -o /etc/apt/keyrings/gitea-Linuxapps.asc
# Repository-Eintrag anlegen
echo "deb [signed-by=/etc/apt/keyrings/gitea-Linuxapps.asc] https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/debian stable main" | sudo tee -a /etc/apt/sources.list.d/gitea.list
# Paketliste aktualisieren und installieren
sudo apt update
sudo apt install docker-update
```
#### Testing (Vorschau / Testing)
```bash
# Keyring installieren
sudo curl https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/debian/repository.key -o /etc/apt/keyrings/gitea-Linuxapps.asc
# Testing-Repository-Eintrag anlegen
echo "deb [signed-by=/etc/apt/keyrings/gitea-Linuxapps.asc] https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/debian testing main" | sudo tee -a /etc/apt/sources.list.d/gitea.list
# Paketliste aktualisieren und installieren
sudo apt update
sudo apt install docker-update
```
---
### 2. RedHat / Fedora / SUSE
#### Stable (Produktiv)
Repository einrichten:
```bash
# auf RedHat-basierten Distributionen
dnf config-manager --add-repo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
# Fedora 41+ (DNF5)
dnf config-manager addrepo --from-repofile=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
# auf SUSE-basierten Distributionen
zypper addrepo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
```
Paket installieren:
```bash
# auf RedHat-basierten Distributionen
dnf install docker-update
# auf SUSE-basierten Distributionen
zypper install docker-update
```
#### Testing (Vorschau / Testing)
Repository einrichten:
```bash
# auf RedHat-basierten Distributionen
dnf config-manager --add-repo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
# Fedora 41+ (DNF5)
dnf config-manager addrepo --from-repofile=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
# auf SUSE-basierten Distributionen
zypper addrepo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
```
Paket installieren:
```bash
# auf RedHat-basierten Distributionen
dnf install docker-update
# auf SUSE-basierten Distributionen
zypper install docker-update
```
---
### 3. Arch Linux (`pacman`)
### Installiere den GPG-Key für das Repository
Key herunterladen:
```bash
wget https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/repository.key
```
ID des Keys anzeigen lassen:
```bash
gpg --show-keys repository.key
```
Key zu pacman hinzufügen und signieren:
```
pacman-key --add repository.key
pacman-key --lsign-key {key id}
```
{key id} = ID aus dem vorherigen Schritt
#### Stable (Produktiv)
Füge das Repository in `/etc/pacman.conf` ein:
```ini
[stable]
SigLevel = Optional TrustAll
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/stable/$arch
```
Installiere das Paket anschließend:
```bash
sudo pacman -Sy docker-update
```
#### Testing (Vorschau / Testing)
Füge das Testing-Repository in `/etc/pacman.conf` ein:
```ini
[testing]
SigLevel = Optional TrustAll
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/testing/$arch
```
Installiere das Paket:
```bash
sudo pacman -Sy docker-update
```
---
### Download vorgefertigter Pakete (Releases)
Alternativ können vorkompilierte Pakete (`.deb`, `.rpm`, `.pkg.tar.zst`) auch direkt aus den Releases heruntergeladen
und manuell installiert werden:
- **Stable-Releases**: [Gitea Releases](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/releases)
- **Preview-Releases (Testing)**: Markiert als Vorabveröffentlichung (*Pre-release*) mit dem Tag `v<VERSION>-preview`.
---
## Manuelle Installation (Build from Source)
```bash
# Repository klonen
git clone https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate.git
cd DockerUpdate
# Release-Binary kompilieren
cargo build --release
# Binary ins Systemverzeichnis installieren
sudo install -m 755 target/release/docker-update /usr/bin/docker-update
```
Anschließend kann das Programm direkt über `docker-update` ausgeführt werden:
```bash
docker-update
```
---
## Konfiguration
Die Konfigurationsdatei wird standardmäßig unter folgendem Pfad abgelegt:
- **Root-Ausführung**: `/etc/docker-update/config.toml`
- **Benutzer-Ausführung**: `~/.config/docker-update/config.toml`
Wird keine Datei gefunden, verwendet `docker-update` die Standardwerte.
### Beispiel-Konfiguration (`config.toml`)
```toml
[general]
# Verzeichnis, in dem sich die Applikationsordner mit Compose-Dateien befinden
apps_dir = "/var/apps"
# Log-Level: "error", "warn", "info", "debug"
log_level = "info"
```
---
## Paketerstellung
Für gängige Linux-Distributionen stehen vorbereitete Paketierungsdefinitionen zur Verfügung.
### 1. Debian / Ubuntu (`.deb`)
Das Debian-Paket kann mithilfe von [`cargo-deb`](https://github.com/kornelski/cargo-deb) erstellt werden:
```bash
# cargo-deb installieren (falls noch nicht vorhanden)
cargo install cargo-deb
# Release-Build und Debian-Paket erstellen
cargo deb
# Installation des erstellten Pakets
sudo dpkg -i target/debian/docker-update_*.deb
```
Die Paketkonfiguration und Abhängigkeiten (`sudo`, `docker.io | docker-ce`) sind in `Cargo.toml` unter
`[package.metadata.deb]` definiert.
---
### 2. Arch Linux (`.pkg.tar.zst`)
Die Paketmetadaten und Abhängigkeiten für Arch Linux sind direkt in `Cargo.toml` unter `[package.metadata.arch]`
hinterlegt. Das Paket kann nach dem Kompilieren des Release-Builds über das Skript `scripts/package-arch.py` (analog zu
CI/CD) erzeugt werden (optional mit `--target <TRIPLE>` für Cross-Builds):
```bash
# Release kompilieren
cargo build --release
# Arch Linux-Paket (.pkg.tar.zst) erstellen (Standard: Host/x86_64)
python3 scripts/package-arch.py
# Optional für spezifische Zielarchitektur:
# python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu
# python3 scripts/package-arch.py --target i686-unknown-linux-gnu
# Installation des erstellten Pakets
sudo pacman -U target/arch/docker-update-*.pkg.tar.zst
```
---
### 3. Fedora / RHEL / CentOS (`.rpm`)
Das RPM-Paket wird mithilfe von [`cargo-generate-rpm`](https://github.com/cat-in-136/cargo-generate-rpm) direkt aus den
Metadaten in `Cargo.toml` (`[package.metadata.generate-rpm]`) erstellt:
```bash
# cargo-generate-rpm installieren (falls noch nicht vorhanden)
cargo install cargo-generate-rpm
# Release bauen und RPM erzeugen
cargo build --release
cargo generate-rpm
# Optional für spezifische Zielarchitektur (z.B. Cross-Builds):
# mkdir -p target/generate-rpm
# cargo generate-rpm --target aarch64-unknown-linux-gnu -o target/generate-rpm
# cargo generate-rpm --target i686-unknown-linux-gnu -o target/generate-rpm
# Installation des erstellten RPM-Pakets
sudo dnf install target/generate-rpm/docker-update-*.rpm
```
---
## Automatisierung (Cron)
Um `docker-update` regelmäßig (z. B. täglich nachts um 03:30 Uhr) automatisch auszuführen, kann ein Cronjob eingerichtet
werden. Da `docker-update` Root-Rechte benötigt, sollte der Cronjob mit Root-Rechten ausgeführt werden.
### Option A: Über `/etc/cron.d/docker-update` (Empfohlen für Server)
Erstelle eine Datei `/etc/cron.d/docker-update`:
```bash
sudo tee /etc/cron.d/docker-update << 'EOF'
# Täglich um 03:30 Uhr docker-update als root ausführen
30 3 * * * root /usr/bin/docker-update >/dev/null 2>&1
EOF
sudo chmod 644 /etc/cron.d/docker-update
```
### Option B: Über die Root-Crontab (`crontab -e`)
Öffne die Crontab des Root-Benutzers:
```bash
sudo crontab -e
```
Füge folgende Zeile ein:
```cron
30 3 * * * /usr/bin/docker-update >/dev/null 2>&1
```
---
## Lizenz
Dieses Projekt ist unter der **GNU General Public License v3.0 or later (GPL-3.0-or-later)** lizenziert. Weitere
Informationen finden sich in der Datei [LICENSE](LICENSE).
+50
View File
@@ -0,0 +1,50 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
#################################################################################
# WARNING: Do not store sensitive information in this file, #
# as its contents will be included in the Qodana report. #
#################################################################################
version: "1.0"
#Specify inspection profile for code analysis
profile:
name: qodana.starter
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>
#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
# severityThresholds - configures maximum thresholds for different problem severities
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
# dependencyLicenses - fails the run on prohibited or unknown dependency licenses
# Code Coverage is available in Ultimate and Ultimate Plus plans
#failureConditions:
# severityThresholds:
# any: 15
# critical: 5
# testCoverageThresholds:
# fresh: 70
# total: 50
# dependencyLicenses:
# failOnProhibited: true
# failOnUnknown: false
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-<linter>:2026.2
+112
View File
@@ -0,0 +1,112 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"timezone": "Europe/Berlin",
"schedule": [
"before 6am on monday"
],
"baseBranchPatterns": [
"dev"
],
"packageRules": [
{
"matchFileNames": [
".gitea/workflows/**"
],
"groupName": "Gitea Actions",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": [
"cargo"
],
"groupName": "Cargo Dependencies",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": [
"dockerfile",
"docker-compose"
],
"groupName": "Docker-Images",
"separateMajorMinor": false,
"separateMinorPatch": false
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "aquasecurity/trivy",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "google/osv-scanner",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "trufflesecurity/trufflehog",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_BINSTALL_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-bins/cargo-binstall",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_DEB_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-deb",
"datasourceTemplate": "crate"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_GENERATE_RPM_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-generate-rpm",
"datasourceTemplate": "crate"
}
]
}
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
Ermittelt automatisch die nächste Build-Nummer (Revision / Release / pkgrel)
für eine gegebene Paket-Version (z. B. 1.0.2 -> wenn 1.0.2-1 existiert, wird 2 zurückgegeben).
"""
import argparse
import json
import os
import re
import subprocess
import sys
import urllib.error
import urllib.parse
import urllib.request
# Paket-Typen, unter denen dasselbe Release veroeffentlicht wird und die sich
# daher eine gemeinsame Build-Nummer teilen muessen.
PACKAGE_TYPES = ("debian", "rpm", "arch")
def get_cargo_pkg_info():
try:
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"]))
pkg = metadata["packages"][0]
name = pkg.get("name", "")
version = pkg.get("version", "0.0.0")
repository = pkg.get("repository", "")
return name, version, repository
except Exception:
# Fallback auf einfaches Parsen der Cargo.toml
name = ""
version = "0.0.0"
repository = ""
if os.path.exists("Cargo.toml"):
with open("Cargo.toml", "r") as f:
for line in f:
line = line.strip()
if line.startswith("name ="):
name = line.split("=", 1)[1].strip().strip('"\'')
elif line.startswith("version ="):
version = line.split("=", 1)[1].strip().strip('"\'')
elif line.startswith("repository ="):
repository = line.split("=", 1)[1].strip().strip('"\'')
return name, version, repository
def parse_repo_info(repo_url):
"""Extrahiert Server-URL, Owner und Repo-Name aus einer Repository-URL."""
if not repo_url:
return None, None, None
parsed = urllib.parse.urlparse(repo_url)
server_url = f"{parsed.scheme}://{parsed.netloc}"
path_parts = [p for p in parsed.path.strip("/").split("/") if p]
if len(path_parts) >= 2:
owner = path_parts[0]
repo_name = path_parts[1]
if repo_name.endswith(".git"):
repo_name = repo_name[:-4]
return server_url, f"{owner}/{repo_name}", owner
return server_url, None, None
def query_existing_build_numbers(name, version, gitea_url, repo, owner, token=None):
build_nums = set()
pattern = re.compile(rf'(?:{re.escape(name)}[_-]|^v?){re.escape(version)}-(\d+)')
headers = {"User-Agent": f"{name}-build-resolver" if name else "rust-build-resolver"}
if token:
headers["Authorization"] = f"token {token}"
# 1. Gitea Releases API
if gitea_url and repo:
try:
url = f"{gitea_url.rstrip('/')}/api/v1/repos/{repo}/releases?limit=50"
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as resp:
releases = json.loads(resp.read().decode())
for rel in releases:
tag = rel.get("tag_name", "")
m = pattern.search(tag)
if m:
build_nums.add(int(m.group(1)))
for asset in rel.get("assets", []):
asset_name = asset.get("name", "")
m = pattern.search(asset_name)
if m:
build_nums.add(int(m.group(1)))
except Exception as e:
sys.stderr.write(f"[Hinweis] Konnte Gitea Releases nicht abfragen: {e}\n")
# 2. Gitea Packages API (falls Token vorhanden): pro Paket-Typ gezielt nur die
# neueste Version abfragen (ein Request, keine Pagination noetig). Das haelt
# die Abfrage schnell unabhaengig von der Historie und ist unbeeinflusst von
# Docker-Tags/anderen Paket-Typen, die unter demselben Owner haengen.
if gitea_url and owner and token and name:
for pkg_type in PACKAGE_TYPES:
try:
url = (
f"{gitea_url.rstrip('/')}/api/v1/packages/{owner}/"
f"{pkg_type}/{urllib.parse.quote(name, safe='')}/-/latest"
)
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req, timeout=10) as resp:
pkg = json.loads(resp.read().decode())
pkg_ver = pkg.get("version", "")
m = pattern.search(pkg_ver)
if m:
build_nums.add(int(m.group(1)))
except urllib.error.HTTPError as e:
if e.code != 404:
sys.stderr.write(f"[Hinweis] Konnte neueste {pkg_type}-Paketversion nicht abfragen: {e}\n")
except Exception as e:
sys.stderr.write(f"[Hinweis] Konnte neueste {pkg_type}-Paketversion nicht abfragen: {e}\n")
# 3. Lokales Target-Verzeichnis prüfen
target_dirs = ["target/debian", "target/generate-rpm", "target/arch"]
for t_dir in target_dirs:
if os.path.isdir(t_dir):
for file in os.listdir(t_dir):
m = pattern.search(file)
if m:
build_nums.add(int(m.group(1)))
return build_nums
def get_next_build_number(version=None, gitea_url=None, repo=None, owner=None, token=None, explicit_build_num=None):
if explicit_build_num is not None:
return int(explicit_build_num)
env_build_num = os.environ.get("BUILD_NUMBER") or os.environ.get("BUILD_NUM") or os.environ.get("PKGREL")
if env_build_num:
try:
return int(env_build_num)
except ValueError:
pass
pkg_name, pkg_version, pkg_repo = get_cargo_pkg_info()
if not version:
version = pkg_version
default_server, default_repo, default_owner = parse_repo_info(pkg_repo)
gitea_url = gitea_url or os.environ.get("GITEA_URL") or os.environ.get("GITHUB_SERVER_URL") or default_server
repo = repo or os.environ.get("REPO") or os.environ.get("GITHUB_REPOSITORY") or default_repo
owner = owner or os.environ.get("REPO_OWNER") or os.environ.get("GITHUB_REPOSITORY_OWNER") or default_owner
token = token or os.environ.get("TOKEN") or os.environ.get("GITEA_TOKEN") or os.environ.get("PACKAGE_TOKEN") or os.environ.get("RELEASE_TOKEN")
existing_nums = query_existing_build_numbers(pkg_name, version, gitea_url, repo, owner, token)
if existing_nums:
return max(existing_nums) + 1
return 1
def main():
parser = argparse.ArgumentParser(description="Ermittelt die nächste Build-Nummer für die Paketierung.")
parser.add_argument("--version", help="Paket-Version (Standard: aus Cargo.toml)")
parser.add_argument("--gitea-url", help="Gitea Basis-URL")
parser.add_argument("--repo", help="Repository (z. B. Owner/Repo)")
parser.add_argument("--owner", help="Repository Owner / Organisation")
parser.add_argument("--token", help="API-Token")
parser.add_argument("--build-number", type=int, help="Explizite Build-Nummer erzwingen")
args = parser.parse_args()
build_num = get_next_build_number(
version=args.version,
gitea_url=args.gitea_url,
repo=args.repo,
owner=args.owner,
token=args.token,
explicit_build_num=args.build_number,
)
print(build_num)
if __name__ == "__main__":
main()
+146
View File
@@ -0,0 +1,146 @@
#!/usr/bin/env python3
import argparse
import importlib.util
import json
import os
import subprocess
import sys
import tempfile
import time
TARGET_ARCH_MAP = {
"x86_64-unknown-linux-gnu": "x86_64",
"x86_64-unknown-linux-musl": "x86_64",
"x86_64": "x86_64",
"amd64": "x86_64",
"aarch64-unknown-linux-gnu": "aarch64",
"aarch64-unknown-linux-musl": "aarch64",
"aarch64": "aarch64",
"arm64": "aarch64",
"i686-unknown-linux-gnu": "i686",
"i686-unknown-linux-musl": "i686",
"i686": "i686",
"i386": "i686",
}
def resolve_pkgrel(version=None, default="1"):
# 1. Environment Variable
env_pkgrel = os.environ.get("BUILD_NUMBER") or os.environ.get("BUILD_NUM") or os.environ.get("PKGREL")
if env_pkgrel:
return str(env_pkgrel)
# 2. get-build-number.py falls vorhanden
script_dir = os.path.dirname(os.path.abspath(__file__))
getter_path = os.path.join(script_dir, "get-build-number.py")
if os.path.exists(getter_path):
try:
spec = importlib.util.spec_from_file_location("get_build_number", getter_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return str(module.get_next_build_number(version=version))
except Exception as e:
sys.stderr.write(f"[Hinweis] Konnte get-build-number nicht ausführen: {e}\n")
return str(default)
def build_package(target_triple=None, target_arch=None, pkgrel=None):
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"]))
pkg = metadata["packages"][0]
name = pkg["name"]
version = pkg["version"]
description = pkg.get("description", "")
license_name = pkg.get("license", "")
repository = pkg.get("repository", "")
authors = pkg.get("authors", [])
author = authors[0] if authors else "Unknown"
arch_meta = pkg.get("metadata", {}).get("arch", {})
default_pkgrel = arch_meta.get("pkgrel", "1")
if not pkgrel:
pkgrel = resolve_pkgrel(version=version, default=default_pkgrel)
default_arch = arch_meta.get("arch", "x86_64")
if target_arch:
arch = TARGET_ARCH_MAP.get(target_arch, target_arch)
elif target_triple:
arch = TARGET_ARCH_MAP.get(target_triple, default_arch)
else:
arch = default_arch
candidate_paths = []
if target_triple:
candidate_paths.append(f"target/{target_triple}/release/{name}")
candidate_paths.append(f"target/release/{name}")
bin_path = None
for p in candidate_paths:
if os.path.exists(p):
bin_path = p
break
if not bin_path:
raise FileNotFoundError(
f"Keine kompilierte Binary für {name} gefunden. Gesuchte Pfade: {candidate_paths}"
)
depends = arch_meta.get("depends", ["gcc-libs", "glibc"])
optdepends = arch_meta.get("optdepends", [])
with tempfile.TemporaryDirectory() as build_dir:
bin_dir = os.path.join(build_dir, "usr/bin")
doc_dir = os.path.join(build_dir, f"usr/share/doc/{name}")
lic_dir = os.path.join(build_dir, f"usr/share/licenses/{name}")
os.makedirs(bin_dir, exist_ok=True)
os.makedirs(doc_dir, exist_ok=True)
os.makedirs(lic_dir, exist_ok=True)
subprocess.run(["install", "-m", "755", bin_path, f"{bin_dir}/{name}"], check=True)
if os.path.exists("LICENSE"):
subprocess.run(["install", "-m", "644", "LICENSE", f"{lic_dir}/LICENSE"], check=True)
if os.path.exists("README.md"):
subprocess.run(["install", "-m", "644", "README.md", f"{doc_dir}/README.md"], check=True)
installed_size = subprocess.check_output(["du", "-sb", build_dir]).decode().split()[0]
builddate = str(int(time.time()))
pkginfo_lines = [
f"pkgname = {name}",
f"pkgbase = {name}",
f"pkgver = {version}-{pkgrel}",
f"pkgdesc = {description}",
f"url = {repository}",
f"builddate = {builddate}",
f"packager = {author}",
f"size = {installed_size}",
f"arch = {arch}",
f"license = {license_name}",
]
for dep in depends:
pkginfo_lines.append(f"depend = {dep}")
for optdep in optdepends:
pkginfo_lines.append(f"optdepend = {optdep}")
pkginfo_lines.append("makepkgopt = strip\n")
with open(os.path.join(build_dir, ".PKGINFO"), "w") as f:
f.write("\n".join(pkginfo_lines))
os.makedirs("target/arch", exist_ok=True)
output_file = os.path.abspath(f"target/arch/{name}-{version}-{pkgrel}-{arch}.pkg.tar.zst")
subprocess.run(["tar", "--zstd", "-cf", output_file, ".PKGINFO", "usr"], cwd=build_dir, check=True)
print(f"Arch-Paket erfolgreich erstellt: {output_file}")
def main():
parser = argparse.ArgumentParser(description="Erstellt Arch Linux-Pakete (.pkg.tar.zst)")
parser.add_argument("--target", help="Rust Target-Triple (z.B. x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, i686-unknown-linux-gnu)")
parser.add_argument("--arch", help="Architektur (z.B. x86_64, aarch64, i686)")
parser.add_argument("--pkgrel", help="Release-/Build-Nummer (z.B. 1, 2, ...)")
args = parser.parse_args()
build_package(target_triple=args.target, target_arch=args.arch, pkgrel=args.pkgrel)
if __name__ == "__main__":
main()
+315
View File
@@ -0,0 +1,315 @@
#!/usr/bin/env python3
"""Erstellt oder kommentiert ein Gitea-Issue mit den Ergebnissen der Security-Scans.
Sucht ein offenes Issue mit dem Label ISSUE_LABEL (Standard: "security-scan").
Existiert eines, wird der aktuelle Scan-Stand als neuer Kommentar angehängt
(die Historie bleibt erhalten). Existiert keines (z.B. weil das letzte
geschlossen wurde), wird ein neues Issue erstellt. Gibt es keine Funde mehr,
wird ein offenes Issue nur kommentiert, nicht geschlossen.
Titel und Label lassen sich per Umgebungsvariable ISSUE_TITLE / ISSUE_LABEL
überschreiben, damit z.B. TruffleHog-Funde in ein eigenes Issue laufen statt
in das gemeinsame Trivy/OSV-Issue.
"""
import json
import os
import sys
import urllib.error
import urllib.request
LABEL_NAME = os.environ.get("ISSUE_LABEL", "security-scan")
LABEL_COLOR = "#b60205"
ISSUE_TITLE = os.environ.get("ISSUE_TITLE", "Security-Scan: Offene Schwachstellen")
SEVERITY_ORDER = {
"VERIFIED": -1,
"CRITICAL": 0,
"HIGH": 1,
"MEDIUM": 2,
"LOW": 3,
"UNKNOWN": 4,
"UNVERIFIED": 6,
}
def api(method, path, token, gitea_url, data=None):
url = f"{gitea_url}/api/v1{path}"
body = json.dumps(data).encode() if data is not None else None
req = urllib.request.Request(url, data=body, method=method)
req.add_header("Authorization", f"token {token}")
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
raw = resp.read()
return json.loads(raw) if raw else None
except urllib.error.HTTPError as e:
print(f"Gitea API Fehler ({method} {path}): {e.code} {e.read().decode()}", file=sys.stderr)
raise
def make_finding(source, id, severity, package, installed="-", fixed="-", target="-"):
return {
"source": source,
"id": id,
"severity": severity,
"package": package,
"installed": installed,
"fixed": fixed,
"target": target,
}
def cvss_score_to_severity(score):
try:
score = float(score)
except (TypeError, ValueError):
return "UNKNOWN"
if score >= 9.0:
return "CRITICAL"
if score >= 7.0:
return "HIGH"
if score >= 4.0:
return "MEDIUM"
if score > 0.0:
return "LOW"
return "UNKNOWN"
def load_trivy(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("Results", []) or []:
target = result.get("Target", "?")
for vuln in result.get("Vulnerabilities", []) or []:
findings.append(make_finding(
"Trivy",
vuln.get("VulnerabilityID", "?"),
vuln.get("Severity", "UNKNOWN"),
vuln.get("PkgName", "?"),
installed=vuln.get("InstalledVersion", "?"),
fixed=vuln.get("FixedVersion") or "-",
target=target,
))
for misc in result.get("Misconfigurations", []) or []:
findings.append(make_finding(
"Trivy (Misconfig)",
misc.get("ID", "?"),
misc.get("Severity", "UNKNOWN"),
misc.get("Title", "?"),
target=target,
))
for secret in result.get("Secrets", []) or []:
findings.append(make_finding(
"Trivy (Secret)",
secret.get("RuleID", "?"),
secret.get("Severity", "UNKNOWN"),
secret.get("Title", "?"),
target=target,
))
return findings
def load_osv(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
data = json.load(f)
for result in data.get("results", []) or []:
source = (result.get("source") or {}).get("path", "?")
for pkg in result.get("packages", []) or []:
info = pkg.get("package", {})
pkg_name = f"{info.get('name', '?')} ({info.get('ecosystem', '?')})"
severity_by_id = {}
for group in pkg.get("groups", []) or []:
label = cvss_score_to_severity(group.get("max_severity"))
for vuln_id in group.get("ids", []) or []:
severity_by_id[vuln_id] = label
for vuln in pkg.get("vulnerabilities", []) or []:
vuln_id = vuln.get("id", "?")
findings.append(make_finding(
"OSV-Scanner",
vuln_id,
severity_by_id.get(vuln_id, "UNKNOWN"),
pkg_name,
installed=info.get("version", "?"),
target=source,
))
return findings
def load_trufflehog(path):
findings = []
if not path or not os.path.isfile(path):
return findings
with open(path) as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
git_meta = ((entry.get("SourceMetadata") or {}).get("Data") or {}).get("Git") or {}
findings.append(make_finding(
"TruffleHog",
entry.get("DetectorName", "?"),
"VERIFIED" if entry.get("Verified") else "UNVERIFIED",
git_meta.get("file", "?"),
target=git_meta.get("commit", "-"),
))
return findings
def sort_findings(findings):
return sorted(findings, key=lambda f: (SEVERITY_ORDER.get(f["severity"], 9), f["id"]))
def print_summary(findings):
if not findings:
print("Keine Funde.")
return
widths = {
key: max(len(key), *(len(str(f[key])) for f in findings))
for key in ("source", "id", "severity", "package", "installed", "fixed", "target")
}
header = ("source", "id", "severity", "package", "installed", "fixed", "target")
row_fmt = " ".join(f"{{:{widths[k]}}}" for k in header)
print(row_fmt.format(*header))
print(row_fmt.format(*("-" * widths[k] for k in header)))
for f in findings:
print(row_fmt.format(*(str(f[k]) for k in header)))
TRUFFLEHOG_GUIDANCE = """### Vorgehen bei gefundenen Secrets
1. **Sofort rotieren/widerrufen**: Das betroffene Secret (Token, Passwort, Schlüssel) beim jeweiligen Dienst ungültig machen und durch ein neues ersetzen. Ein einmal committetes Secret gilt als kompromittiert, auch wenn es später aus der Historie entfernt wird.
2. **Ursache beheben**: Neues Secret nur noch über Umgebungsvariablen/Secrets-Store einbinden, nicht erneut hart codieren.
3. **Historie bereinigen (optional, manuell, erst nach Schritt 1)**: Mit `git filter-repo` oder BFG Repo-Cleaner den Commit-Inhalt entfernen, danach `git push --force` in Absprache mit allen Mitwirkenden bestehende Clones/Forks werden dadurch ungültig.
4. **Issue schließen**, sobald rotiert wurde. TruffleHog findet das alte Secret ggf. weiterhin in der Historie nach der Rotation ist das unkritisch.
> Der Wert des Secrets selbst wird hier bewusst nicht ausgegeben, auch nicht gekürzt nur Detector, Datei und Commit. Fund lässt sich über "Ziel" (Commit-Hash) und "Paket" (Dateipfad) lokalisieren."""
def escape_md_cell(value):
return str(value).replace("|", "\\|").replace("\r", " ").replace("\n", " ")
def build_report(findings, run_url):
lines = [
"Automatisch erstellt vom Security-Scan-Workflow.",
f"Lauf: {run_url}" if run_url else "",
"",
"| Quelle | ID | Schweregrad | Paket | Installiert | Fix | Ziel |",
"|---|---|---|---|---|---|---|",
]
for f in findings:
cells = (f["source"], f["id"], f["severity"], f["package"], f["installed"], f["fixed"], f["target"])
lines.append("| " + " | ".join(escape_md_cell(c) for c in cells) + " |")
if any(f["source"] == "TruffleHog" for f in findings):
lines.append("")
lines.append(TRUFFLEHOG_GUIDANCE)
return "\n".join(lines)
def ensure_label(token, gitea_url, repo):
page = 1
while True:
labels = api("GET", f"/repos/{repo}/labels?limit=50&page={page}", token, gitea_url) or []
for label in labels:
if label.get("name") == LABEL_NAME:
return label["id"]
if len(labels) < 50:
break
page += 1
created = api("POST", f"/repos/{repo}/labels", token, gitea_url, {
"name": LABEL_NAME,
"color": LABEL_COLOR,
"description": "Automatisch verwaltet vom Security-Scan-Workflow",
})
return created["id"]
def find_open_issue(token, gitea_url, repo):
issues = api(
"GET",
f"/repos/{repo}/issues?state=open&type=issues&labels={LABEL_NAME}",
token,
gitea_url,
) or []
for issue in issues:
if issue.get("title") == ISSUE_TITLE:
return issue
return None
def main():
trivy_path = sys.argv[1] if len(sys.argv) > 1 else None
osv_path = sys.argv[2] if len(sys.argv) > 2 else None
trufflehog_path = sys.argv[3] if len(sys.argv) > 3 else None
gitea_url = os.environ.get("GITEA_URL", "").strip().rstrip("/")
repo = os.environ.get("REPO", "").strip()
token = os.environ.get("TOKEN", "").strip()
run_url = os.environ.get("RUN_URL", "")
osv_exit = int(os.environ.get("OSV_EXIT", "0"))
trufflehog_exit = int(os.environ.get("TRUFFLEHOG_EXIT", "0"))
findings = sort_findings(load_trivy(trivy_path) + load_osv(osv_path) + load_trufflehog(trufflehog_path))
print_summary(findings)
if not token or not gitea_url or not repo:
missing = [name for name, val in [("TOKEN", token), ("GITEA_URL", gitea_url), ("REPO", repo)] if not val]
print(f"{', '.join(missing)} nicht gesetzt oder leer überspringe Gitea-Issue-Synchronisation.")
else:
open_issue = find_open_issue(token, gitea_url, repo)
if findings:
report = build_report(findings, run_url)
if open_issue:
print(f"Kommentiere bestehendes Issue #{open_issue['number']} mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {"body": report})
else:
label_id = ensure_label(token, gitea_url, repo)
print(f"Erstelle neues Issue mit {len(findings)} Fund(en).")
api("POST", f"/repos/{repo}/issues", token, gitea_url, {
"title": ISSUE_TITLE,
"body": report,
"labels": [label_id],
})
elif open_issue:
print(f"Keine aktuellen Funde mehr. Kommentiere Issue #{open_issue['number']}.")
api("POST", f"/repos/{repo}/issues/{open_issue['number']}/comments", token, gitea_url, {
"body": f"Aktueller Scan hat keine offenen Schwachstellen mehr gefunden.\n\n{run_url}".strip(),
})
else:
print("Keine Funde und kein offenes Issue vorhanden.")
osv_ok_exits = {0, 1}
trufflehog_ok_exits = {0, 183}
if osv_exit not in osv_ok_exits:
print(f"WARNUNG: osv-scanner beendete sich mit unerwartetem Exit-Code {osv_exit} - Scan evtl. unvollständig.",
file=sys.stderr)
if trufflehog_exit not in trufflehog_ok_exits:
print(
f"WARNUNG: trufflehog beendete sich mit unerwartetem Exit-Code {trufflehog_exit} - Scan evtl. unvollständig.",
file=sys.stderr)
has_trivy_findings = any(f["source"].startswith("Trivy") for f in findings)
if (
has_trivy_findings
or osv_exit == 1
or trufflehog_exit == 183
or osv_exit not in osv_ok_exits
or trufflehog_exit not in trufflehog_ok_exits
):
sys.exit(1)
if __name__ == "__main__":
main()
+37
View File
@@ -0,0 +1,37 @@
//! Modul für die Konfigurationsverwaltung der Anwendung.
//!
//! Dieses Modul stellt die Datenstrukturen für die Anwendungskonfiguration bereit.
//! Das Laden, Speichern und Cachen erfolgt über das Crate `config-ctdra`.
use config_ctdra::get_config as get_cached_config;
use serde::{Deserialize, Serialize};
/// Hauptkonfigurationsstruktur der Anwendung.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub struct AppConfig {
/// Allgemeine Einstellungen
pub general: General,
}
/// Allgemeine Konfigurationseinstellungen.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct General {
/// Log-Level für die Anwendung (error, warn, info, debug)
pub log_level: String,
/// Pfad zum Verzeichnis mit den Docker-Applikationen
pub apps_dir: String,
}
impl Default for General {
fn default() -> Self {
Self {
log_level: "info".to_string(),
apps_dir: "/var/apps".to_string(),
}
}
}
/// Gibt die globale Konfiguration zurück.
pub fn get_config() -> &'static AppConfig {
get_cached_config::<AppConfig>()
}
+4
View File
@@ -0,0 +1,4 @@
//! DockerUpdate Bibliotheksexporte
pub mod config;
pub mod updater;
+31
View File
@@ -0,0 +1,31 @@
use docker_update::config::get_config;
use docker_update::updater;
use logger_ctdra::{LogLevel, log};
use std::process::exit;
use sudo_ctdra::{is_run_as_root, run_as_root};
fn main() {
if !is_run_as_root() {
log(
"auth",
"Root-Rechte erforderlich. Starte mit sudo neu...",
LogLevel::Info,
);
let err = run_as_root();
log(
"auth",
&format!("Fehler beim Ausführen von 'sudo': {}", err),
LogLevel::Error,
);
exit(1);
}
let config = get_config();
if let Ok(level) = LogLevel::try_from(config.general.log_level.as_str()) {
logger_ctdra::init(level);
}
log("main", "docker-update gestartet.", LogLevel::Info);
updater::run_updates();
log("main", "docker-update beendet.", LogLevel::Info);
}
+412
View File
@@ -0,0 +1,412 @@
use crate::config;
use logger_ctdra::{LogLevel, log};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
const COMPOSE_FILENAMES: &[&str] = &[
"docker-compose.yaml",
"docker-compose.yml",
"compose.yaml",
"compose.yml",
];
#[derive(Debug, Deserialize)]
struct ComposeFile {
#[serde(default)]
services: HashMap<String, ServiceConfig>,
}
#[derive(Debug, Deserialize)]
struct ServiceConfig {
#[serde(default)]
image: Option<String>,
}
/// Prüft, ob ein Image-Tag auf `:latest` zeigt (oder implizit `:latest` ist, weil kein Tag angegeben wurde).
pub fn is_latest_image(image: &str) -> bool {
let image = image.trim();
if image.is_empty() {
return false;
}
// Wenn das Image an einen Digest gepinnt ist (z. B. image@sha256:...), ist es kein dynamisches latest-Tag.
if image.contains('@') {
return false;
}
// Den Tag-Teil nach dem letzten Schrägstrich ermitteln (um Host:Port/Repo nicht fälschlich als Tag zu werten)
let repo_and_tag = image.rsplit('/').next().unwrap_or(image);
if let Some((_, tag)) = repo_and_tag.split_once(':') {
tag.eq_ignore_ascii_case("latest")
} else {
// Kein Tag angegeben bedeutet bei Docker implizit `:latest`
true
}
}
/// Sucht in einem Verzeichnis nach einer gültigen Docker-Compose-Datei.
pub fn find_compose_file(dir: &Path) -> Option<PathBuf> {
for filename in COMPOSE_FILENAMES {
let path = dir.join(filename);
if path.is_file() {
return Some(path);
}
}
None
}
/// Liest eine Compose-Datei ein und liefert eine Liste von (Service-Name, Image-Name).
pub fn parse_compose_services(compose_path: &Path) -> Result<Vec<(String, String)>, String> {
let content = fs::read_to_string(compose_path).map_err(|e| {
format!(
"Fehler beim Lesen der Datei '{}': {}",
compose_path.display(),
e
)
})?;
let compose: ComposeFile = serde_saphyr::from_str(&content).map_err(|e| {
format!(
"Fehler beim Parsen der YAML-Datei '{}': {}",
compose_path.display(),
e
)
})?;
let mut services = Vec::new();
for (service_name, config) in compose.services {
if let Some(image) = config.image {
let image = image.trim().to_string();
if !image.is_empty() {
services.push((service_name, image));
}
}
}
Ok(services)
}
/// Führt einen Docker-Befehl aus und liefert die Ausgabe.
fn run_docker_command(args: &[&str]) -> std::io::Result<Output> {
Command::new("docker").args(args).output()
}
/// Führt einen Docker-Compose-Befehl im angegebenen Verzeichnis aus.
/// Versucht zuerst `docker compose` und fällt bei Nichtverfügbarkeit auf `docker-compose` zurück.
pub fn run_compose_command(dir: &Path, args: &[&str]) -> std::io::Result<Output> {
let mut compose_args = vec!["compose"];
compose_args.extend_from_slice(args);
let output = Command::new("docker")
.current_dir(dir)
.args(&compose_args)
.output();
match output {
Ok(out) => {
let stderr = String::from_utf8_lossy(&out.stderr);
let is_unavailable = !out.status.success()
&& (stderr.contains("is not a docker command")
|| stderr.contains("unknown command \"compose\"")
|| stderr.contains("unknown command: compose"));
if is_unavailable
&& let Ok(fallback_out) = Command::new("docker-compose")
.current_dir(dir)
.args(args)
.output()
{
return Ok(fallback_out);
}
Ok(out)
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
// docker-Binary nicht gefunden, versuche docker-compose
Command::new("docker-compose")
.current_dir(dir)
.args(args)
.output()
}
Err(err) => Err(err),
}
}
/// Ermittelt die lokale Image-ID für ein gegebenes Docker-Image.
pub fn get_local_image_id(image: &str) -> Option<String> {
let output = run_docker_command(&["image", "inspect", "--format", "{{.Id}}", image]).ok()?;
if output.status.success() {
let id = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !id.is_empty() {
return Some(id);
}
}
None
}
/// Zieht ein Docker-Image herunter und gibt zurück, ob ein neueres Image heruntergeladen wurde.
pub fn pull_image(image: &str) -> Result<bool, String> {
let old_id = get_local_image_id(image);
let output = run_docker_command(&["pull", image])
.map_err(|e| format!("Fehler beim Ausführen von 'docker pull {}': {}", image, e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(format!(
"'docker pull {}' fehlgeschlagen: {}",
image, stderr
));
}
let stdout_str = String::from_utf8_lossy(&output.stdout);
let new_id = get_local_image_id(image);
let updated = match (&old_id, &new_id) {
(Some(old), Some(new)) => old != new,
(None, Some(_)) => true,
_ => stdout_str.contains("Downloaded newer image") || stdout_str.contains("Pull complete"),
};
Ok(updated)
}
/// Aktualisiert und startet die Docker-Compose-Services im angegebenen Verzeichnis neu.
pub fn restart_compose_service(dir: &Path, service_name: Option<&str>) -> Result<(), String> {
let mut args = vec!["up", "-d"];
if let Some(service) = service_name {
args.push(service);
}
let output = run_compose_command(dir, &args)
.map_err(|e| format!("Fehler beim Ausführen von 'docker compose up -d': {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(format!("Neustart fehlgeschlagen: {}", stderr));
}
Ok(())
}
/// Bereinigt ungenutzte Zwischen-Images via `docker image prune -f`.
pub fn prune_images() -> Result<(), String> {
let output = run_docker_command(&["image", "prune", "-f"])
.map_err(|e| format!("Fehler beim Ausführen von 'docker image prune -f': {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(format!(
"'docker image prune -f' fehlgeschlagen: {}",
stderr
));
}
Ok(())
}
/// Prüft alle Unterverzeichnisse im konfigurierten Apps-Verzeichnis auf Aktualisierungen.
pub fn run_updates() {
let config = config::get_config();
let apps_dir_path = Path::new(&config.general.apps_dir);
log(
"scanner",
&format!(
"Überprüfe Anwendungsverzeichnis: {}",
apps_dir_path.display()
),
LogLevel::Info,
);
if !apps_dir_path.exists() {
log(
"scanner",
&format!("Verzeichnis '{}' existiert nicht.", apps_dir_path.display()),
LogLevel::Warn,
);
return;
}
let entries = match fs::read_dir(apps_dir_path) {
Ok(e) => e,
Err(e) => {
log(
"scanner",
&format!("Fehler beim Lesen von '{}': {}", apps_dir_path.display(), e),
LogLevel::Error,
);
return;
}
};
let mut app_dirs: Vec<PathBuf> = entries
.filter_map(|res| res.ok().map(|e| e.path()))
.filter(|p| p.is_dir())
.collect();
app_dirs.sort();
if app_dirs.is_empty() {
log(
"scanner",
&format!(
"Keine Unterverzeichnisse in '{}' gefunden.",
apps_dir_path.display()
),
LogLevel::Info,
);
return;
}
for app_dir in app_dirs {
let app_name = app_dir.file_name().unwrap_or_default().to_string_lossy();
log(
"scanner",
&format!("Untersuche Anwendung '{}'...", app_name),
LogLevel::Info,
);
let compose_file = match find_compose_file(&app_dir) {
Some(f) => f,
None => {
log(
"scanner",
&format!(
"Keine Docker-Compose-Datei in '{}' gefunden. Überspringe.",
app_dir.display()
),
LogLevel::Debug,
);
continue;
}
};
log(
"compose",
&format!("Gefundene Compose-Datei: {}", compose_file.display()),
LogLevel::Debug,
);
let services = match parse_compose_services(&compose_file) {
Ok(s) => s,
Err(e) => {
log(
"compose",
&format!(
"Fehler beim Analysieren von '{}': {}",
compose_file.display(),
e
),
LogLevel::Warn,
);
continue;
}
};
let mut updated_any = false;
for (service_name, image) in services {
if !is_latest_image(&image) {
log(
"updater",
&format!(
"Service '{}' verwendet kein :latest-Image ('{}'). Überspringe.",
service_name, image
),
LogLevel::Debug,
);
continue;
}
log(
"updater",
&format!(
"Prüfe auf neues Image für Service '{}' ('{}')...",
service_name, image
),
LogLevel::Info,
);
match pull_image(&image) {
Ok(true) => {
log(
"updater",
&format!(
"Neueres Image für Service '{}' ('{}') gefunden und heruntergeladen.",
service_name, image
),
LogLevel::Info,
);
updated_any = true;
}
Ok(false) => {
log(
"updater",
&format!(
"Image für Service '{}' ('{}') ist bereits aktuell.",
service_name, image
),
LogLevel::Info,
);
}
Err(e) => {
log(
"updater",
&format!("Fehler beim Aktualisieren von Image '{}': {}", image, e),
LogLevel::Error,
);
}
}
}
if updated_any {
log(
"updater",
&format!(
"Starte Anwendung '{}' neu mit aktualisierten Images...",
app_name
),
LogLevel::Info,
);
match restart_compose_service(&app_dir, None) {
Ok(_) => {
log(
"updater",
&format!(
"Anwendung '{}' erfolgreich aktualisiert und neu gestartet.",
app_name
),
LogLevel::Info,
);
}
Err(e) => {
log(
"updater",
&format!("Fehler beim Neustarten der Anwendung '{}': {}", app_name, e),
LogLevel::Error,
);
}
}
log(
"updater",
"Bereinige ungenutzte Docker-Images...",
LogLevel::Info,
);
if let Err(e) = prune_images() {
log(
"updater",
&format!("Fehler beim Bereinigen ungenutzter Docker-Images: {}", e),
LogLevel::Warn,
);
}
}
}
log(
"scanner",
"Aktualisierungsvorgang abgeschlossen.",
LogLevel::Info,
);
}
+8
View File
@@ -0,0 +1,8 @@
use docker_update::config::AppConfig;
#[test]
fn test_default_config() {
let config = AppConfig::default();
assert_eq!(config.general.apps_dir, "/var/apps");
assert_eq!(config.general.log_level, "info");
}
+68
View File
@@ -0,0 +1,68 @@
use docker_update::updater::{find_compose_file, is_latest_image, parse_compose_services};
use std::fs::{self, File};
use std::io::Write;
#[test]
fn test_is_latest_image() {
assert!(is_latest_image("nginx"));
assert!(is_latest_image("nginx:latest"));
assert!(is_latest_image("nginx:LATEST"));
assert!(is_latest_image("gitea/gitea"));
assert!(is_latest_image("gitea/gitea:latest"));
assert!(is_latest_image("localhost:5000/my-app"));
assert!(is_latest_image("localhost:5000/my-app:latest"));
assert!(!is_latest_image("nginx:alpine"));
assert!(!is_latest_image("gitea/gitea:1.20"));
assert!(!is_latest_image("postgres:15-alpine"));
assert!(!is_latest_image("localhost:5000/my-app:v1.0"));
assert!(!is_latest_image("nginx@sha256:1234567890abcdef"));
assert!(!is_latest_image(""));
}
#[test]
fn test_parse_compose_services() {
let temp_dir = std::env::temp_dir().join("docker_update_test_compose");
let _ = fs::create_dir_all(&temp_dir);
let compose_path = temp_dir.join("docker-compose.yaml");
let yaml_content = r#"
services:
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
db:
image: postgres:15
environment:
POSTGRES_DB: gitea
"#;
let mut file = File::create(&compose_path).unwrap();
file.write_all(yaml_content.as_bytes()).unwrap();
let services = parse_compose_services(&compose_path).unwrap();
assert_eq!(services.len(), 2);
let gitea_svc = services.iter().find(|(s, _)| s == "gitea").unwrap();
assert_eq!(gitea_svc.1, "gitea/gitea:latest");
let db_svc = services.iter().find(|(s, _)| s == "db").unwrap();
assert_eq!(db_svc.1, "postgres:15");
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_find_compose_file() {
let temp_dir = std::env::temp_dir().join("docker_update_test_find");
let _ = fs::create_dir_all(&temp_dir);
assert_eq!(find_compose_file(&temp_dir), None);
let compose_path = temp_dir.join("docker-compose.yaml");
File::create(&compose_path).unwrap();
assert_eq!(find_compose_file(&temp_dir), Some(compose_path));
let _ = fs::remove_dir_all(&temp_dir);
}