41 Commits
Author SHA1 Message Date
DragonSlayer_14 9f774ac4e8 Merge pull request 'Merge dev in testing: Aktualisiert dependencies und Workflows' (#17) from dev into testing
Unit-Tests / Unit-Tests (pull_request) Skipped
Auto-PR (Testing → Main) / Erstelle automatisch PR von testing nach main (push) Successful in 20s
Security Scans / Trivy & OSV-Scanner (push) Successful in 57s
Testing Build, Check & Preview Release / Erkenne relevante Code-Änderungen (push) Successful in 15s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 28s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 50s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 1m24s
TruffleHog Secret Scan / TruffleHog (push) Successful in 14s
Reviewed-on: #17
2026-09-13 12:40:41 +00:00
Gitea-Bot d85826757b Chore: Erhöht Patch-Version auf 1.0.6 für Promotion nach testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 28s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 54s
Security Scans / Trivy & OSV-Scanner (push) Successful in 53s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 1m0s
TruffleHog Secret Scan / TruffleHog (push) Successful in 23s
Unit-Tests / Unit-Tests (pull_request) Successful in 28s
2026-09-13 12:38:11 +00:00
DragonSlayer_14 6937d3142e Merge branch 'dev' of origin into dev
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 26s
Security Scans / Trivy & OSV-Scanner (push) Successful in 49s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 55s
Auto Patch-Version-Bump (Dev → Testing PR) / Erkenne relevante Änderungen im PR (pull_request) Successful in 12s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 29s
Auto Patch-Version-Bump (Dev → Testing PR) / Patch-Version erhöhen & auf Dev pushen (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 50s
Unit-Tests / Unit-Tests (pull_request) Successful in 1m19s
2026-09-13 14:28:04 +02:00
DragonSlayer_14andClaude Sonnet 5 bd3f05d09e Feat: Fügt automatischen PR-Workflow für Testing→Main hinzu
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_14 9e3a568b22 Merge pull request 'chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.83.0' (#16) from renovate/gitea-actions into dev
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 35s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 43s
Reviewed-on: #16
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-13 12:15:43 +00:00
DragonSlayer_14 bb3d5dc212 Merge pull request 'chore(deps): update cargo dependencies to v1.0.1' (#15) from renovate/cargo-dependencies into dev
Security Scans / Trivy & OSV-Scanner (push) Successful in 26s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 27s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 14s
Reviewed-on: #15
Reviewed-by: dragonslayer14@murena.io <4+dragonslayer_14@noreply.localhost>
2026-09-13 12:14:16 +00:00
DragonSlayer_14andClaude Sonnet 5 3871ffa3b0 Feat: Fügt automatischen Patch-Version-Bump für Dev→Testing-PRs hinzu
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 56s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
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
Renovate-Bot 6ec1eda53d chore(deps): update ghcr.io/renovatebot/renovate docker tag to v44.83.0
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 27s
TruffleHog Secret Scan / TruffleHog (push) Successful in 8s
Unit-Tests / Unit-Tests (pull_request) Skipped
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 53s
2026-09-13 12:02:22 +00:00
Renovate-Bot e1f8d70a61 chore(deps): update cargo dependencies to v1.0.1
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
Unit-Tests / Unit-Tests (pull_request) Skipped
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 10s
TruffleHog Secret Scan / TruffleHog (push) Successful in 11s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 20s
2026-09-13 12:02:18 +00:00
DragonSlayer_14andClaude Sonnet 5 7821364733 Feat: Überspringt Release/Publish bei bereits veröffentlichter Version
TruffleHog Secret Scan / TruffleHog (push) Successful in 24s
Security Scans / Trivy & OSV-Scanner (push) Successful in 51s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 55s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
Prüft vor Build & Tests per Gitea Package-API, ob die aktuelle Crate-Version
schon in der Registry existiert, und bricht den Release/Publish-Job dann
gar nicht erst an, statt an einem Registry-Fehler zu scheitern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K2DxAVavuu4vMb2MC2G15g
2026-09-13 12:34:09 +02:00
DragonSlayer_14 1add336e12 Merge pull request 'Merge dev in testing: Passt Workflows an' (#13) from dev into testing
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 57s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 1m24s
Unit-Tests / Unit-Tests (pull_request) Skipped
Testing Build, Check & Preview Release / Erkenne relevante Code-Änderungen (push) Successful in 14s
TruffleHog Secret Scan / TruffleHog (push) Successful in 29s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 28s
Security Scans / Trivy & OSV-Scanner (push) Successful in 51s
Reviewed-on: #13
2026-09-13 10:15:32 +00:00
DragonSlayer_14andClaude Sonnet 5 6e7fcf459f Feat: Baut Pakete nur bei tatsächlichen Code-Änderungen
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 58s
TruffleHog Secret Scan / TruffleHog (push) Successful in 29s
Security Scans / Trivy & OSV-Scanner (push) Successful in 1m17s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 27s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 39s
Unit-Tests / Unit-Tests (pull_request) Successful in 48s
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 9d22a77d0e Fix: Verschiebt allowCustomCrateRegistries in die globale Renovate-Config
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Skipped
TruffleHog Secret Scan / TruffleHog (push) Successful in 27s
Security Scans / Trivy & OSV-Scanner (push) Successful in 54s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 59s
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:18:15 +02:00
DragonSlayer_14 45d571c487 Merge pull request 'Merge dev in testing: Feat: Fügt Security-Scan-Skripte, Renovate-Konfiguration und CI/CD-Workflows hinzu; Version auf 1.0.5 erhöht' (#10) from dev into testing
Unit-Tests / Unit-Tests (pull_request) Skipped
Security Scans / Trivy & OSV-Scanner (push) Successful in 35s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 19s
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 56s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 35s
TruffleHog Secret Scan / TruffleHog (push) Successful in 29s
Reviewed-on: #10
2026-09-12 22:57:20 +00:00
Gitea-Bot 6a48840ec0 Style: Automatische Formatierung & Clippy-Fixes
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Security Scans / Trivy & OSV-Scanner (push) Successful in 26s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 28s
TruffleHog Secret Scan / TruffleHog (pull_request) Successful in 16s
Security Scans / Trivy & OSV-Scanner (pull_request) Successful in 32s
Unit-Tests / Unit-Tests (pull_request) Successful in 55s
2026-09-12 22:33:04 +00:00
Gitea-Bot 29e41e8ea2 Style: Automatische Formatierung & Clippy-Fixes
TruffleHog Secret Scan / TruffleHog (push) Successful in 16s
Security Scans / Trivy & OSV-Scanner (push) Successful in 27s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 29s
2026-09-12 22:32:35 +00:00
DragonSlayer_14 4edf7e8a46 Feat: Fügt Security-Scan-Skripte, Renovate-Konfiguration und CI/CD-Workflows hinzu; Version auf 1.0.5 erhöht
TruffleHog Secret Scan / TruffleHog (push) Successful in 18s
Security Scans / Trivy & OSV-Scanner (push) Successful in 40s
Code Quality (Auto-Format & Clippy-Fix) / Formatierung & Clippy automatisch beheben (push) Successful in 44s
2026-09-13 00:31:49 +02:00
DragonSlayer_14 4972318aff Merge pull request 'Merge dev in testing: Ermittelt Programmnamen automatisch aus der program-ctdra-Crate' (#8) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 32s
Reviewed-on: #8
2026-08-26 18:24:54 +00:00
DragonSlayer_14 f029efe315 Feat: Hebt Version auf 1.0.4 an 2026-08-26 20:24:46 +02:00
DragonSlayer_14 dcf2efbf98 Merge remote-tracking branch 'origin/main' into dev 2026-08-26 20:23:16 +02:00
DragonSlayer_14 2e7d2f98fd Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 20:23:09 +02:00
DragonSlayer_14 469e699074 Feat: Ermittelt den Programmnamen automatisch durch die program-ctdra-Crate und fügt Unit-Tests hinzu. 2026-08-26 20:23:05 +02:00
DragonSlayer_14 d87ece69da Merge pull request 'Merge testing in main: Release 1.0.3' (#7) from testing into main
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 29s
Reviewed-on: #7
2026-08-26 17:23:48 +00:00
DragonSlayer_14 f73ba4b1d6 Merge pull request 'Merge dev in testing: Feat: Fügt config.toml hinzu, welche die gitea-Registry konfiguriert.' (#6) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 31s
Reviewed-on: #6
2026-08-26 17:22:45 +00:00
DragonSlayer_14 d7845349e8 Merge branch 'testing' into dev 2026-08-26 17:22:37 +00:00
DragonSlayer_14 7bea78f194 Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 19:21:56 +02:00
DragonSlayer_14 87c1a1c09c Feat: Fügt config.toml hinzu, welche die gitea-Registry konfiguriert. 2026-08-26 19:21:52 +02:00
DragonSlayer_14 d781f3f97b Merge pull request 'Merge dev in testing: Feat: Ersetzt is_run_as_root durch die Implementation von Crate sudo_ctdra' (#5) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Failing after 15s
Reviewed-on: #5
2026-08-26 17:15:41 +00:00
DragonSlayer_14 a4f27ce091 Merge branch 'testing' into dev 2026-08-26 17:15:32 +00:00
DragonSlayer_14 61b016775e Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 19:14:54 +02:00
DragonSlayer_14 439ccd60d4 Feat: Hebt Version auf 1.0.3 2026-08-26 19:14:49 +02:00
DragonSlayer_14 1c99387d6f Feat: Ersetzt is_run_as_root durch die Implementation von Crate sudo_ctdra 2026-08-26 19:14:30 +02:00
DragonSlayer_14 bf32d82ae7 Feat: Ersetzt is_run_as_root durch die Implementation von Crate sudo_ctdra 2026-08-26 19:13:36 +02:00
DragonSlayer_14 34f61bba5f Merge remote-tracking branch 'origin/dev' into dev 2026-08-26 18:53:20 +02:00
DragonSlayer_14 d351a8b6be Fix: Passt LICENSE an 2026-08-26 18:53:16 +02:00
DragonSlayer_14 a18eb13cbf Merge pull request 'Merge testing in main: Release 1.0.2' (#4) from testing into main
Main Release & Publish / Build, Publish Crate to Gitea Registry & Create Release (push) Successful in 30s
Reviewed-on: #4
2026-08-26 16:43:21 +00:00
DragonSlayer_14 dd7c2d2390 Merge pull request 'Merge dev in testing: Passt Paketnamen auf config-ctdra ab an, um Verwechslungen zu vermeiden' (#3) from dev into testing
Testing Build, Check & Preview Release / Build, Check & Create Preview Release (push) Successful in 28s
Reviewed-on: #3
2026-08-26 16:41:57 +00:00
DragonSlayer_14 107b18b461 Merge branch 'testing' into dev 2026-08-26 16:41:48 +00:00
DragonSlayer_14 506bf90cce Feat: Ändert Versionsnummer auf 1.0.2 2026-08-26 18:37:31 +02:00
DragonSlayer_14 6d784b074b Fix: Benennt auch die anderen Vorkomnisse des Crate-Namens um 2026-08-26 18:34:51 +02:00
DragonSlayer_14 8e1401439a Feat: Benennt Crate um, um Verwechslungen zu vermeiden. 2026-08-26 18:22:26 +02:00
24 changed files with 1611 additions and 281 deletions
+9
View File
@@ -0,0 +1,9 @@
[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
+54
View File
@@ -0,0 +1,54 @@
name: Code Quality (Auto-Format & Clippy-Fix)
on:
push:
branches:
- dev
workflow_dispatch:
jobs:
fix:
name: Formatierung & Clippy automatisch beheben
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
ref: ${{ gitea.ref_name || github.ref_name }}
token: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
components: clippy, rustfmt
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Formatierung automatisch beheben
run: cargo fmt
- name: Clippy-Fixes automatisch anwenden
run: cargo clippy --fix --allow-dirty --allow-staged --all-targets
- name: Änderungen committen & pushen
run: |
if [ -n "$(git status --porcelain)" ]; then
git config user.name "Gitea-Bot"
git config user.email "no-reply@creativedragonslayer.de"
git add -A
git commit -m "Style: Automatische Formatierung & Clippy-Fixes"
git push origin HEAD:${{ gitea.ref_name || github.ref_name }}
else
echo "Keine Formatierungs- oder Clippy-Änderungen."
fi
+49 -2
View File
@@ -6,19 +6,65 @@ on:
- main
jobs:
detect-changes:
name: Erkenne relevante Code-Änderungen
runs-on: ubuntu-latest
outputs:
code_changed: ${{ steps.filter.outputs.code }}
version_exists: ${{ steps.check-version.outputs.exists }}
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'
- name: Prüfe ob Version bereits in der Registry existiert
id: check-version
if: steps.filter.outputs.code == 'true'
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO_OWNER: ${{ gitea.repository_owner || github.repository_owner }}
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
run: |
python3 scripts/check-version-published.py
release-and-publish:
name: Build, Publish Crate to Gitea Registry & Create Release
needs: detect-changes
if: needs.detect-changes.outputs.code_changed == 'true' && needs.detect-changes.outputs.version_exists != 'true'
runs-on: ubuntu-latest
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: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
@@ -58,12 +104,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 config Crate ${VERSION}."
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} Crate ${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.83.0
steps:
- name: Renovate ausführen
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ gitea.server_url || github.server_url }}/api/v1/
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
RENOVATE_AUTODISCOVER: "false"
RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES: "true"
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creativedragonslayer.de>"
RENOVATE_HOST_RULES: >-
[{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}]
GITHUB_COM_TOKEN: ${{ secrets.GH_RENOVATE_TOKEN }}
LOG_LEVEL: info
+95
View File
@@ -0,0 +1,95 @@
name: Security Scans
on:
push:
branches:
- main
- testing
- dev
pull_request:
schedule:
- cron: "0 5 * * 1"
workflow_dispatch:
jobs:
security-scan:
name: Trivy & OSV-Scanner
runs-on: ubuntu-latest
env:
TRIVY_VERSION: "0.74.0"
OSV_SCANNER_VERSION: "2.5.1"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache Trivy-Binary
id: cache-trivy
uses: actions/cache@v6
with:
path: ~/.local/bin/trivy
key: trivy-bin-${{ runner.os }}-${{ env.TRIVY_VERSION }}
- name: Install Trivy
if: steps.cache-trivy.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trivy.tar.gz \
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
tar -xzf trivy.tar.gz trivy
chmod +x trivy
mv trivy "$HOME/.local/bin/trivy"
rm -f trivy.tar.gz
- name: Ermittle Cache-Datum für Trivy-DB
run: echo "CACHE_DATE=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV"
- name: Cache Trivy-Schwachstellen-Datenbank
uses: actions/cache@v6
with:
path: ~/.cache/trivy
key: trivy-db-${{ runner.os }}-${{ env.CACHE_DATE }}
restore-keys: |
trivy-db-${{ runner.os }}-
- name: Run Trivy Scanner
run: |
trivy fs \
--scanners vuln,secret,misconfig \
--severity CRITICAL,HIGH \
--format json \
--output trivy-results.json \
--exit-code 0 \
.
- name: Cache OSV-Scanner-Binary
id: cache-osv-scanner
uses: actions/cache@v6
with:
path: ~/.local/bin/osv-scanner
key: osv-scanner-bin-${{ runner.os }}-${{ env.OSV_SCANNER_VERSION }}
- name: Install OSV-Scanner
if: steps.cache-osv-scanner.outputs.cache-hit != 'true'
run: |
curl -fsSL -o "$HOME/.local/bin/osv-scanner" \
"https://github.com/google/osv-scanner/releases/download/v${OSV_SCANNER_VERSION}/osv-scanner_linux_amd64"
chmod +x "$HOME/.local/bin/osv-scanner"
- name: Run OSV-Scanner
run: |
set +e
osv-scanner scan source --recursive --format json --output-file osv-results.json .
echo "OSV_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
run: |
python3 scripts/report-security-issue.py trivy-results.json osv-results.json
+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}"
+38 -2
View File
@@ -6,19 +6,54 @@ 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-preview:
name: Build, Check & Create Preview Release
needs: detect-changes
if: needs.detect-changes.outputs.code_changed == 'true'
runs-on: ubuntu-latest
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: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: |
cargo test
@@ -35,12 +70,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 config Crate ${VERSION} (Branch: Testing)."
RELEASE_NOTES="Automatisches Preview-Release für ${REPO_NAME} Crate ${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.4"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Lokales bin-Verzeichnis zum PATH hinzufügen
run: |
mkdir -p "$HOME/.local/bin"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache TruffleHog-Binary
id: cache-trufflehog
uses: actions/cache@v6
with:
path: ~/.local/bin/trufflehog
key: trufflehog-bin-${{ runner.os }}-${{ env.TRUFFLEHOG_VERSION }}
- name: Install TruffleHog
if: steps.cache-trufflehog.outputs.cache-hit != 'true'
run: |
curl -fsSL -o trufflehog.tar.gz \
"https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz"
tar -xzf trufflehog.tar.gz trufflehog
chmod +x trufflehog
mv trufflehog "$HOME/.local/bin/trufflehog"
rm trufflehog.tar.gz
- name: Run TruffleHog Scanner
run: |
set +e
trufflehog git file://. --results=verified,unknown --fail --json > trufflehog-results.json
echo "TRUFFLEHOG_EXIT=$?" >> "$GITHUB_ENV"
- name: Ergebnisse & Gitea-Issue erstellen/aktualisieren
env:
GITEA_URL: ${{ gitea.server_url || github.server_url }}
REPO: ${{ gitea.repository || github.repository }}
TOKEN: ${{ secrets.SECURITY_TOKEN }}
RUN_URL: ${{ gitea.server_url || github.server_url }}/${{ gitea.repository || github.repository }}/actions/runs/${{ gitea.run_id || github.run_id }}
ISSUE_TITLE: "Security-Scan: TruffleHog Secrets"
ISSUE_LABEL: "security-scan-trufflehog"
run: |
python3 scripts/report-security-issue.py "" "" trufflehog-results.json
+38
View File
@@ -0,0 +1,38 @@
name: Unit-Tests
on:
pull_request:
types:
- opened
- synchronize
- reopened
branches:
- testing
jobs:
test:
name: Unit-Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Rust Toolchain
uses: actions-rust-lang/setup-rust-toolchain@v2
with:
toolchain: stable
cache: false
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Run Tests
run: cargo test
+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 }}
+2
View File
@@ -108,3 +108,5 @@ 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" />
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="GitHub Workflow">
<value>
<SchemaInfo>
<option name="name" value="GitHub Workflow" />
<option name="relativePathToSchema" value="https://www.schemastore.org/github-workflow.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value=".gitea/workflows/code-quality.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/main.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/renovate.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/security-scan.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/testing.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/trufflehog-scan.yaml" />
</Item>
<Item>
<option name="path" value=".gitea/workflows/unit-tests.yaml" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>
+7 -5
View File
@@ -1,12 +1,12 @@
# AGENTS.md — Entwickler- und Agenten-Dokumentation für Config
# AGENTS.md — Entwickler- und Agenten-Dokumentation für Config (config-ctdra)
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `config` arbeiten.
Diese Datei dient als Leitfaden und Kontextdokumentation für autonome Agenten und Entwickler, die an der Codebasis der Crate `config-ctdra` arbeiten.
---
## 1. Projektübersicht
`config` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024) zur Konfigurationsverwaltung. Sie ermöglicht das Speichern, Laden und Modifizieren beliebiger Konfigurationsstrukturen mit automatischer Pfadermittlung für Root- und regulären Benutzermodus.
`config-ctdra` ist eine leichtgewichtige, threadsichere Rust-Crate (Edition 2024) zur Konfigurationsverwaltung. Sie ermöglicht das Speichern, Laden und Modifizieren beliebiger Konfigurationsstrukturen mit automatischer Pfadermittlung für Root- und regulären Benutzermodus.
### Wichtige Rahmenbedingungen & Prinzipien
@@ -31,8 +31,10 @@ Config/
├── LICENSE # GPL-3.0 Lizenztext
├── README.md # Projektdokumentation & Nutzungsbeispiele
├── AGENTS.md # Entwickler- und Agenten-Richtlinien
── src/
└── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung, Tests)
── src/
└── lib.rs # Hauptimplementierung (load, store, modify, get_config, Pfadauflösung)
└── tests/
└── integration_tests.rs # Vollständige Integrationstests
```
---
Generated
+19 -3
View File
@@ -9,12 +9,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "config"
version = "1.0.1"
name = "config-ctdra"
version = "1.0.6"
dependencies = [
"confy",
"libc",
"program-ctdra",
"serde",
"sudo-ctdra",
]
[[package]]
@@ -93,6 +94,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "program-ctdra"
version = "1.0.1"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "528d5771916f74bdffb77ad60ea00c0ae4bd85f9a84e920cd65730a082133d67"
[[package]]
name = "quote"
version = "1.0.47"
@@ -141,6 +148,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "sudo-ctdra"
version = "1.0.1"
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
checksum = "20e576be60eb2050d475d0fbe46f0d09462ba9985ab55c7833e0c2e6d116d341"
dependencies = [
"libc",
]
[[package]]
name = "syn"
version = "3.0.4"
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "config"
version = "1.0.1"
name = "config-ctdra"
version = "1.0.6"
edition = "2024"
authors = ['DragonSlayer_14']
readme = "README.md"
@@ -10,8 +10,9 @@ description = "Einfache, threadsichere Konfigurationsverwaltung für Rust mit au
[dependencies]
confy = "2.0.0"
program-ctdra = { version = "1.0.0", registry = "gitea" }
serde = { version = "1.0.229", features = ["derive"] }
libc = "1.0.0-alpha.4"
sudo-ctdra = { version = "1.0.0", registry = "gitea" }
[profile.release]
debug = "none"
+3 -3
View File
@@ -208,8 +208,8 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
DockerUpdater
Copyright (C) 2026 Linuxapps
config
Copyright (C) 2026 Rust-Crates
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
DockerUpdater Copyright (C) 2026 Linuxapps
config Copyright (C) 2026 Rust-Crates
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
+10 -12
View File
@@ -1,6 +1,6 @@
# Config (`config`)
# Config (`config-ctdra`)
`config` ist eine leichtgewichtige, threadsichere Konfigurationsverwaltungs-Crate für Rust mit automatischer Pfadermittlung für Root- und regulären Benutzermodus sowie Unterstützung für beliebige benutzerdefinierte Datenstrukturen.
`config-ctdra` ist eine leichtgewichtige, threadsichere Konfigurationsverwaltungs-Crate für Rust mit automatischer Pfadermittlung für Root- und regulären Benutzermodus sowie Unterstützung für beliebige benutzerdefinierte Datenstrukturen.
## Merkmale
@@ -8,7 +8,7 @@
- **Einheitliche Pfadauflösung**:
- **Root-Modus** (Linux/Unix UID 0): Konfigurationsdateien werden unter `/etc/<programmname>/<config_name>.toml` abgelegt.
- **Benutzermodus**: Standardmäßiges Benutzerverzeichnis (z. B. `~/.config/<programmname>/<config_name>.toml` via `confy`).
- **Individuell anpassbar**: Programmnamen (`set_program_name`), Dateinamen (`set_config_name`), Verzeichnisse (`set_custom_dir`) oder explizite Dateipfade (`set_custom_path`).
- **Individuell anpassbar**: Dateinamen (`set_config_name`), Verzeichnisse (`set_custom_dir`) oder explizite Dateipfade (`set_custom_path`). Der Programmname wird automatisch über `program-ctdra` ermittelt.
- **Globales Caching (`get_config::<T>()`)**: Einmaliges Laden und threadsicheres Zwischenspeichern statischer Referenzen pro Konfigurationstyp.
- **Atomare Operationen**: `load`, `store`, `modify`, `modify_config` für konsistentes Laden, Bearbeiten und Speichern.
@@ -29,16 +29,16 @@ Füge anschließend die Abhängigkeit in deiner `Cargo.toml` ein:
```toml
[dependencies]
config = { version = "1.0.0", registry = "gitea" }
config-ctdra = { version = "1.0.1", registry = "gitea" }
```
Alternativ als Git- oder Pfad-Abhängigkeit:
```toml
[dependencies]
config = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/config.git", branch = "main" }
config-ctdra = { git = "https://gitea.creative-dragonslayer.de/Rust-Crates/config.git", branch = "main" }
# oder lokal:
# config = { path = "../Config" }
# config-ctdra = { path = "../Config" }
```
---
@@ -97,11 +97,9 @@ impl Default for Database {
### 2. Globales Caching mit `get_config`
```rust
use config::{get_config, set_program_name};
use config_ctdra::get_config;
fn main() {
set_program_name("my-service");
let cfg = get_config::<AppConfig>();
println!("Log-Level: {}", cfg.general.log_level);
println!("DB URL: {}", cfg.database.url);
@@ -111,7 +109,7 @@ fn main() {
### 3. Konfiguration modifizieren
```rust
use config::modify_config;
use config_ctdra::modify_config;
fn update_settings() {
modify_config::<AppConfig, _>(|cfg| {
@@ -123,9 +121,9 @@ fn update_settings() {
### 4. Manuelles Laden und Speichern
```rust
use config::{load, store};
use config_ctdra::{load, store};
fn custom_workflow() -> Result<(), config::ConfyError> {
fn custom_workflow() -> Result<(), config_ctdra::ConfyError> {
let mut cfg: AppConfig = load()?;
cfg.general.log_level = "debug".to_string();
store(&cfg)?;
+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
+101
View File
@@ -0,0 +1,101 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"timezone": "Europe/Berlin",
"schedule": ["before 6am on monday"],
"baseBranchPatterns": [
"dev"
],
"packageRules": [
{
"matchFileNames": [".gitea/workflows/**"],
"groupName": "Gitea Actions",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["cargo"],
"groupName": "Cargo Dependencies",
"separateMajorMinor": false,
"separateMinorPatch": false
},
{
"matchManagers": ["dockerfile", "docker-compose"],
"groupName": "Docker-Images",
"separateMajorMinor": false,
"separateMinorPatch": false
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "aquasecurity/trivy",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "google/osv-scanner",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "trufflesecurity/trufflehog",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_BINSTALL_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-bins/cargo-binstall",
"datasourceTemplate": "github-releases",
"extractVersionTemplate": "^v(?<version>.*)$"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_DEB_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-deb",
"datasourceTemplate": "crate"
},
{
"customType": "regex",
"managerFilePatterns": [
"/^\\.gitea/workflows/.+\\.ya?ml$/"
],
"matchStrings": [
"CARGO_GENERATE_RPM_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "cargo-generate-rpm",
"datasourceTemplate": "crate"
}
]
}
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Prüft, ob die aktuelle Crate-Version bereits in der Gitea Cargo-Registry existiert.
Liest Name und Version aus Cargo.toml und fragt die Gitea Package-API ab.
Das Ergebnis wird als Step-Output "exists" (true/false) in GITHUB_OUTPUT
geschrieben, damit der Release/Publish-Job komplett übersprungen werden kann,
statt erst nach Build & Tests an einem "Version existiert bereits"-Fehler der
Registry zu scheitern.
"""
import os
import re
import urllib.error
import urllib.request
def read_cargo_field(field, path="Cargo.toml"):
pattern = re.compile(rf'^{field}\s*=\s*"(.*)"')
with open(path) as f:
for line in f:
match = pattern.match(line.strip())
if match:
return match.group(1)
raise SystemExit(f"Feld '{field}' nicht in {path} gefunden.")
def version_exists(gitea_url, owner, name, version, token):
url = f"{gitea_url}/api/v1/packages/{owner}/cargo/{name}/{version}"
req = urllib.request.Request(url, method="GET")
req.add_header("Authorization", f"token {token}")
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status == 200
except urllib.error.HTTPError as e:
if e.code == 404:
return False
raise
def main():
gitea_url = os.environ["GITEA_URL"].strip().rstrip("/")
owner = os.environ["REPO_OWNER"].strip()
token = os.environ["TOKEN"].strip()
github_output = os.environ["GITHUB_OUTPUT"]
name = read_cargo_field("name")
version = read_cargo_field("version")
print(f"Prüfe {name}@{version} in der Gitea Cargo Registry...")
exists = version_exists(gitea_url, owner, name, version, token)
if exists:
print(f"Version {version} existiert bereits in der Registry. Release/Publish wird übersprungen.")
else:
print(f"Version {version} ist neu.")
with open(github_output, "a") as f:
f.write(f"exists={'true' if exists else 'false'}\n")
if __name__ == "__main__":
main()
+312
View File
@@ -0,0 +1,312 @@
#!/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()
+31 -251
View File
@@ -5,13 +5,13 @@
//! - Einheitliche Pfadermittlung:
//! - Bei Ausführung als Root (unter Linux/Unix): `/etc/<program_name>/<config_name>.toml`
//! - Bei regulärem Benutzer: Standard-Benutzer-Konfigurationsverzeichnis (z. B. `~/.config/<program_name>/<config_name>.toml`)
//! - Anpassbar über `set_program_name`, `set_config_name`, `set_custom_dir` und `set_custom_path`.
//! - Anpassbar über `set_config_name`, `set_custom_dir` und `set_custom_path`.
//! - Thread-sicheres globales Caching (`get_config::<T>()`).
//! - Atomares Laden, Speichern und Modifizieren (`load`, `store`, `modify`, `modify_config`).
//!
//! # Beispiel
//! ```rust
//! use config::{get_config, modify_config, set_program_name};
//! use config_ctdra::{get_config, modify_config};
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
@@ -20,8 +20,6 @@
//! pub enable_ssl: bool,
//! }
//!
//! set_program_name("my-service");
//!
//! // Konfiguration abrufen (wird beim ersten Aufruf geladen und zwischengespeichert)
//! let cfg = get_config::<MyConfig>();
//! println!("Port: {}", cfg.server_port);
@@ -34,11 +32,10 @@ use std::path::PathBuf;
use std::sync::{OnceLock, RwLock};
pub use confy::ConfyError;
use serde::de::DeserializeOwned;
use program_ctdra::try_program_name;
use serde::Serialize;
/// Optionaler benutzerdefinierter Programmname für die Konfigurationspfade.
static PROGRAM_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
use serde::de::DeserializeOwned;
use sudo_ctdra::is_run_as_root;
/// Optionaler benutzerdefinierter Konfigurationsdateiname (Standard: "config").
static CONFIG_NAME: OnceLock<RwLock<Option<String>>> = OnceLock::new();
@@ -56,50 +53,13 @@ static GLOBAL_CONFIGS: OnceLock<RwLock<HashMap<TypeId, &'static (dyn Any + Send
/// Standardname für Konfigurationsdateien.
const DEFAULT_CONFIG_NAME: &str = "config";
/// Prüft, ob das Programm mit Root-/Administrator-Rechten ausgeführt wird.
/// Ermittelt den Programmnamen über `program-ctdra`.
///
/// # Returns
/// # Panics
///
/// * `true` - Das Programm läuft mit erhöhten Rechten (z. B. UID 0 unter Linux/Unix)
/// * `false` - Das Programm läuft mit normalen Benutzerrechten
pub fn is_run_as_root() -> bool {
#[cfg(target_os = "windows")]
{
std::process::Command::new("net")
.args(["session"])
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
#[cfg(not(target_os = "windows"))]
{
unsafe { libc::geteuid() == 0 }
}
}
/// Setzt den Programmnamen für die Pfadermittlung explizit.
pub fn set_program_name(name: impl Into<String>) {
let lock = PROGRAM_NAME.get_or_init(|| RwLock::new(None));
if let Ok(mut guard) = lock.write() {
*guard = Some(name.into());
}
}
/// Ermittelt den konfigurierten Programmnamen (aus `set_program_name` oder `std::env::current_exe`).
/// Löst eine Panik aus, wenn der Programmname nicht ermittelt werden kann.
pub fn get_program_name() -> String {
if let Some(lock) = PROGRAM_NAME.get() {
if let Ok(guard) = lock.read() {
if let Some(name) = guard.as_ref() {
return name.clone();
}
}
}
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())
try_program_name().expect("Programmname konnte nicht ermittelt werden")
}
/// Setzt den Namen der Konfigurationsdatei (ohne Dateiendung `.toml`).
@@ -112,12 +72,11 @@ pub fn set_config_name(name: impl Into<String>) {
/// Liefert den konfigurierten Namen der Konfigurationsdatei (Standard: `"config"`).
pub fn get_config_name() -> String {
if let Some(lock) = CONFIG_NAME.get() {
if let Ok(guard) = lock.read() {
if let Some(name) = guard.as_ref() {
return name.clone();
}
}
if let Some(lock) = CONFIG_NAME.get()
&& let Ok(guard) = lock.read()
&& let Some(name) = guard.as_ref()
{
return name.clone();
}
DEFAULT_CONFIG_NAME.to_string()
}
@@ -140,10 +99,10 @@ pub fn clear_custom_dir() {
/// Liefert das aktuell gesetzte benutzerdefinierte Konfigurationsverzeichnis, falls vorhanden.
pub fn get_custom_dir() -> Option<PathBuf> {
if let Some(lock) = CUSTOM_CONFIG_DIR.get() {
if let Ok(guard) = lock.read() {
return guard.clone();
}
if let Some(lock) = CUSTOM_CONFIG_DIR.get()
&& let Ok(guard) = lock.read()
{
return guard.clone();
}
None
}
@@ -166,10 +125,10 @@ pub fn clear_custom_path() {
/// Liefert den aktuell gesetzten expliziten Pfad zur Konfigurationsdatei, falls vorhanden.
pub fn get_custom_path() -> Option<PathBuf> {
if let Some(lock) = CUSTOM_CONFIG_PATH.get() {
if let Ok(guard) = lock.read() {
return guard.clone();
}
if let Some(lock) = CUSTOM_CONFIG_PATH.get()
&& let Ok(guard) = lock.read()
{
return guard.clone();
}
None
}
@@ -303,19 +262,18 @@ where
{
let map_lock = GLOBAL_CONFIGS.get_or_init(|| RwLock::new(HashMap::new()));
if let Ok(guard) = map_lock.read() {
if let Some(entry) = guard.get(&TypeId::of::<T>()) {
if let Some(val) = entry.downcast_ref::<T>() {
return val;
}
}
if let Ok(guard) = map_lock.read()
&& let Some(entry) = guard.get(&TypeId::of::<T>())
&& let Some(val) = entry.downcast_ref::<T>()
{
return val;
}
let mut guard = map_lock.write().unwrap();
if let Some(entry) = guard.get(&TypeId::of::<T>()) {
if let Some(val) = entry.downcast_ref::<T>() {
return val;
}
if let Some(entry) = guard.get(&TypeId::of::<T>())
&& let Some(val) = entry.downcast_ref::<T>()
{
return val;
}
let loaded: T = load_config::<T>();
@@ -323,181 +281,3 @@ where
guard.insert(TypeId::of::<T>(), boxed);
boxed
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
use std::fs;
use std::sync::Mutex;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct DummyAppConfig {
general: DummyGeneral,
}
impl Default for DummyAppConfig {
fn default() -> Self {
Self {
general: DummyGeneral::default(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct DummyGeneral {
log_level: String,
apps_dir: String,
}
impl Default for DummyGeneral {
fn default() -> Self {
Self {
log_level: "info".to_string(),
apps_dir: "/var/apps".to_string(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct CustomServerConfig {
host: String,
port: u16,
}
impl Default for CustomServerConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
}
}
}
#[test]
fn test_default_config_loading() {
let _guard = TEST_LOCK.lock().unwrap();
let cfg = DummyAppConfig::default();
assert_eq!(cfg.general.log_level, "info");
assert_eq!(cfg.general.apps_dir, "/var/apps");
}
#[test]
fn test_program_and_config_name() {
let _guard = TEST_LOCK.lock().unwrap();
set_program_name("test-app");
assert_eq!(get_program_name(), "test-app");
set_config_name("settings");
assert_eq!(get_config_name(), "settings");
}
#[test]
fn test_custom_path_and_dir() {
let _guard = TEST_LOCK.lock().unwrap();
clear_custom_path();
clear_custom_dir();
set_config_name("config");
let temp_dir = env::temp_dir().join("test-config-crate-dir");
let _ = fs::create_dir_all(&temp_dir);
set_custom_dir(&temp_dir);
assert_eq!(get_custom_dir(), Some(temp_dir.clone()));
let expected_path = temp_dir.join(format!("{}.toml", get_config_name()));
assert_eq!(get_config_path(), expected_path);
let explicit_file = temp_dir.join("explicit.toml");
set_custom_path(&explicit_file);
assert_eq!(get_custom_path(), Some(explicit_file.clone()));
assert_eq!(get_config_path(), explicit_file);
clear_custom_path();
clear_custom_dir();
}
#[test]
fn test_store_and_load_with_custom_path() {
let _guard = TEST_LOCK.lock().unwrap();
let temp_file = env::temp_dir().join("test_store_load.toml");
let _ = fs::remove_file(&temp_file);
set_custom_path(&temp_file);
let initial_cfg = CustomServerConfig {
host: "0.0.0.0".to_string(),
port: 9000,
};
let store_res = store(&initial_cfg);
assert!(store_res.is_ok());
let loaded_cfg: CustomServerConfig = load().expect("Failed to load stored config");
assert_eq!(loaded_cfg, initial_cfg);
// Test modify
let mod_res = modify::<CustomServerConfig, _>(|c| {
c.port = 9090;
});
assert!(mod_res.is_ok());
let reloaded_cfg: CustomServerConfig = load().expect("Failed to reload modified config");
assert_eq!(reloaded_cfg.port, 9090);
let _ = fs::remove_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_multiple_config_types_global() {
let _guard = TEST_LOCK.lock().unwrap();
let file_a = env::temp_dir().join("test_global_type_a.toml");
let _ = fs::remove_file(&file_a);
set_custom_path(&file_a);
let cfg_app: &'static DummyAppConfig = get_config();
let cfg_server: &'static CustomServerConfig = get_config();
assert_eq!(cfg_app.general.apps_dir, "/var/apps");
assert_eq!(cfg_server.port, 8080);
assert_eq!(cfg_server.host, "127.0.0.1");
let _ = fs::remove_file(&file_a);
clear_custom_path();
}
#[test]
fn test_modify_config_convenience() {
let _guard = TEST_LOCK.lock().unwrap();
let temp_file = env::temp_dir().join("test_modify_convenience.toml");
let _ = fs::remove_file(&temp_file);
set_custom_path(&temp_file);
let initial = CustomServerConfig {
host: "localhost".to_string(),
port: 3000,
};
save_config(initial);
modify_config::<CustomServerConfig, _>(|c| {
c.port = 4000;
});
let loaded = load_config::<CustomServerConfig>();
assert_eq!(loaded.port, 4000);
assert_eq!(loaded.host, "localhost");
let _ = fs::remove_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_is_run_as_root() {
let _ = is_run_as_root();
}
}
+402
View File
@@ -0,0 +1,402 @@
use config_ctdra::{
ConfyError, clear_custom_dir, clear_custom_path, get_config, get_config_name, get_config_path,
get_custom_dir, get_custom_path, get_program_name, load, load_config, modify, modify_config,
save_config, set_config_name, set_custom_dir, set_custom_path, store,
};
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::thread;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn lock_test() -> std::sync::MutexGuard<'static, ()> {
TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
struct DummyAppConfig {
general: DummyGeneral,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct DummyGeneral {
log_level: String,
apps_dir: String,
}
impl Default for DummyGeneral {
fn default() -> Self {
Self {
log_level: "info".to_string(),
apps_dir: "/var/apps".to_string(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct CustomServerConfig {
host: String,
port: u16,
}
impl Default for CustomServerConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct DatabaseConfig {
url: String,
max_connections: u32,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
url: "postgres://localhost/db".to_string(),
max_connections: 10,
}
}
}
fn cleanup_temp_file(path: &PathBuf) {
let _ = fs::remove_file(path);
}
#[test]
fn test_program_name_resolution() {
let _guard = lock_test();
let name = get_program_name();
assert!(!name.is_empty(), "Programmname darf nicht leer sein");
}
#[test]
fn test_config_name_getter_and_setter() {
let _guard = lock_test();
set_config_name("custom_app_config");
assert_eq!(get_config_name(), "custom_app_config");
set_config_name("config");
assert_eq!(get_config_name(), "config");
}
#[test]
fn test_custom_dir_and_path_management() {
let _guard = lock_test();
clear_custom_path();
clear_custom_dir();
set_config_name("app");
let temp_dir = env::temp_dir().join("test_custom_dir_crate");
let _ = fs::create_dir_all(&temp_dir);
// 1. Benutzerdefiniertes Verzeichnis
set_custom_dir(&temp_dir);
assert_eq!(get_custom_dir(), Some(temp_dir.clone()));
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
// 2. Benutzerdefiniertes Verzeichnis mit .toml im Config-Namen
set_config_name("app.toml");
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
set_config_name("app");
// 3. Expliziter benutzerdefinierter Pfad hat Vorrang vor Verzeichnis
let explicit_path = temp_dir.join("explicit_settings.toml");
set_custom_path(&explicit_path);
assert_eq!(get_custom_path(), Some(explicit_path.clone()));
assert_eq!(get_config_path(), explicit_path);
// 4. Zurücksetzen
clear_custom_path();
assert_eq!(get_custom_path(), None);
assert_eq!(get_config_path(), temp_dir.join("app.toml"));
clear_custom_dir();
assert_eq!(get_custom_dir(), None);
set_config_name("config");
let _ = fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_default_config_path_resolution() {
let _guard = lock_test();
clear_custom_path();
clear_custom_dir();
set_config_name("config");
let path = get_config_path();
let prog_name = get_program_name();
let path_str = path.to_string_lossy();
assert!(
path_str.contains(&prog_name),
"Pfad {:?} sollte Programmnamen {} enthalten",
path,
prog_name
);
assert!(
path_str.ends_with("config.toml"),
"Pfad {:?} sollte auf config.toml enden",
path
);
}
#[test]
fn test_default_config_loading() {
let _guard = lock_test();
let cfg = DummyAppConfig::default();
assert_eq!(cfg.general.log_level, "info");
assert_eq!(cfg.general.apps_dir, "/var/apps");
}
#[test]
fn test_store_and_load_flow() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_store_and_load_flow.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let initial = CustomServerConfig {
host: "192.168.1.100".to_string(),
port: 9090,
};
// Speichern
let store_result = store(&initial);
assert!(store_result.is_ok(), "Store sollte erfolgreich sein");
assert!(temp_file.exists(), "Konfigurationsdatei sollte existieren");
// Laden
let loaded: CustomServerConfig = load().expect("Load sollte erfolgreich sein");
assert_eq!(loaded, initial);
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_load_config_fallback_and_invalid_toml() {
let _guard = lock_test();
let corrupt_file = env::temp_dir().join("corrupt_config_test.toml");
cleanup_temp_file(&corrupt_file);
fs::write(&corrupt_file, "INVALID_TOML_CONTENT = [[[[[").unwrap();
set_custom_path(&corrupt_file);
// load::<T>() sollte bei ungültigem TOML fehlschlagen
let res: Result<CustomServerConfig, ConfyError> = load();
assert!(
res.is_err(),
"Laden von korruptem TOML sollte mit ConfyError fehlschlagen"
);
// load_config::<T>() fällt im Fehlerfall auf Default zurück
let loaded_default: CustomServerConfig = load_config();
assert_eq!(loaded_default, CustomServerConfig::default());
cleanup_temp_file(&corrupt_file);
clear_custom_path();
}
#[test]
fn test_save_config_and_load_config() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_save_config_convenience.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let server_cfg = CustomServerConfig {
host: "0.0.0.0".to_string(),
port: 443,
};
save_config(server_cfg.clone());
assert!(temp_file.exists());
let reloaded: CustomServerConfig = load_config();
assert_eq!(reloaded, server_cfg);
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_modify_function() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_modify_function.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let initial = CustomServerConfig {
host: "10.0.0.1".to_string(),
port: 80,
};
store(&initial).expect("Store initial config failed");
let modified_result = modify::<CustomServerConfig, _>(|cfg| {
cfg.port = 8081;
cfg.host = "10.0.0.2".to_string();
});
assert!(modified_result.is_ok());
let modified = modified_result.unwrap();
assert_eq!(modified.port, 8081);
assert_eq!(modified.host, "10.0.0.2");
let loaded: CustomServerConfig = load().expect("Reload failed");
assert_eq!(loaded.port, 8081);
assert_eq!(loaded.host, "10.0.0.2");
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_modify_config_convenience() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_modify_config_convenience.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let initial = DatabaseConfig {
url: "sqlite://memory".to_string(),
max_connections: 5,
};
save_config(initial);
modify_config::<DatabaseConfig, _>(|cfg| {
cfg.max_connections = 25;
});
let loaded: DatabaseConfig = load_config();
assert_eq!(loaded.max_connections, 25);
assert_eq!(loaded.url, "sqlite://memory");
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct GlobalAppConfig {
service_name: String,
}
impl Default for GlobalAppConfig {
fn default() -> Self {
Self {
service_name: "global_service".to_string(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct GlobalServerConfig {
host: String,
port: u16,
}
impl Default for GlobalServerConfig {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct ConcurrentTestConfig {
worker_id: u32,
}
impl Default for ConcurrentTestConfig {
fn default() -> Self {
Self { worker_id: 1 }
}
}
#[test]
fn test_get_config_singleton_cache() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_get_config_singleton.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let initial = DatabaseConfig {
url: "postgres://prod-db:5432/main".to_string(),
max_connections: 50,
};
save_config(initial.clone());
// Erster Aufruf: lädt und speichert im Cache
let ref1: &'static DatabaseConfig = get_config();
assert_eq!(ref1.url, "postgres://prod-db:5432/main");
assert_eq!(ref1.max_connections, 50);
// Zweiter Aufruf: liefert dieselbe statische Referenz
let ref2: &'static DatabaseConfig = get_config();
assert!(
std::ptr::eq(ref1, ref2),
"get_config muss dieselbe Referenz zurückgeben"
);
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_multiple_config_types_in_global_cache() {
let _guard = lock_test();
let temp_file = env::temp_dir().join("test_multiple_types_cache.toml");
cleanup_temp_file(&temp_file);
set_custom_path(&temp_file);
let app_cfg: &'static GlobalAppConfig = get_config();
let srv_cfg: &'static GlobalServerConfig = get_config();
assert_eq!(app_cfg.service_name, "global_service");
assert_eq!(srv_cfg.port, 8080);
assert_eq!(srv_cfg.host, "127.0.0.1");
cleanup_temp_file(&temp_file);
clear_custom_path();
}
#[test]
fn test_concurrent_access() {
let _guard = lock_test();
let temp_file = Arc::new(env::temp_dir().join("test_concurrent_access.toml"));
cleanup_temp_file(&temp_file);
set_custom_path(&*temp_file);
let initial = ConcurrentTestConfig { worker_id: 42 };
save_config(initial);
let mut handles = Vec::new();
for i in 0..10 {
let handle = thread::spawn(move || {
let cfg: &'static ConcurrentTestConfig = get_config();
assert_eq!(cfg.worker_id, 42);
let _ = load_config::<ConcurrentTestConfig>();
if i % 2 == 0 {
modify_config::<ConcurrentTestConfig, _>(|c| {
c.worker_id = 1000 + i;
});
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
cleanup_temp_file(&temp_file);
clear_custom_path();
}