Compare commits
72
Commits
v0.1.0-preview
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af52604eb
|
||
|
|
6acbc605a5
|
||
|
|
d00b9a9aec | ||
|
|
cfc484937a
|
||
|
|
8637146add
|
||
|
|
009a1ef853 | ||
|
|
fc2cc4230d | ||
|
|
fe7a9472f7
|
||
|
|
77bce7b90e
|
||
|
|
66546f93e4
|
||
|
|
50d5e48d22
|
||
|
|
0d8af424c3
|
||
|
|
167825aa81
|
||
|
|
4fb1731020
|
||
|
|
70059896a4
|
||
|
|
c4ed24558f | ||
|
|
28991dc52a | ||
|
|
8c6ec81664
|
||
|
|
899a70a189
|
||
|
|
1d441cc83a
|
||
|
|
08b9ba74c6
|
||
|
|
c72122e20e | ||
|
|
677aef8e78
|
||
|
|
a465718f41 | ||
|
|
a8dd84045e
|
||
|
|
a4f9f4894a
|
||
|
|
31cfc8e460
|
||
|
|
f890c89f1c
|
||
|
|
0dbe24e7d9
|
||
|
|
51a800802d
|
||
|
|
9819dfce5b
|
||
|
|
d974f1b7ac
|
||
|
|
9858ee959d
|
||
|
|
d726bab972
|
||
|
|
851cfaeda8
|
||
|
|
9c4a1e1c95
|
||
|
|
fc798b39a3
|
||
|
|
2bde2baf66
|
||
|
|
24f127c629
|
||
|
|
b4bf33ebd9
|
||
|
|
83ae3ad3cc
|
||
|
|
b525a6c954
|
||
|
|
18854200c0
|
||
|
|
7e644e52c6
|
||
|
|
fd0ddf6aeb
|
||
|
|
c0ad5987df
|
||
|
|
52d9bb6880
|
||
|
|
d8ce9849b8
|
||
|
|
f04663803c
|
||
|
|
a1fac95722
|
||
|
|
020e2f3b1a
|
||
|
|
aa9e1da74f
|
||
|
|
cc1643cb8b
|
||
|
|
c2d21e93c8
|
||
|
|
c02644b48d
|
||
|
|
a1d22ce71f
|
||
|
|
10dc412566
|
||
|
|
68c1b07b23
|
||
|
|
25e076ba18
|
||
|
|
a0ffe731bf
|
||
|
|
9a2a1730dd
|
||
|
|
851c863b24
|
||
|
|
0d811cfb75
|
||
|
|
bd374b95fc
|
||
|
|
223ed09d94
|
||
|
|
54f74a2f2a
|
||
|
|
736dc1f2e5
|
||
|
|
2c232ec953
|
||
|
|
451abe83c0
|
||
|
|
8ef3ad28a0
|
||
|
|
24955f75b5
|
||
|
|
d44930de7e
|
@@ -0,0 +1,15 @@
|
||||
[registry]
|
||||
default = "gitea"
|
||||
|
||||
[registries.gitea]
|
||||
index = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/" # Sparse index
|
||||
# index = "https://gitea.creative-dragonslayer.de/Rust-Crates/_cargo-index.git" # Git
|
||||
|
||||
[net]
|
||||
git-fetch-with-cli = true
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
|
||||
[target.i686-unknown-linux-gnu]
|
||||
linker = "i686-linux-gnu-gcc"
|
||||
@@ -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
|
||||
+115
-16
@@ -6,46 +6,144 @@ 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 Packaging Tools (Prebuilt Binaries)
|
||||
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-${{ runner.os }}-
|
||||
|
||||
- name: Alte Paketierungs-Ausgaben aus dem Cache entfernen
|
||||
run: rm -rf target/debian target/generate-rpm target/arch
|
||||
|
||||
- name: Install Cross-Compilation Toolchains (apt)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||
|
||||
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
|
||||
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache Rustup Cross-Compilation-Targets
|
||||
id: cache-rustup-targets
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
|
||||
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
|
||||
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Add Rust Cross-Compilation Targets
|
||||
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
|
||||
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||
|
||||
- name: PATH um Cargo-bin-Verzeichnis ergänzen
|
||||
run: |
|
||||
mkdir -p ~/.cargo/bin
|
||||
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: |
|
||||
cargo test
|
||||
|
||||
- name: Build Release Binary
|
||||
- name: Build Release Binaries
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
cargo build --release --target aarch64-unknown-linux-gnu
|
||||
cargo build --release --target i686-unknown-linux-gnu
|
||||
|
||||
- name: Build Debian Package (.deb)
|
||||
- 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: |
|
||||
cargo deb
|
||||
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 Fedora / RPM Package (.rpm)
|
||||
- name: Build Debian Packages (.deb)
|
||||
run: |
|
||||
cargo generate-rpm
|
||||
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 Arch Linux Package (.pkg.tar.zst)
|
||||
- name: Build Fedora / RPM Packages (.rpm)
|
||||
run: |
|
||||
chmod +x packaging/arch/build-package.sh
|
||||
./packaging/arch/build-package.sh
|
||||
mkdir -p target/generate-rpm
|
||||
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
|
||||
- name: Build Arch Linux Packages (.pkg.tar.zst)
|
||||
run: |
|
||||
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
|
||||
- name: Publish Packages to Gitea Package Registry
|
||||
env:
|
||||
@@ -77,19 +175,20 @@ jobs:
|
||||
curl -f -s -S -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$pkg" \
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/stable/upload"
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/stable"
|
||||
done
|
||||
|
||||
- name: Create Gitea Release and Upload Assets
|
||||
env:
|
||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||
REPO: ${{ gitea.repository || github.repository }}
|
||||
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
|
||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
||||
TAG_NAME="v${VERSION}"
|
||||
RELEASE_TITLE="Release ${TAG_NAME}"
|
||||
RELEASE_NOTES="Automatisches Release für docker-update ${VERSION}."
|
||||
RELEASE_NOTES="Automatisches Release für ${REPO_NAME} ${VERSION}."
|
||||
|
||||
echo "Erstelle oder hole Release für Tag ${TAG_NAME} in ${REPO}..."
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
+115
-16
@@ -6,46 +6,144 @@ 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 Packaging Tools (Prebuilt Binaries)
|
||||
- name: Cache Cargo-Abhängigkeiten & Build-Artefakte
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-${{ runner.os }}-
|
||||
|
||||
- name: Alte Paketierungs-Ausgaben aus dem Cache entfernen
|
||||
run: rm -rf target/debian target/generate-rpm target/arch
|
||||
|
||||
- name: Install Cross-Compilation Toolchains (apt)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||
|
||||
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
|
||||
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Cache Rustup Cross-Compilation-Targets
|
||||
id: cache-rustup-targets
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
|
||||
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
|
||||
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Add Rust Cross-Compilation Targets
|
||||
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
|
||||
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||
|
||||
- name: PATH um Cargo-bin-Verzeichnis ergänzen
|
||||
run: |
|
||||
mkdir -p ~/.cargo/bin
|
||||
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: |
|
||||
cargo test
|
||||
|
||||
- name: Build Release Binary
|
||||
- name: Build Release Binaries
|
||||
run: |
|
||||
cargo build --release
|
||||
cargo build --release --target x86_64-unknown-linux-gnu
|
||||
cargo build --release --target aarch64-unknown-linux-gnu
|
||||
cargo build --release --target i686-unknown-linux-gnu
|
||||
|
||||
- name: Build Debian Package (.deb)
|
||||
- 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: |
|
||||
cargo deb
|
||||
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 Fedora / RPM Package (.rpm)
|
||||
- name: Build Debian Packages (.deb)
|
||||
run: |
|
||||
cargo generate-rpm
|
||||
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 Arch Linux Package (.pkg.tar.zst)
|
||||
- name: Build Fedora / RPM Packages (.rpm)
|
||||
run: |
|
||||
chmod +x packaging/arch/build-package.sh
|
||||
./packaging/arch/build-package.sh
|
||||
mkdir -p target/generate-rpm
|
||||
cargo generate-rpm --target x86_64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target aarch64-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
cargo generate-rpm --target i686-unknown-linux-gnu -s "release=\"${BUILD_NUMBER}\"" -o target/generate-rpm
|
||||
|
||||
- name: Build Arch Linux Packages (.pkg.tar.zst)
|
||||
run: |
|
||||
python3 scripts/package-arch.py --target x86_64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
python3 scripts/package-arch.py --target i686-unknown-linux-gnu --pkgrel "${BUILD_NUMBER}"
|
||||
|
||||
- name: Publish Packages to Gitea Package Registry
|
||||
env:
|
||||
@@ -77,19 +175,20 @@ jobs:
|
||||
curl -f -s -S -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
--upload-file "$pkg" \
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/testing/upload"
|
||||
"${GITEA_URL}/api/packages/${REPO_OWNER}/arch/testing"
|
||||
done
|
||||
|
||||
- name: Create Gitea Pre-Release and Upload Assets
|
||||
env:
|
||||
GITEA_URL: ${{ gitea.server_url || github.server_url }}
|
||||
REPO: ${{ gitea.repository || github.repository }}
|
||||
REPO_NAME: ${{ gitea.repository_name || github.event.repository.name }}
|
||||
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.RELEASE_TOKEN || secrets.PUBLISH_TOKEN || secrets.API_TOKEN || secrets.PAT_TOKEN || secrets.CUSTOM_TOKEN || secrets.GITEA_TOKEN || secrets.GITHUB_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
||||
TAG_NAME="v${VERSION}-preview"
|
||||
RELEASE_TITLE="Preview Release ${TAG_NAME}"
|
||||
RELEASE_NOTES="Automatisches Preview-Release für 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}..."
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -109,3 +109,4 @@ fabric.properties
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
.junie/plans
|
||||
|
||||
Generated
+1
@@ -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" />
|
||||
|
||||
Generated
+15
@@ -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>
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -31,50 +31,43 @@ DockerUpdate/
|
||||
│ └── workflows/
|
||||
│ ├── testing.yaml # CI/CD für testing-Branch (Tests, Testing-Packages & Preview-Release)
|
||||
│ └── main.yaml # CI/CD für main-Branch (Tests, Stable-Packages & Release)
|
||||
├── Cargo.toml # Rust-Manifest, Abhängigkeiten und Metadaten für cargo-deb / generate-rpm
|
||||
├── scripts/
|
||||
│ ├── get-build-number.py # Ermittelt dynamisch die nächste Build-Nummer für Pakete
|
||||
│ └── package-arch.py # Hilfsskript zur Arch-Linux-Paketerstellung (.pkg.tar.zst)
|
||||
├── Cargo.toml # Rust-Manifest, Abhängigkeiten und Metadaten für deb / generate-rpm / arch
|
||||
├── Cargo.lock
|
||||
├── LICENSE # GPL-3.0 Lizenztext
|
||||
├── README.md # Projektdokumentation für Endanwender und Administratoren
|
||||
├── AGENTS.md # Richtlinien und Dokumentation für Agenten
|
||||
├── packaging/
|
||||
│ ├── arch/
|
||||
│ │ ├── PKGBUILD # Arch Linux PKGBUILD
|
||||
│ │ └── build-package.sh # Hilfsskript zur Erstellung des .pkg.tar.zst-Pakets
|
||||
│ └── fedora/
|
||||
│ └── docker-update.spec # RPM-Spec für Fedora / RHEL / CentOS
|
||||
├── 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
|
||||
@@ -83,10 +76,6 @@ DockerUpdate/
|
||||
- `main.yaml`: Führt bei Push auf `main` Tests und Builds durch, veröffentlicht Pakete in der Gitea Package Registry
|
||||
(`distribution: stable`, `component: main` bzw. Gruppe/Repo `stable`) und erstellt ein Gitea Release mit den
|
||||
Paketen als Assets.
|
||||
- **`packaging/`**:
|
||||
- `arch/PKGBUILD`: Quellpaketdefinition für Arch Linux (AUR / Makepkg).
|
||||
- `arch/build-package.sh`: Hilfsskript zum Bauen des standardkonformen `.pkg.tar.zst`-Pakets aus dem Cargo-Build.
|
||||
- `fedora/docker-update.spec`: RPM-Spec-Datei für Fedora, RHEL und CentOS.
|
||||
|
||||
---
|
||||
|
||||
@@ -115,7 +104,7 @@ cargo deb
|
||||
cargo generate-rpm
|
||||
|
||||
# Arch Linux-Paket (.pkg.tar.zst) erstellen
|
||||
./packaging/arch/build-package.sh
|
||||
python3 scripts/package-arch.py
|
||||
```
|
||||
|
||||
---
|
||||
@@ -124,10 +113,10 @@ cargo generate-rpm
|
||||
|
||||
1. **Abwärtskompatibilität & Pfade**:
|
||||
Der Standard-Apps-Pfad `/var/apps` und die Konfigurationspfade dürfen nicht ohne triftigen Grund geändert werden.
|
||||
2. **Paketierungsdefinitionen synchron halten**:
|
||||
Werden Abhängigkeiten, Metadaten oder Assets geändert, müssen `Cargo.toml` (`[package.metadata.deb]`,
|
||||
`[package.metadata.generate-rpm]`), `packaging/arch/PKGBUILD`, `packaging/arch/build-package.sh` und
|
||||
`packaging/fedora/docker-update.spec` entsprechend aktualisiert werden.
|
||||
2. **Paketierungsdefinitionen in Cargo.toml pflegen**:
|
||||
Alle Paketierungsdefinitionen für Debian, RPM und Arch Linux werden zentral in `Cargo.toml` verwaltet
|
||||
(`[package.metadata.deb]`, `[package.metadata.generate-rpm]`, `[package.metadata.arch]`). Es wird kein separater
|
||||
`packaging/`-Ordner benötigt.
|
||||
3. **Automatisierung**:
|
||||
Die empfohlene Automatisierungsmethode ist **Cron** (`/etc/cron.d/docker-update` oder `crontab -e`).
|
||||
4. **Keine Mocking-Bypässe**:
|
||||
|
||||
Generated
+153
-28
@@ -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 = "0.1.0"
|
||||
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.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48aa83cc6ac4dc610adf5501a5392b4bcc543b2c1f24eaf77469a96e31ec9abe"
|
||||
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.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3afb591f9cdb6223c88ba39269aff895620c7f0716dc42b705b5733d5c7c0823"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"base64",
|
||||
"encoding_rs_io",
|
||||
"granit-parser",
|
||||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"serde_core",
|
||||
"smallvec",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
@@ -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"
|
||||
|
||||
+13
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "docker-update"
|
||||
version = "0.1.0"
|
||||
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"
|
||||
@@ -24,7 +24,7 @@ maintainer = "DragonSlayer_14"
|
||||
copyright = "2026 Linuxapps"
|
||||
section = "utils"
|
||||
priority = "optional"
|
||||
depends = "$auto, sudo, docker.io | docker-ce"
|
||||
depends = "$auto, sudo, docker-ce-cli, docker-compose-plugin"
|
||||
extended-description = """\
|
||||
DockerUpdate durchsucht Verzeichnisse (standardmäßig in /var/apps) nach
|
||||
Docker-Compose-Dateien mit ':latest'-Images, prüft auf neuere Images,
|
||||
@@ -43,3 +43,9 @@ assets = [
|
||||
{ source = "LICENSE", dest = "/usr/share/licenses/docker-update/LICENSE", mode = "644", license = true },
|
||||
]
|
||||
requires = { "sudo" = "*", "docker" = "*" }
|
||||
|
||||
[package.metadata.arch]
|
||||
pkgrel = "1"
|
||||
arch = "x86_64"
|
||||
depends = ["gcc-libs", "glibc", "sudo", "docker-ce-cli", "docker-compose-plugin"]
|
||||
optdepends = ["docker-compose: Unterstützung für Docker Compose v1"]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# DockerUpdate (`docker-update`)
|
||||
|
||||
[](https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate/actions?workflow=main.yaml)
|
||||
[](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.
|
||||
|
||||
@@ -29,7 +32,7 @@ auf neuere Versionen prüft, aktualisiert und neu startet.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- **Betriebssystem**: Linux (x86_64, aarch64)
|
||||
- **Betriebssystem**: Linux (amd64 / x86_64, arm64 / aarch64, i386 / i686)
|
||||
- **Laufzeit-Abhängigkeiten**:
|
||||
- `docker` (inkl. Docker Compose Plugin `docker compose` oder Standalone `docker-compose`)
|
||||
- `sudo`
|
||||
@@ -76,50 +79,88 @@ sudo apt install docker-update
|
||||
|
||||
---
|
||||
|
||||
### 2. Fedora / RHEL / CentOS (`dnf` / `yum`)
|
||||
### 2. RedHat / Fedora / SUSE
|
||||
|
||||
#### Stable (Produktiv)
|
||||
|
||||
Erstelle die Repository-Datei `/etc/yum.repos.d/docker-update.repo`:
|
||||
Repository einrichten:
|
||||
```bash
|
||||
# auf RedHat-basierten Distributionen
|
||||
dnf config-manager --add-repo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
|
||||
|
||||
# Fedora 41+ (DNF5)
|
||||
dnf config-manager addrepo --from-repofile=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
|
||||
|
||||
# auf SUSE-basierten Distributionen
|
||||
zypper addrepo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable.repo
|
||||
```
|
||||
|
||||
Paket installieren:
|
||||
|
||||
```bash
|
||||
sudo tee /etc/yum.repos.d/docker-update-stable.repo << 'EOF'
|
||||
[docker-update-stable]
|
||||
name=DockerUpdate Stable
|
||||
baseurl=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/stable
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
EOF
|
||||
# auf RedHat-basierten Distributionen
|
||||
dnf install docker-update
|
||||
|
||||
# Installation via DNF
|
||||
sudo dnf install docker-update
|
||||
# auf SUSE-basierten Distributionen
|
||||
zypper install docker-update
|
||||
```
|
||||
|
||||
#### Testing (Vorschau / Testing)
|
||||
|
||||
Repository einrichten:
|
||||
```bash
|
||||
sudo tee /etc/yum.repos.d/docker-update-testing.repo << 'EOF'
|
||||
[docker-update-testing]
|
||||
name=DockerUpdate Testing
|
||||
baseurl=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing
|
||||
enabled=1
|
||||
gpgcheck=0
|
||||
EOF
|
||||
# auf RedHat-basierten Distributionen
|
||||
dnf config-manager --add-repo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
|
||||
|
||||
# Installation via DNF
|
||||
sudo dnf install docker-update
|
||||
# Fedora 41+ (DNF5)
|
||||
dnf config-manager addrepo --from-repofile=https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
|
||||
|
||||
# auf SUSE-basierten Distributionen
|
||||
zypper addrepo https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/rpm/testing.repo
|
||||
```
|
||||
|
||||
Paket installieren:
|
||||
|
||||
```bash
|
||||
# auf RedHat-basierten Distributionen
|
||||
dnf install docker-update
|
||||
|
||||
# auf SUSE-basierten Distributionen
|
||||
zypper install docker-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Arch Linux (`pacman`)
|
||||
|
||||
### Installiere den GPG-Key für das Repository
|
||||
|
||||
Key herunterladen:
|
||||
|
||||
```bash
|
||||
wget https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/repository.key
|
||||
```
|
||||
|
||||
ID des Keys anzeigen lassen:
|
||||
|
||||
```bash
|
||||
gpg --show-keys repository.key
|
||||
```
|
||||
|
||||
Key zu pacman hinzufügen und signieren:
|
||||
|
||||
```
|
||||
pacman-key --add repository.key
|
||||
pacman-key --lsign-key {key id}
|
||||
```
|
||||
|
||||
{key id} = ID aus dem vorherigen Schritt
|
||||
|
||||
#### Stable (Produktiv)
|
||||
|
||||
Füge das Repository in `/etc/pacman.conf` ein:
|
||||
|
||||
```ini
|
||||
[docker-update-stable]
|
||||
[stable]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/stable/$arch
|
||||
```
|
||||
@@ -135,7 +176,7 @@ sudo pacman -Sy docker-update
|
||||
Füge das Testing-Repository in `/etc/pacman.conf` ein:
|
||||
|
||||
```ini
|
||||
[docker-update-testing]
|
||||
[testing]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://gitea.creative-dragonslayer.de/api/packages/Linuxapps/arch/testing/$arch
|
||||
```
|
||||
@@ -218,7 +259,7 @@ cargo install cargo-deb
|
||||
cargo deb
|
||||
|
||||
# Installation des erstellten Pakets
|
||||
sudo dpkg -i target/debian/docker-update_0.1.0-1_amd64.deb
|
||||
sudo dpkg -i target/debian/docker-update_*.deb
|
||||
```
|
||||
|
||||
Die Paketkonfiguration und Abhängigkeiten (`sudo`, `docker.io | docker-ce`) sind in `Cargo.toml` unter
|
||||
@@ -226,49 +267,49 @@ Die Paketkonfiguration und Abhängigkeiten (`sudo`, `docker.io | docker-ce`) sin
|
||||
|
||||
---
|
||||
|
||||
### 2. Arch Linux (`PKGBUILD`)
|
||||
### 2. Arch Linux (`.pkg.tar.zst`)
|
||||
|
||||
Für Arch Linux liegt ein `PKGBUILD` im Verzeichnis `packaging/arch/` bereit:
|
||||
Die Paketmetadaten und Abhängigkeiten für Arch Linux sind direkt in `Cargo.toml` unter `[package.metadata.arch]`
|
||||
hinterlegt. Das Paket kann nach dem Kompilieren des Release-Builds über das Skript `scripts/package-arch.py` (analog zu
|
||||
CI/CD) erzeugt werden (optional mit `--target <TRIPLE>` für Cross-Builds):
|
||||
|
||||
```bash
|
||||
cd packaging/arch
|
||||
# Release kompilieren
|
||||
cargo build --release
|
||||
|
||||
# Paket bauen und installieren
|
||||
makepkg -si
|
||||
# Arch Linux-Paket (.pkg.tar.zst) erstellen (Standard: Host/x86_64)
|
||||
python3 scripts/package-arch.py
|
||||
|
||||
# Optional für spezifische Zielarchitektur:
|
||||
# python3 scripts/package-arch.py --target aarch64-unknown-linux-gnu
|
||||
# python3 scripts/package-arch.py --target i686-unknown-linux-gnu
|
||||
|
||||
# Installation des erstellten Pakets
|
||||
sudo pacman -U target/arch/docker-update-*.pkg.tar.zst
|
||||
```
|
||||
|
||||
Alternativ kann das Binary auch manuell mit `pacman -U <paketdatei>.pkg.tar.zst` installiert werden.
|
||||
|
||||
---
|
||||
|
||||
### 3. Fedora / RHEL / CentOS (`.rpm`)
|
||||
|
||||
Für RPM-basierte Distributionen gibt es zwei Möglichkeiten:
|
||||
|
||||
#### Option A: Mit `cargo-generate-rpm`
|
||||
Das RPM-Paket wird mithilfe von [`cargo-generate-rpm`](https://github.com/cat-in-136/cargo-generate-rpm) direkt aus den
|
||||
Metadaten in `Cargo.toml` (`[package.metadata.generate-rpm]`) erstellt:
|
||||
|
||||
```bash
|
||||
# cargo-generate-rpm installieren
|
||||
# cargo-generate-rpm installieren (falls noch nicht vorhanden)
|
||||
cargo install cargo-generate-rpm
|
||||
|
||||
# Release bauen und RPM erzeugen
|
||||
cargo build --release
|
||||
cargo generate-rpm
|
||||
|
||||
# Optional für spezifische Zielarchitektur (z.B. Cross-Builds):
|
||||
# mkdir -p target/generate-rpm
|
||||
# cargo generate-rpm --target aarch64-unknown-linux-gnu -o target/generate-rpm
|
||||
# cargo generate-rpm --target i686-unknown-linux-gnu -o target/generate-rpm
|
||||
|
||||
# Installation des erstellten RPM-Pakets
|
||||
sudo dnf install target/generate-rpm/docker-update-0.1.0-1.*.rpm
|
||||
```
|
||||
|
||||
#### Option B: Mit `rpmbuild` und Spec-Datei
|
||||
|
||||
Die RPM-Spec-Datei befindet sich in `packaging/fedora/docker-update.spec`:
|
||||
|
||||
```bash
|
||||
# Erforderliche Build-Werkzeuge installieren
|
||||
sudo dnf install rpm-build
|
||||
|
||||
# Paket über rpmbuild erstellen
|
||||
rpmbuild -ba packaging/fedora/docker-update.spec
|
||||
sudo dnf install target/generate-rpm/docker-update-*.rpm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
# Maintainer: DragonSlayer_14
|
||||
|
||||
pkgname=docker-update
|
||||
pkgver=0.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images"
|
||||
arch=('x86_64' 'aarch64')
|
||||
url="https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate"
|
||||
license=('GPL-3.0-or-later')
|
||||
depends=('gcc-libs' 'glibc' 'sudo' 'docker')
|
||||
optdepends=('docker-compose: Unterstützung für Docker Compose v1')
|
||||
makedepends=('cargo')
|
||||
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
prepare() {
|
||||
cd "$srcdir/$pkgname-$pkgver" 2>/dev/null || cd "$srcdir/$pkgname" 2>/dev/null || cd "$srcdir"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" 2>/dev/null || cargo fetch || true
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "$srcdir/$pkgname-$pkgver" 2>/dev/null || cd "$srcdir/$pkgname" 2>/dev/null || cd "$srcdir"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
export CARGO_TARGET_DIR=target
|
||||
cargo build --frozen --release --all-targets 2>/dev/null || cargo build --release
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "$srcdir/$pkgname-$pkgver" 2>/dev/null || cd "$srcdir/$pkgname" 2>/dev/null || cd "$srcdir"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo test --frozen 2>/dev/null || cargo test
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$srcdir/$pkgname-$pkgver" 2>/dev/null || cd "$srcdir/$pkgname" 2>/dev/null || cd "$srcdir"
|
||||
install -Dm755 "target/release/$pkgname" "$pkgdir/usr/bin/$pkgname"
|
||||
install -Dm644 "LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||
install -Dm644 "README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
if [ ! -f "target/release/docker-update" ]; then
|
||||
cargo build --release
|
||||
fi
|
||||
|
||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)"
|
||||
PKGVER="${VERSION}"
|
||||
PKGREL="1"
|
||||
ARCH="x86_64"
|
||||
|
||||
BUILD_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$BUILD_DIR"' EXIT
|
||||
|
||||
mkdir -p "$BUILD_DIR/usr/bin"
|
||||
mkdir -p "$BUILD_DIR/usr/share/licenses/docker-update"
|
||||
mkdir -p "$BUILD_DIR/usr/share/doc/docker-update"
|
||||
|
||||
install -m 755 "target/release/docker-update" "$BUILD_DIR/usr/bin/docker-update"
|
||||
install -m 644 "LICENSE" "$BUILD_DIR/usr/share/licenses/docker-update/LICENSE"
|
||||
install -m 644 "README.md" "$BUILD_DIR/usr/share/doc/docker-update/README.md"
|
||||
|
||||
INSTALLED_SIZE="$(du -sb "$BUILD_DIR" | awk '{print $1}')"
|
||||
|
||||
cat <<EOF > "$BUILD_DIR/.PKGINFO"
|
||||
pkgname = docker-update
|
||||
pkgbase = docker-update
|
||||
pkgver = ${PKGVER}-${PKGREL}
|
||||
pkgdesc = Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images
|
||||
url = https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate
|
||||
builddate = $(date +%s)
|
||||
packager = DragonSlayer_14
|
||||
size = ${INSTALLED_SIZE}
|
||||
arch = ${ARCH}
|
||||
license = GPL-3.0-or-later
|
||||
depend = gcc-libs
|
||||
depend = glibc
|
||||
depend = sudo
|
||||
depend = docker
|
||||
optdepend = docker-compose: Unterstützung für Docker Compose v1
|
||||
makepkgopt = strip
|
||||
EOF
|
||||
|
||||
mkdir -p "target/arch"
|
||||
OUTPUT_FILE="$PROJECT_ROOT/target/arch/docker-update-${PKGVER}-${PKGREL}-${ARCH}.pkg.tar.zst"
|
||||
(cd "$BUILD_DIR" && tar --zstd -cf "$OUTPUT_FILE" .PKGINFO usr)
|
||||
|
||||
echo "Arch-Paket erfolgreich erstellt: $OUTPUT_FILE"
|
||||
@@ -1,42 +0,0 @@
|
||||
Name: docker-update
|
||||
Version: 0.1.0
|
||||
Release: 1%{?dist}
|
||||
Summary: Automatisches Aktualisieren von Docker-Compose-Anwendungen in /var/apps mit :latest-Images
|
||||
|
||||
License: GPL-3.0-or-later
|
||||
URL: https://gitea.creative-dragonslayer.de/Linuxapps/DockerUpdate
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
|
||||
BuildRequires: cargo
|
||||
BuildRequires: rust
|
||||
Requires: sudo
|
||||
Requires: (docker or moby-engine or docker-ce)
|
||||
Suggests: (docker-compose or docker-compose-plugin)
|
||||
|
||||
%description
|
||||
DockerUpdate durchsucht Verzeichnisse (standardmäßig in /var/apps) nach
|
||||
Docker-Compose-Dateien mit ':latest'-Images, prüft auf neuere Images,
|
||||
lädt diese herunter und startet die betroffenen Container automatisch neu.
|
||||
|
||||
%prep
|
||||
%autosetup
|
||||
|
||||
%build
|
||||
cargo build --release
|
||||
|
||||
%check
|
||||
cargo test
|
||||
|
||||
%install
|
||||
install -D -p -m 0755 target/release/%{name} %{buildroot}%{_bindir}/%{name}
|
||||
install -D -p -m 0644 README.md %{buildroot}%{_docdir}/%{name}/README.md
|
||||
install -D -p -m 0644 LICENSE %{buildroot}%{_licensedir}/%{name}/LICENSE
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
%doc README.md
|
||||
%{_bindir}/%{name}
|
||||
|
||||
%changelog
|
||||
* Sun Aug 23 2026 DragonSlayer_14 <dragonslayer@creative-dragonslayer.de> - 0.1.0-1
|
||||
- Erstveröffentlichung von docker-update
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
|
||||
#################################################################################
|
||||
# WARNING: Do not store sensitive information in this file, #
|
||||
# as its contents will be included in the Qodana report. #
|
||||
#################################################################################
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
#exclude:
|
||||
# - name: <SomeDisabledInspectionId>
|
||||
# paths:
|
||||
# - <path/where/not/run/inspection>
|
||||
|
||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||
#bootstrap: sh ./prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||
#plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
|
||||
# severityThresholds - configures maximum thresholds for different problem severities
|
||||
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
|
||||
# dependencyLicenses - fails the run on prohibited or unknown dependency licenses
|
||||
# Code Coverage is available in Ultimate and Ultimate Plus plans
|
||||
#failureConditions:
|
||||
# severityThresholds:
|
||||
# any: 15
|
||||
# critical: 5
|
||||
# testCoverageThresholds:
|
||||
# fresh: 70
|
||||
# total: 50
|
||||
# dependencyLicenses:
|
||||
# failOnProhibited: true
|
||||
# failOnUnknown: false
|
||||
|
||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||
linter: jetbrains/qodana-<linter>:2026.2
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
],
|
||||
"timezone": "Europe/Berlin",
|
||||
"schedule": [
|
||||
"before 6am on monday"
|
||||
],
|
||||
"baseBranchPatterns": [
|
||||
"dev"
|
||||
],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchFileNames": [
|
||||
".gitea/workflows/**"
|
||||
],
|
||||
"groupName": "Gitea Actions",
|
||||
"separateMajorMinor": false,
|
||||
"separateMinorPatch": false
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"cargo"
|
||||
],
|
||||
"groupName": "Cargo Dependencies",
|
||||
"separateMajorMinor": false,
|
||||
"separateMinorPatch": false
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"dockerfile",
|
||||
"docker-compose"
|
||||
],
|
||||
"groupName": "Docker-Images",
|
||||
"separateMajorMinor": false,
|
||||
"separateMinorPatch": false
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "aquasecurity/trivy",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "google/osv-scanner",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "trufflesecurity/trufflehog",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"CARGO_BINSTALL_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "cargo-bins/cargo-binstall",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"CARGO_DEB_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "cargo-deb",
|
||||
"datasourceTemplate": "crate"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"CARGO_GENERATE_RPM_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||
],
|
||||
"depNameTemplate": "cargo-generate-rpm",
|
||||
"datasourceTemplate": "crate"
|
||||
}
|
||||
]
|
||||
}
|
||||
Executable
+178
@@ -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()
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
TARGET_ARCH_MAP = {
|
||||
"x86_64-unknown-linux-gnu": "x86_64",
|
||||
"x86_64-unknown-linux-musl": "x86_64",
|
||||
"x86_64": "x86_64",
|
||||
"amd64": "x86_64",
|
||||
"aarch64-unknown-linux-gnu": "aarch64",
|
||||
"aarch64-unknown-linux-musl": "aarch64",
|
||||
"aarch64": "aarch64",
|
||||
"arm64": "aarch64",
|
||||
"i686-unknown-linux-gnu": "i686",
|
||||
"i686-unknown-linux-musl": "i686",
|
||||
"i686": "i686",
|
||||
"i386": "i686",
|
||||
}
|
||||
|
||||
|
||||
def resolve_pkgrel(version=None, default="1"):
|
||||
# 1. Environment Variable
|
||||
env_pkgrel = os.environ.get("BUILD_NUMBER") or os.environ.get("BUILD_NUM") or os.environ.get("PKGREL")
|
||||
if env_pkgrel:
|
||||
return str(env_pkgrel)
|
||||
|
||||
# 2. get-build-number.py falls vorhanden
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
getter_path = os.path.join(script_dir, "get-build-number.py")
|
||||
if os.path.exists(getter_path):
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location("get_build_number", getter_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return str(module.get_next_build_number(version=version))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"[Hinweis] Konnte get-build-number nicht ausführen: {e}\n")
|
||||
|
||||
return str(default)
|
||||
|
||||
|
||||
def build_package(target_triple=None, target_arch=None, pkgrel=None):
|
||||
metadata = json.loads(subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"]))
|
||||
pkg = metadata["packages"][0]
|
||||
name = pkg["name"]
|
||||
version = pkg["version"]
|
||||
description = pkg.get("description", "")
|
||||
license_name = pkg.get("license", "")
|
||||
repository = pkg.get("repository", "")
|
||||
authors = pkg.get("authors", [])
|
||||
author = authors[0] if authors else "Unknown"
|
||||
|
||||
arch_meta = pkg.get("metadata", {}).get("arch", {})
|
||||
default_pkgrel = arch_meta.get("pkgrel", "1")
|
||||
if not pkgrel:
|
||||
pkgrel = resolve_pkgrel(version=version, default=default_pkgrel)
|
||||
default_arch = arch_meta.get("arch", "x86_64")
|
||||
|
||||
if target_arch:
|
||||
arch = TARGET_ARCH_MAP.get(target_arch, target_arch)
|
||||
elif target_triple:
|
||||
arch = TARGET_ARCH_MAP.get(target_triple, default_arch)
|
||||
else:
|
||||
arch = default_arch
|
||||
|
||||
candidate_paths = []
|
||||
if target_triple:
|
||||
candidate_paths.append(f"target/{target_triple}/release/{name}")
|
||||
candidate_paths.append(f"target/release/{name}")
|
||||
|
||||
bin_path = None
|
||||
for p in candidate_paths:
|
||||
if os.path.exists(p):
|
||||
bin_path = p
|
||||
break
|
||||
|
||||
if not bin_path:
|
||||
raise FileNotFoundError(
|
||||
f"Keine kompilierte Binary für {name} gefunden. Gesuchte Pfade: {candidate_paths}"
|
||||
)
|
||||
|
||||
depends = arch_meta.get("depends", ["gcc-libs", "glibc"])
|
||||
optdepends = arch_meta.get("optdepends", [])
|
||||
|
||||
with tempfile.TemporaryDirectory() as build_dir:
|
||||
bin_dir = os.path.join(build_dir, "usr/bin")
|
||||
doc_dir = os.path.join(build_dir, f"usr/share/doc/{name}")
|
||||
lic_dir = os.path.join(build_dir, f"usr/share/licenses/{name}")
|
||||
os.makedirs(bin_dir, exist_ok=True)
|
||||
os.makedirs(doc_dir, exist_ok=True)
|
||||
os.makedirs(lic_dir, exist_ok=True)
|
||||
|
||||
subprocess.run(["install", "-m", "755", bin_path, f"{bin_dir}/{name}"], check=True)
|
||||
if os.path.exists("LICENSE"):
|
||||
subprocess.run(["install", "-m", "644", "LICENSE", f"{lic_dir}/LICENSE"], check=True)
|
||||
if os.path.exists("README.md"):
|
||||
subprocess.run(["install", "-m", "644", "README.md", f"{doc_dir}/README.md"], check=True)
|
||||
|
||||
installed_size = subprocess.check_output(["du", "-sb", build_dir]).decode().split()[0]
|
||||
builddate = str(int(time.time()))
|
||||
|
||||
pkginfo_lines = [
|
||||
f"pkgname = {name}",
|
||||
f"pkgbase = {name}",
|
||||
f"pkgver = {version}-{pkgrel}",
|
||||
f"pkgdesc = {description}",
|
||||
f"url = {repository}",
|
||||
f"builddate = {builddate}",
|
||||
f"packager = {author}",
|
||||
f"size = {installed_size}",
|
||||
f"arch = {arch}",
|
||||
f"license = {license_name}",
|
||||
]
|
||||
for dep in depends:
|
||||
pkginfo_lines.append(f"depend = {dep}")
|
||||
for optdep in optdepends:
|
||||
pkginfo_lines.append(f"optdepend = {optdep}")
|
||||
pkginfo_lines.append("makepkgopt = strip\n")
|
||||
|
||||
with open(os.path.join(build_dir, ".PKGINFO"), "w") as f:
|
||||
f.write("\n".join(pkginfo_lines))
|
||||
|
||||
os.makedirs("target/arch", exist_ok=True)
|
||||
output_file = os.path.abspath(f"target/arch/{name}-{version}-{pkgrel}-{arch}.pkg.tar.zst")
|
||||
subprocess.run(["tar", "--zstd", "-cf", output_file, ".PKGINFO", "usr"], cwd=build_dir, check=True)
|
||||
print(f"Arch-Paket erfolgreich erstellt: {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Erstellt Arch Linux-Pakete (.pkg.tar.zst)")
|
||||
parser.add_argument("--target", help="Rust Target-Triple (z.B. x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, i686-unknown-linux-gnu)")
|
||||
parser.add_argument("--arch", help="Architektur (z.B. x86_64, aarch64, i686)")
|
||||
parser.add_argument("--pkgrel", help="Release-/Build-Nummer (z.B. 1, 2, ...)")
|
||||
args = parser.parse_args()
|
||||
|
||||
build_package(target_triple=args.target, target_arch=args.arch, pkgrel=args.pkgrel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//! DockerUpdate Bibliotheksexporte
|
||||
|
||||
pub mod config;
|
||||
pub mod updater;
|
||||
-254
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user