Compare commits

...
49 Commits
Author SHA1 Message Date
DragonSlayer_14andClaude Sonnet 5 052f5fa7c1 CI: Renovate-Schedule von wöchentlich auf täglich verkürzt
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 27s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m1s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m10s
'before 6am on monday' erlaubte nur einmal pro Woche automatische
PR-Erstellung - jeder außerhalb dieses Fensters gefundene Update musste
im Dependency-Dashboard-Issue erst manuell abgehakt werden ("Awaiting
Schedule"), auch nach der letzten Automerge-Änderung. 'before 6am' (ohne
Wochentag) öffnet das Fenster jetzt täglich, PRs für sichere Updates
laufen dadurch spürbar zeitnaher und größtenteils ohne manuelles
Abhaken durch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:36:45 +02:00
DragonSlayer_14andClaude Sonnet 5 882118c58c CI: Renovate merged sichere Updates automatisch, unit-tests laufen jetzt auch für PRs gegen dev
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 21s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 59s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m8s
Patch-/Minor-/Digest-Updates (Cargo, Gitea Actions, Docker-Images)
werden künftig automatisch gemergt, sobald alle CI-Checks erfolgreich
sind - Major-Updates bleiben weiterhin manuell zu prüfen, da sie eher
brechende Änderungen enthalten.

Voraussetzung dafür: unit-tests.yaml (der eigentliche 'cargo test'-Lauf)
löste bisher nur bei Pull Requests gegen 'testing' aus, nicht gegen
'dev' - Renovate-PRs zielen aber auf 'dev' und wurden damit nie durch
einen echten Build/Test abgesichert, nur durch die Security-/Secret-
Scans. Jetzt läuft er auch dort.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:26:55 +02:00
DragonSlayer_14andClaude Sonnet 5 e67cecdf6e CI: Ersetzt rohes actions/cache für Cargo-Build-Artefakte durch Swatinem/rust-cache
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 21s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m6s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 2m8s
Ein rohes 'actions/cache' auf target/ lieferte zwar einen technischen
Cache-Hit, Cargo kompilierte die Abhängigkeiten aber trotzdem neu: der
tar-basierte Restore-Vorgang setzt bei allen wiederhergestellten
Dateien dieselbe Mtime, wodurch Cargos Fingerprinting nicht mehr
zuverlässig erkennen kann, was älter/neuer als was ist. Swatinem/rust-cache
behebt das gezielt (u. a. gezielte Mtime-Korrektur nach dem Restore).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 23:17:51 +02:00
DragonSlayer_14 2343363b68 Merge pull request 'chore(deps): update rust crate serde-saphyr to v1.3.0' (#35) 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 29s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 54s
Security Scans / Trivy & OSV-Scanner (push) Successful in 56s
Reviewed-on: #35
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-20 09:03:05 +00:00
DragonSlayer_14 463152d4fa Merge pull request 'chore(deps): update gitea actions' (#34) 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 29s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m17s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m16s
Reviewed-on: #34
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-20 09:02:31 +00:00
Renovate-Bot a2c4a2cbad chore(deps): update gitea actions
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 16s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 29s
2026-09-20 03:01:00 +00:00
Renovate-Bot 780a014d21 chore(deps): update rust crate serde-saphyr to v1.3.0
TruffleHog Secret Scan / TruffleHog (push) Successful in 31s
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 (pull_request) Successful in 29s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 48s
2026-09-19 19:09:15 +00:00
DragonSlayer_14 783d5c8782 Merge pull request 'chore(deps): update gitea actions' (#33) from renovate/gitea-actions into dev
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m9s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m16s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 31s
Reviewed-on: #33
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-14 10:51:47 +00:00
Renovate-Bot 46ea4c32c6 chore(deps): update gitea actions
TruffleHog Secret Scan / TruffleHog (push) Successful in 15s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 39s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
Unit-Tests / Unit-Tests (pull_request) Skipped
2026-09-14 10:01:00 +00:00
Gitea-Bot d00b9a9aec Chore: Erhöht Patch-Version auf 1.2.3 für Promotion nach testing
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 55s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m19s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 56s
TruffleHog Secret Scan / TruffleHog (push) Successful in 31s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 24s
Unit-Tests / Unit-Tests (pull_request) Successful in 32s
2026-09-13 14:49:03 +00:00
DragonSlayer_14 cfc484937a Merge pull request 'chore(deps): update cargo dependencies' (#29) 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 30s
Security Scans / Trivy & OSV-Scanner (push) Successful in 59s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m15s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 30s
Auto Patch-Version-Bump (Dev → Testing PR) / Erkenne relevante Änderungen im PR (pull_request) Successful in 13s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 53s
Auto Patch-Version-Bump (Dev → Testing PR) / Patch-Version erhöhen & auf Dev pushen (pull_request) Successful in 15s
Unit-Tests / Unit-Tests (pull_request) Successful in 1m13s
Reviewed-on: #29
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-13 14:45:08 +00:00
DragonSlayer_14 8637146add Merge pull request 'chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.83.0' (#30) 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 15s
Security Scans / Trivy & OSV-Scanner (push) Successful in 28s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 36s
Reviewed-on: #30
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-13 14:42:57 +00:00
Renovate-Bot 009a1ef853 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.83.0
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 11s
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
2026-09-13 14:02:47 +00:00
Renovate-Bot fc2cc4230d chore(deps): update cargo dependencies
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 21s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 22s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 31s
2026-09-13 14:02:41 +00:00
DragonSlayer_14andClaude Sonnet 5 fe7a9472f7 Feat: Fügt automatischen PR-Workflow für Testing→Main hinzu
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 27s
Security Scans / Trivy & OSV-Scanner (push) Successful in 55s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 57s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AjPGeW28DpjnMkuvLSRTKv
2026-09-13 14:26:01 +02:00
DragonSlayer_14andClaude Sonnet 5 77bce7b90e Feat: Fügt automatischen Patch-Version-Bump für Dev→Testing-PRs hinzu
Security Scans / Trivy & OSV-Scanner (push) Successful in 54s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m5s
Testing Build, Publish & Preview Release / Build, Publish Packages (Testing) & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 29s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DrWxHCG7URf4FEy8Hja23f
2026-09-13 14:04:51 +02: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 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
31 changed files with 1691 additions and 549 deletions
+10
View File
@@ -1,3 +1,13 @@
[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"
+52
View File
@@ -0,0 +1,52 @@
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
# Ein rohes 'actions/cache' auf target/ liefert zwar einen technischen Cache-Hit (Dateien
# werden wiederhergestellt), Cargo kompiliert die Abhängigkeiten aber oft trotzdem neu:
# Der tar-basierte Restore-Vorgang setzt bei allen wiederhergestellten Dateien dieselbe
# Mtime, wodurch Cargos Fingerprinting nicht mehr zuverlässig erkennen kann, was
# älter/neuer als was ist, und sicherheitshalber alles neu baut. Swatinem/rust-cache ist
# genau dafür gebaut (u. a. gezielte Mtime-Korrektur nach dem Restore).
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: Swatinem/rust-cache@v2
- 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
+100 -17
View File
@@ -6,31 +6,100 @@ on:
- 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@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Install Cross-Compilation Toolchains & Dependencies
# Ein rohes 'actions/cache' auf target/ liefert zwar einen technischen Cache-Hit (Dateien
# werden wiederhergestellt), Cargo kompiliert die Abhängigkeiten aber oft trotzdem neu:
# Der tar-basierte Restore-Vorgang setzt bei allen wiederhergestellten Dateien dieselbe
# Mtime, wodurch Cargos Fingerprinting nicht mehr zuverlässig erkennen kann, was
# älter/neuer als was ist, und sicherheitshalber alles neu baut. Swatinem/rust-cache ist
# genau dafür gebaut (u. a. gezielte Mtime-Korrektur nach dem Restore).
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: Swatinem/rust-cache@v2
- 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
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
- name: Install Packaging Tools (Prebuilt Binaries)
- 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
curl -fsSL https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xz -C ~/.cargo/bin
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
~/.cargo/bin/cargo-binstall -y --no-symlinks cargo-deb cargo-generate-rpm
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: |
@@ -42,24 +111,37 @@ jobs:
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 --no-build
cargo deb --target aarch64-unknown-linux-gnu --no-build
cargo deb --target i686-unknown-linux-gnu --no-build
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 -o 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
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
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu
python3 scripts/package-arch.py --target i686-unknown-linux-gnu
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:
@@ -98,12 +180,13 @@ jobs:
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 docker-update ${VERSION}."
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} ${VERSION}."
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
+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.103.6
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.6.0"
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
+111
View File
@@ -0,0 +1,111 @@
name: Auto-PR (Testing → Main)
on:
push:
branches:
- testing
jobs:
create-pr:
name: Erstelle automatisch PR von testing nach main
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Prüfe auf bereits offenen PR nach main
id: check_pr
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
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: |
OPEN_PRS=$(curl -s -H "Authorization: token ${TOKEN}" "${GITEA_URL}/api/v1/repos/${REPO}/pulls?state=open&limit=50")
EXISTS=$(echo "$OPEN_PRS" | jq -r '[.[] | select(.base.ref == "main" and .head.ref == "testing")] | length')
echo "Bereits offene testing→main PRs: ${EXISTS}"
echo "exists=${EXISTS}" >> "$GITHUB_OUTPUT"
- name: Ermittle Versionen auf main & testing
id: versions
if: steps.check_pr.outputs.exists == '0'
run: |
git fetch origin main
MAIN_VERSION="$(git show origin/main:Cargo.toml | sed -n 's/^version = "\(.*\)"/\1/p' | head -n1)"
TESTING_VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
echo "Version auf main: ${MAIN_VERSION} / Version auf testing: ${TESTING_VERSION}"
echo "main_version=${MAIN_VERSION}" >> "$GITHUB_OUTPUT"
echo "testing_version=${TESTING_VERSION}" >> "$GITHUB_OUTPUT"
- name: Ermittle geänderte Kategorien (main...testing)
id: categories
if: steps.check_pr.outputs.exists == '0' && steps.versions.outputs.main_version == steps.versions.outputs.testing_version
run: |
CHANGED_FILES="$(git diff --name-only origin/main...HEAD)"
echo "Geänderte Dateien main...testing:"
echo "$CHANGED_FILES"
WORKFLOWS="false"
CONFIG="false"
DOCS="false"
if echo "$CHANGED_FILES" | grep -q '^\.gitea/workflows/'; then
WORKFLOWS="true"
fi
if echo "$CHANGED_FILES" | grep -qE '^(renovate\.json|qodana\.yaml|Cargo\.toml|Cargo\.lock|\.cargo/)'; then
CONFIG="true"
fi
if echo "$CHANGED_FILES" | grep -qE '(^|/)[^/]+\.md$|^LICENSE$'; then
DOCS="true"
fi
echo "workflows=${WORKFLOWS}" >> "$GITHUB_OUTPUT"
echo "config=${CONFIG}" >> "$GITHUB_OUTPUT"
echo "docs=${DOCS}" >> "$GITHUB_OUTPUT"
- name: Bestimme PR-Titel
id: title
if: steps.check_pr.outputs.exists == '0'
run: |
if [ "${{ steps.versions.outputs.main_version }}" != "${{ steps.versions.outputs.testing_version }}" ]; then
TITLE="Merge testing in main: Release ${{ steps.versions.outputs.testing_version }}"
else
PARTS=()
[ "${{ steps.categories.outputs.workflows }}" = "true" ] && PARTS+=("Workflows")
[ "${{ steps.categories.outputs.config }}" = "true" ] && PARTS+=("Konfigurationen")
[ "${{ steps.categories.outputs.docs }}" = "true" ] && PARTS+=("Dokumentation")
if [ ${#PARTS[@]} -eq 0 ]; then
TITLE="Merge testing in main"
else
JOINED=""
for PART in "${PARTS[@]}"; do
if [ -z "$JOINED" ]; then
JOINED="$PART"
else
JOINED="${JOINED} & ${PART}"
fi
done
TITLE="Merge testing in main: ${JOINED} aktualisiert"
fi
fi
echo "Ermittelter PR-Titel: ${TITLE}"
echo "title=${TITLE}" >> "$GITHUB_OUTPUT"
- name: Erstelle PR (testing -> main)
if: steps.check_pr.outputs.exists == '0'
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
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 }}
TITLE: ${{ steps.title.outputs.title }}
run: |
PAYLOAD=$(jq -n --arg title "$TITLE" --arg head "testing" --arg base "main" \
'{title: $title, head: $head, base: $base}')
curl -f -s -S -X POST \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" \
"${GITEA_URL}/api/v1/repos/${REPO}/pulls"
echo "PR erstellt: ${TITLE}"
+100 -17
View File
@@ -6,31 +6,100 @@ on:
- 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@v1
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Install Cross-Compilation Toolchains & Dependencies
# Ein rohes 'actions/cache' auf target/ liefert zwar einen technischen Cache-Hit (Dateien
# werden wiederhergestellt), Cargo kompiliert die Abhängigkeiten aber oft trotzdem neu:
# Der tar-basierte Restore-Vorgang setzt bei allen wiederhergestellten Dateien dieselbe
# Mtime, wodurch Cargos Fingerprinting nicht mehr zuverlässig erkennen kann, was
# älter/neuer als was ist, und sicherheitshalber alles neu baut. Swatinem/rust-cache ist
# genau dafür gebaut (u. a. gezielte Mtime-Korrektur nach dem Restore).
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: Swatinem/rust-cache@v2
- 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
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
- name: Install Packaging Tools (Prebuilt Binaries)
- 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
curl -fsSL https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xz -C ~/.cargo/bin
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
~/.cargo/bin/cargo-binstall -y --no-symlinks cargo-deb cargo-generate-rpm
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: |
@@ -42,24 +111,37 @@ jobs:
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 --no-build
cargo deb --target aarch64-unknown-linux-gnu --no-build
cargo deb --target i686-unknown-linux-gnu --no-build
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 -o 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
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
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu
python3 scripts/package-arch.py --target i686-unknown-linux-gnu
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:
@@ -98,12 +180,13 @@ jobs:
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 docker-update ${VERSION} (Branch: Testing)."
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}..."
+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.5"
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
+37
View File
@@ -0,0 +1,37 @@
name: Unit-Tests
on:
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- testing
- dev
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
# Ein rohes 'actions/cache' auf target/ liefert zwar einen technischen Cache-Hit (Dateien
# werden wiederhergestellt), Cargo kompiliert die Abhängigkeiten aber oft trotzdem neu:
# Der tar-basierte Restore-Vorgang setzt bei allen wiederhergestellten Dateien dieselbe
# Mtime, wodurch Cargos Fingerprinting nicht mehr zuverlässig erkennen kann, was
# älter/neuer als was ist, und sicherheitshalber alles neu baut. Swatinem/rust-cache ist
# genau dafür gebaut (u. a. gezielte Mtime-Korrektur nach dem Restore).
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: Swatinem/rust-cache@v2
- name: Run Tests
run: cargo test
+84
View File
@@ -0,0 +1,84 @@
name: Auto Patch-Version-Bump (Dev → Testing PR)
on:
pull_request:
types: [opened]
branches:
- testing
jobs:
detect-changes:
name: Erkenne relevante Änderungen im PR
runs-on: ubuntu-latest
if: ${{ (gitea.head_ref || github.head_ref) == 'dev' }}
outputs:
code_changed: ${{ steps.filter.outputs.code }}
steps:
- name: Checkout Dev-Branch (PR-Head)
uses: actions/checkout@v7
with:
ref: ${{ gitea.head_ref || github.head_ref }}
fetch-depth: 0
- name: Prüfe auf Änderungen an Cargo.toml, Cargo.lock oder src/
uses: dorny/paths-filter@v4
id: filter
with:
base: ${{ gitea.base_ref || github.base_ref }}
filters: |
code:
- 'Cargo.toml'
- 'Cargo.lock'
- 'src/**'
bump-version:
name: Patch-Version erhöhen & auf Dev pushen
needs: detect-changes
if: needs.detect-changes.outputs.code_changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout Dev-Branch (PR-Head)
uses: actions/checkout@v7
with:
ref: ${{ gitea.head_ref || github.head_ref }}
fetch-depth: 0
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: Ermittle Cargo-Version auf testing & dev
id: versions
run: |
git fetch origin testing --depth=1
TESTING_VERSION="$(git show origin/testing:Cargo.toml | sed -n 's/^version = "\(.*\)"/\1/p' | head -n1)"
DEV_VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
echo "Version auf testing: ${TESTING_VERSION} / Version auf dev: ${DEV_VERSION}"
echo "testing_version=${TESTING_VERSION}" >> "$GITHUB_OUTPUT"
echo "dev_version=${DEV_VERSION}" >> "$GITHUB_OUTPUT"
- name: Patch-Version um 1 erhöhen (Cargo.toml & Cargo.lock)
if: steps.versions.outputs.testing_version == steps.versions.outputs.dev_version
run: |
VERSION="${{ steps.versions.outputs.dev_version }}"
MAJOR="$(echo "$VERSION" | cut -d. -f1)"
MINOR="$(echo "$VERSION" | cut -d. -f2)"
PATCH="$(echo "$VERSION" | cut -d. -f3)"
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
echo "Erhöhe Version: ${VERSION} -> ${NEW_VERSION}"
sed -i "0,/^version = \"${VERSION}\"/s//version = \"${NEW_VERSION}\"/" Cargo.toml
PACKAGE_NAME="$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
awk -v new="$NEW_VERSION" -v pkg="$PACKAGE_NAME" '
found_name && /^version = "/ {
print "version = \"" new "\""
found_name = 0
next
}
$0 == "name = \"" pkg "\"" { found_name = 1 }
{ print }
' Cargo.lock > Cargo.lock.tmp && mv Cargo.lock.tmp Cargo.lock
git config user.name "Gitea-Bot"
git config user.email "no-reply@creativedragonslayer.de"
git add Cargo.toml Cargo.lock
git commit -m "Chore: Erhöht Patch-Version auf ${NEW_VERSION} für Promotion nach testing"
git push origin HEAD:${{ gitea.head_ref || github.head_ref }}
+1
View File
@@ -109,3 +109,4 @@ fabric.properties
# Built Visual Studio Code Extensions
*.vsix
.junie/plans
+1
View File
@@ -3,6 +3,7 @@
<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" />
+15
View File
@@ -17,6 +17,21 @@
<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>
+15 -18
View File
@@ -17,7 +17,7 @@ lädt diese herunter, startet betroffene Services neu und bereinigt ungenutzte Z
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 (`src/log.rs`) verwendet einheitliche ASCII-Präfixe (`[!]`
- **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`).
@@ -32,45 +32,42 @@ DockerUpdate/
│ ├── 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/
── package-arch.py # Hilfsskript zur Arch-Linux-Paketerstellung (.pkg.tar.zst)
── 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
├── program.rs # Hilfsfunktion zur Programmnamensermittlung
├── sudo.rs # Root-Prüfung (geteuid == 0) und Re-Exec via sudo
├── config.rs # Konfigurationsverwaltung (confy / TOML)
├── log.rs # Threadsicheres Logging (Terminal + Datei im Temp-Verzeichnis)
├── 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::is_run_as_root()`, ob Root-Rechte vorliegen. Wenn nicht, wird mit
`sudo::run_as_root()` neu gestartet. Ruft anschließend `updater::run_updates()` auf.
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_yaml` und filtert mit `is_latest_image()` nach Services mit `:latest`-Images oder ohne
- 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`**:
- Lädt und speichert die Konfiguration (`AppConfig`, `General` mit `apps_dir` und `log_level`).
- Standardpfade: `/etc/docker-update/config.toml` (Root) bzw. `~/.config/docker-update/config.toml` (Benutzer).
- **`src/log.rs`**:
- Formatiert Logmeldungen nach Schema `[Präfix][Zeitstempel][LEVEL][Tag]: Nachricht`.
- Gibt auf Konsole aus und schreibt zusätzlich in tagesbasierte Logdateien im Temp-Verzeichnis
(`/tmp/docker-update-.../log-YYYY-MM-DD.log`).
- **`src/sudo.rs`**:
- `is_run_as_root()`: Nutzt `libc::geteuid() == 0`.
- `run_as_root()`: Führt `sudo <args>` per `exec` aus.
- 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
Generated
+153 -28
View File
@@ -2,12 +2,59 @@
# 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.6"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "83eb23d473fce79e8234ad66baf210289b5c9e4c4a587273b4df7deceb73f416"
dependencies = [
"confy",
"program-ctdra",
"serde",
"sudo-ctdra",
]
[[package]]
name = "confy"
version = "2.0.0"
@@ -29,13 +76,31 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
[[package]]
name = "docker-update"
version = "1.0.2"
version = "1.2.3"
dependencies = [
"confy",
"libc 1.0.0-alpha.4",
"config-ctdra",
"logger-ctdra",
"serde",
"serde_yaml",
"time",
"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]]
@@ -55,6 +120,16 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "granit-parser"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e20f99e46474f56bd905c56e817ebddcf377a611f94c53ac4649e4d3fa3c0cd0"
dependencies = [
"arraydeque",
"smallvec",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
@@ -80,12 +155,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -104,12 +173,43 @@ 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.5"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "ce612b7d943b77060fa36eab0c85782e650c4cf023e9d560bef791951ed92cad"
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"
@@ -134,6 +234,12 @@ 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"
@@ -143,12 +249,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "serde"
version = "1.0.229"
@@ -159,6 +259,23 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-saphyr"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8050abb251097357e24aff63ba2c52a6309ecb7d23a5474023df960a02694d8"
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"
@@ -189,16 +306,18 @@ dependencies = [
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "sudo-ctdra"
version = "1.0.1"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "20e576be60eb2050d475d0fbe46f0d09462ba9985ab55c7833e0c2e6d116d341"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
"libc 1.0.0-alpha.4",
]
[[package]]
@@ -310,10 +429,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "windows-link"
@@ -414,3 +533,9 @@ 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"
+6 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "docker-update"
version = "1.0.2"
version = "1.2.3"
edition = "2024"
authors = ['DragonSlayer_14']
readme = "README.md"
@@ -9,11 +9,11 @@ repository = "https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate"
description = "Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images"
[dependencies]
time = { version="0.3.41", features = ["formatting", "macros", "local-offset"] }
serde = { version="1.0.219", features = ["derive"] }
serde_yaml = "0.9"
confy = "2.0.0"
libc = "1.0.0-alpha.1"
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"
+3
View File
@@ -1,5 +1,8 @@
# DockerUpdate (`docker-update`)
[![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.
+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
+131
View File
@@ -0,0 +1,131 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"timezone": "Europe/Berlin",
"schedule": [
"before 6am"
],
"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
},
{
"description": "Patch-/Minor-/Digest-Updates automatisch mergen, sobald alle CI-Checks (inkl. Unit-Tests) erfolgreich sind - Major-Updates sind unten explizit ausgenommen (siehe nächste Regel).",
"matchUpdateTypes": [
"patch",
"minor",
"digest",
"lockFileMaintenance"
],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": true
},
{
"description": "Major-Updates immer manuell prüfen, da potenziell brechende Änderungen.",
"matchUpdateTypes": [
"major"
],
"automerge": 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()
+31 -5
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env python3
import argparse
import importlib.util
import json
import os
import subprocess
import sys
import tempfile
import time
@@ -22,7 +24,28 @@ TARGET_ARCH_MAP = {
}
def build_package(target_triple=None, target_arch=None):
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"]
@@ -34,7 +57,9 @@ def build_package(target_triple=None, target_arch=None):
author = authors[0] if authors else "Unknown"
arch_meta = pkg.get("metadata", {}).get("arch", {})
pkgrel = arch_meta.get("pkgrel", "1")
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:
@@ -60,8 +85,8 @@ def build_package(target_triple=None, target_arch=None):
f"Keine kompilierte Binary für {name} gefunden. Gesuchte Pfade: {candidate_paths}"
)
depends = arch_meta.get("depends", ["gcc-libs", "glibc", "sudo", "docker-ce-cli", "docker-compose-plugin"])
optdepends = arch_meta.get("optdepends", ["docker-compose: Unterstützung für Docker Compose v1"])
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")
@@ -111,9 +136,10 @@ 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)
build_package(target_triple=args.target, target_arch=args.arch, pkgrel=args.pkgrel)
if __name__ == "__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()
+7 -71
View File
@@ -1,31 +1,20 @@
//! Modul für die Konfigurationsverwaltung der Anwendung.
//!
//! Dieses Modul stellt die Funktionalität zum Laden, Speichern und Verwalten
//! der Anwendungskonfiguration bereit. Die Konfiguration wird in einer Datei gespeichert
//! und beim Programmstart automatisch geladen.
//! Dieses Modul stellt die Datenstrukturen für die Anwendungskonfiguration bereit.
//! Das Laden, Speichern und Cachen erfolgt über das Crate `config-ctdra`.
use crate::program;
use crate::sudo::is_run_as_root;
use config_ctdra::get_config as get_cached_config;
use serde::{Deserialize, Serialize};
use std::sync::OnceLock;
/// Hauptkonfigurationsstruktur der Anwendung.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
pub struct AppConfig {
/// Allgemeine Einstellungen
pub general: General,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
general: General::default(),
}
}
}
/// Allgemeine Konfigurationseinstellungen.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct General {
/// Log-Level für die Anwendung (error, warn, info, debug)
pub log_level: String,
@@ -42,60 +31,7 @@ impl Default for General {
}
}
static CONFIG_NAME: &str = "config";
static CONFIG: OnceLock<AppConfig> = OnceLock::new();
/// Gibt die aktuelle Konfiguration zurück.
///
/// Lädt die Konfiguration beim ersten Aufruf und speichert sie zwischen.
/// Nachfolgende Aufrufe geben die gespeicherte Konfiguration zurück.
/// Gibt die globale Konfiguration zurück.
pub fn get_config() -> &'static AppConfig {
CONFIG.get_or_init(|| {
load_config()
})
get_cached_config::<AppConfig>()
}
/// Modifiziert die aktuelle Konfiguration mit der übergebenen Mutator-Funktion.
///
/// Die Funktion lädt die Konfiguration neu von der Festplatte, wendet die Mutator-Funktion an
/// und speichert die geänderte Konfiguration anschließend wieder.
#[allow(dead_code)]
pub fn modify_config<F>(mutator: F)
where
F: FnOnce(&mut AppConfig),
{
// Immer frisch von Disk laden, damit Änderungen konsistent sind
let mut cfg = load_config();
mutator(&mut cfg);
save_config(cfg);
}
/// Lädt die Konfiguration aus der Konfigurationsdatei.
fn load_config() -> AppConfig {
if is_run_as_root() {
return confy::load_path(format!("/etc/{}/{}.toml", program::program_name(), CONFIG_NAME)).unwrap_or_default();
}
confy::load(&*program::program_name(), CONFIG_NAME).unwrap_or_default()
}
/// Speichert die übergebene Konfiguration in der Konfigurationsdatei.
#[allow(dead_code)]
fn save_config(config: AppConfig) {
if is_run_as_root() {
let _ = confy::store_path(format!("/etc/{}/{}.toml", program::program_name(), CONFIG_NAME), config);
return;
}
let _ = confy::store(&*program::program_name(), CONFIG_NAME, config);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = AppConfig::default();
assert_eq!(config.general.apps_dir, "/var/apps");
assert_eq!(config.general.log_level, "info");
}
}
+4
View File
@@ -0,0 +1,4 @@
//! DockerUpdate Bibliotheksexporte
pub mod config;
pub mod updater;
-254
View File
@@ -1,254 +0,0 @@
//! Einfaches, threadsicheres Logging-Modul.
//!
//! Merkmale:
//! - Ausgabe auf Standard-Streams (stdout/stderr) und zusätzlich in eine Logdatei im temporären Verzeichnis.
//! - Nachrichtenvorlage: `[DD.MM.YYYY HH:MM:SS.mmm][LEVEL][TAG]: <Text>` mit Präfix ([!]/[?]/[i]/[d]).
//! - Der aktuell verwendete Schweregradfilter ist statisch (`LOG_LEVEL`) und wird zur Laufzeit nicht geändert.
//! - Dateihandle wird lazily initialisiert und zwischengespeichert (`OnceLock`).
//!
//! Hinweise:
//! - Bei einem leeren `tag` wird ein leerer Tag-Abschnitt erzeugt.
//! - Die Logdatei wird unterhalb eines prozessspezifischen Ordners im System-Temp-Verzeichnis abgelegt und nach Datum
//! benannt (z. B. `log-2025-08-18.log`). Es wird im Append-Modus geschrieben.
//!
//! Beispiel (ohne Ausführung in Doctests):
//! ```rust,no_run
//! use crate::log::{log, LogLevel};
//!
//! log("startup", "Dienst wird initialisiert...", LogLevel::Info);
//! log("db", "Verbindung unterbrochen!", LogLevel::Warn);
//! log("core", "Unerwarteter Fehler", LogLevel::Error);
//! ```
use std::env;
use std::fmt::{Display, Formatter};
use std::fs::{File, OpenOptions, create_dir_all};
use std::io::Write;
use std::sync::{Mutex, OnceLock};
use crate::config;
use crate::program;
use time::{OffsetDateTime, macros::format_description};
/// Schweregrade für Logeinträge in aufsteigender Detailtiefe.
///
/// Die Reihenfolge bestimmt den Filter: Nur Einträge mit `log_level <= LOG_LEVEL` werden ausgegeben.
///
/// Anzeige (Display):
/// - `Error` -> `ERROR`
/// - `Warn` -> `WARN`
/// - `Info` -> `INFO`
/// - `Debug` -> `DEBUG`
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum LogLevel {
/// Kritische Fehler, gehen zusätzlich auf `stderr`.
Error = 1,
/// Wichtige Warnungen über potenzielle Probleme.
Warn = 2,
/// Allgemeine Betriebsinformationen.
Info = 3,
/// Ausführliche Diagnoseausgaben für die Entwicklung.
Debug = 4,
}
impl Display for LogLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LogLevel::Error => write!(f, "ERROR"),
LogLevel::Warn => write!(f, "WARN"),
LogLevel::Info => write!(f, "INFO"),
LogLevel::Debug => write!(f, "DEBUG"),
}
}
}
impl TryFrom<String> for LogLevel {
type Error = LogLevel;
fn try_from(value: String) -> Result<Self, Self> {
match value.to_lowercase().as_str() {
"error" => Ok(LogLevel::Error),
"warn" => Ok(LogLevel::Warn),
"info" => Ok(LogLevel::Info),
"debug" => Ok(LogLevel::Debug),
_ => Err(LogLevel::Info),
}
}
}
/// Globaler Schweregradfilter für Ausgabe.
static LOG_LEVEL: OnceLock<LogLevel> = OnceLock::new();
/// Lazy-initialisiertes Handle zur Logdatei; kann `None` sein, falls das Öffnen fehlschlug.
static LOG_FILE: OnceLock<Mutex<Option<File>>> = OnceLock::new();
/// Protokolliert eine Nachricht abhängig vom angegebenen Schweregrad.
///
/// Verhalten:
/// - Wenn `log_level` größer als der globale Filter ist, wird nichts ausgegeben.
/// - `Error`-Meldungen gehen nach `stderr`, alle anderen nach `stdout`.
/// - Zusätzlich wird in eine tägliche Logdatei im Temp-Verzeichnis geschrieben (wenn erfolgreich geöffnet).
///
/// Parameter:
/// - `tag`: Kurzer Kontext (z. B. Modulname).
/// - `message`: Die eigentliche Nachricht (eine Zeile).
/// - `log_level`: Schweregrad der Nachricht.
///
/// Thread-Sicherheit:
/// - Dateischreibzugriffe sind über `Mutex` serialisiert.
///
/// Beispiel:
/// ```rust,no_run
/// # use crate::log::{log, LogLevel};
/// log("http", "Server gestartet auf Port 8080", LogLevel::Info);
/// ```
pub fn log(tag: &str, message: &str, log_level: LogLevel) {
if log_level <= get_log_level() {
let message: String = format_message(tag, message, &log_level);
if log_level == LogLevel::Error {
eprintln!("{}", message);
} else {
println!("{}", message);
}
let file_lock = get_or_init_log_file();
if let Ok(mut guard) = file_lock.lock() {
if let Some(f) = guard.as_mut() {
let _ = writeln!(f, "{}", message);
let _ = f.flush();
}
}
}
}
/// Liefert das einmalig initialisierte Log-Level für die Filterung.
///
/// Ermittelt das konfigurierte Log-Level aus der Config-Datei.
/// Wenn ungültig oder nicht vorhanden, wird LogLevel::Info als Fallback verwendet.
///
/// Rückgabe:
/// - `LogLevel`: Das zu verwendende Log-Level als enum-Wert.
fn get_log_level() -> LogLevel {
*LOG_LEVEL.get_or_init(|| {
let log_level = &config::get_config().general.log_level;
LogLevel::try_from(log_level.to_string()).unwrap_or(LogLevel::Info)
})
}
/// Formatiert eine Lognachricht mit Zeitstempel, Level, Tag und Präfix.
///
/// Format:
/// - Datum/Zeit lokal (Fallback: UTC) im Format `DD.MM.YYYY HH:MM:SS.mmm`.
/// - Level als Text (`ERROR`, `WARN`, `INFO`, `DEBUG`).
/// - Tag in eckigen Klammern; bei leerem Tag entsteht ein leeres `[]`.
/// - Präfix pro Level: `[!]` (Error), `[?]` (Warn), `[i]` (Info), `[d]` (Debug).
///
/// Beispielausgabe:
/// - `[i][18.08.2025 14:23:45.012][INFO][init]: Fertig`
///
/// Hinweis:
/// - Diese Funktion formatiert nur; sie führt keinen I/O aus.
fn format_message(tag: &str, message: &str, log_level: &LogLevel) -> String {
let mut prefix: String = String::new();
let now_local: OffsetDateTime =
OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc());
let fmt =
format_description!("[day].[month].[year] [hour]:[minute]:[second].[subsecond digits:3]");
if tag != "" {
prefix = format!("[{}]", tag);
}
let message = format!(
"[{}][{}]{}: {}",
now_local.format(fmt).unwrap(),
log_level.to_string(),
prefix,
message
);
match log_level {
&LogLevel::Error => format!("[!]{}", message),
&LogLevel::Warn => format!("[?]{}", message),
&LogLevel::Info => format!("[i]{}", message),
&LogLevel::Debug => format!("[d]{}", message),
}
}
/// Initialisiert das Logdatei-Handle beim ersten Aufruf und liefert eine Referenz darauf.
///
/// Rückgabe:
/// - `&'static Mutex<Option<File>>`: Das Mutex schützt den optionalen Dateihandler.
/// `None` bedeutet, dass das Öffnen fehlgeschlagen ist (z. B. fehlende Rechte).
fn get_or_init_log_file() -> &'static Mutex<Option<File>> {
LOG_FILE.get_or_init(|| {
let file = open_log_file().ok();
Mutex::new(file)
})
}
/// Öffnet (und erstellt bei Bedarf) die tagesbasierte Logdatei im Temp-Verzeichnis.
///
/// Pfadaufbau:
/// - Basis: `std::env::temp_dir()`
/// - Unterordner: `<programmname>-<konstante-uuid>`
/// - Datei: `log-YYYY-MM-DD.log` im Append-Modus
///
/// Rückgabe:
/// - `Ok(File)`, wenn der Ordner erstellt/gefunden und die Datei geöffnet/angelegt werden konnte.
/// - `Err(std::io::Error)`, wenn ein I/O-Fehler auftrat.
///
/// Fehler:
/// - Gibt I/O-Fehler unverändert weiter (z. B. beim Erstellen des Ordners oder Öffnen der Datei).
fn open_log_file() -> std::io::Result<File> {
let program = program::program_name();
let rand = "13692bbf-a93b-43e9-9cc6-f05f94a8cfb6";
let mut dir = env::temp_dir();
dir.push(format!("{}-{}", program, rand));
create_dir_all(&dir)?;
let today_fmt = format_description!("[year]-[month]-[day]");
let date_str = OffsetDateTime::now_local()
.unwrap_or(OffsetDateTime::now_utc())
.format(today_fmt)
.unwrap_or_else(|_| "0000-00-00".to_string());
let file_name = format!("log-{}.log", date_str);
dir.push(file_name);
OpenOptions::new().create(true).append(true).open(dir)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_message_prefixes() {
let msg_info = format_message("test", "Info message", &LogLevel::Info);
assert!(msg_info.starts_with("[i]"));
assert!(msg_info.contains("[INFO][test]: Info message"));
let msg_err = format_message("test", "Error message", &LogLevel::Error);
assert!(msg_err.starts_with("[!]"));
assert!(msg_err.contains("[ERROR][test]: Error message"));
let msg_warn = format_message("test", "Warn message", &LogLevel::Warn);
assert!(msg_warn.starts_with("[?]"));
assert!(msg_warn.contains("[WARN][test]: Warn message"));
let msg_debug = format_message("test", "Debug message", &LogLevel::Debug);
assert!(msg_debug.starts_with("[d]"));
assert!(msg_debug.contains("[DEBUG][test]: Debug message"));
}
#[test]
fn test_format_message_empty_tag() {
let msg = format_message("", "No tag message", &LogLevel::Info);
assert!(msg.starts_with("[i]"));
assert!(msg.contains("[INFO]: No tag message"));
}
}
+24 -12
View File
@@ -1,19 +1,31 @@
mod program;
mod sudo;
mod log;
mod config;
mod updater;
use crate::log::{log, LogLevel};
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 !sudo::is_run_as_root() {
log("auth", "Root-Rechte erforderlich. Starte mit sudo neu...", LogLevel::Info);
sudo::run_as_root();
return;
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);
}
}
-14
View File
@@ -1,14 +0,0 @@
use std::env;
/// Liefert den Programmnamen (Dateistamm der aktuellen ausführbaren Datei).
///
/// Rückgabe:
/// - Dateistamm der aktuellen Executable als `String`.
/// - Fallback `"app"`, wenn der Name nicht ermittelt werden kann.
pub(crate) fn program_name() -> String {
env::current_exe()
.ok()
.and_then(|p| p.file_stem().map(|s| s.to_string_lossy().to_string()))
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "app".to_string())
}
-28
View File
@@ -1,28 +0,0 @@
use crate::log::{LogLevel, log};
use std::env;
use std::os::unix::process::CommandExt;
use std::process::{Command, exit};
/// Prüft, ob das Programm mit Root-/Administrator-Rechten ausgeführt wird.
///
/// # Returns
///
/// * `true` - Das Programm läuft mit erhöhten Rechten
/// * `false` - Das Programm läuft mit normalen Benutzerrechten
pub fn is_run_as_root() -> bool {
unsafe { libc::geteuid() == 0 }
}
/// Startet das Programm mit Root-Rechten über `sudo` neu.
///
/// Diese Funktion versucht das Programm mit erhöhten Rechten neu zu starten.
pub fn run_as_root() {
let commandline_args: Vec<String> = env::args().collect();
let err = Command::new("sudo").args(&commandline_args).exec();
log(
"auth",
&format!("Fehler beim Ausführen von 'sudo': {}", err),
LogLevel::Error,
);
exit(1);
}
+6 -79
View File
@@ -1,5 +1,5 @@
use crate::config;
use crate::log::{LogLevel, log};
use logger_ctdra::{LogLevel, log};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
@@ -66,7 +66,7 @@ pub fn parse_compose_services(compose_path: &Path) -> Result<Vec<(String, String
)
})?;
let compose: ComposeFile = serde_yaml::from_str(&content).map_err(|e| {
let compose: ComposeFile = serde_saphyr::from_str(&content).map_err(|e| {
format!(
"Fehler beim Parsen der YAML-Datei '{}': {}",
compose_path.display(),
@@ -111,14 +111,13 @@ pub fn run_compose_command(dir: &Path, args: &[&str]) -> std::io::Result<Output>
|| stderr.contains("unknown command \"compose\"")
|| stderr.contains("unknown command: compose"));
if is_unavailable {
if let Ok(fallback_out) = Command::new("docker-compose")
if is_unavailable
&& let Ok(fallback_out) = Command::new("docker-compose")
.current_dir(dir)
.args(args)
.output()
{
return Ok(fallback_out);
}
{
return Ok(fallback_out);
}
Ok(out)
}
@@ -411,75 +410,3 @@ pub fn run_updates() {
LogLevel::Info,
);
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::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);
}
}
+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);
}