Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a734edd76c
|
||
|
|
64420cc224 | ||
|
|
bdd26a9189
|
||
|
|
84a64f1bde
|
||
|
|
4d2e92f6bb | ||
|
|
eb5519be87 | ||
|
|
f7f5290404
|
||
|
|
dffa74f47c
|
||
|
|
c455aa894e
|
||
|
|
513eae640f
|
||
|
|
475f0f9903
|
||
|
|
db42d73adb | ||
|
|
074d5a7ff2 | ||
|
|
5ac35bb370
|
||
|
|
4b5de248cd
|
||
|
|
0f01f53391 | ||
|
|
64a0aaa772
|
||
|
|
d247777667
|
||
|
|
9507bb1cca
|
||
|
|
df58f0f08f
|
||
|
|
2a059466b3
|
||
|
|
12828d45e1
|
||
|
|
32eb649c20
|
||
|
|
5f1d677485
|
||
|
|
80be163e55
|
||
|
|
5b385b4277
|
||
|
|
dcc7812501 | ||
|
|
113c5d2120 | ||
|
|
65c115932f
|
||
|
|
10a3ae0bf7
|
||
|
|
9c14d965db
|
||
|
|
e85c613e7d
|
||
|
|
b8a4d2d665
|
||
|
|
7ee0c2d272
|
||
|
|
8359d6e4bf
|
||
|
|
3d33972e98
|
||
|
|
5768604ac9
|
||
|
|
07ef691bc1
|
||
|
|
4d110b7c52
|
||
|
|
5f4be34a73
|
||
|
|
2a16a866ed
|
||
|
|
e96e9dc46a
|
||
|
|
b4d2e387da
|
||
|
|
e8820fde59
|
||
|
|
906aa81c6c
|
||
|
|
9855f91adf
|
@@ -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
|
||||||
@@ -6,15 +6,44 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
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/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- '.gitea/workflows/main.yaml'
|
||||||
|
|
||||||
release-and-publish:
|
release-and-publish:
|
||||||
name: Build, Publish Packages (Stable) & Create Release
|
name: Build, Publish Packages (Stable) & Create Release
|
||||||
|
needs: detect-changes
|
||||||
|
if: needs.detect-changes.outputs.code_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Install Rust Toolchain
|
- name: Install Rust Toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v2
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
cache: false
|
cache: false
|
||||||
@@ -30,18 +59,50 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
cargo-${{ runner.os }}-
|
cargo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install Cross-Compilation Toolchains & Dependencies
|
- 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: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||||
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
|
||||||
|
|
||||||
- name: Install Packaging Tools (Prebuilt Binaries)
|
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
|
||||||
|
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Cache Rustup Cross-Compilation-Targets
|
||||||
|
id: cache-rustup-targets
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
|
||||||
|
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
|
||||||
|
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
|
||||||
|
|
||||||
|
- name: Add Rust Cross-Compilation Targets
|
||||||
|
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
|
||||||
|
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||||
|
|
||||||
|
- name: PATH um Cargo-bin-Verzeichnis ergänzen
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.cargo/bin
|
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"
|
||||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
~/.cargo/bin/cargo-binstall -y --no-symlinks cargo-deb cargo-generate-rpm
|
- 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
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ name: Renovate
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 4 * * 1"
|
- cron: "0 * * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
name: Dependency-Updates prüfen & Pull Requests erstellen
|
name: Dependency-Updates prüfen & Pull Requests erstellen
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/renovatebot/renovate:44.79.2
|
container: ghcr.io/renovatebot/renovate:44.83.0
|
||||||
steps:
|
steps:
|
||||||
- name: Renovate ausführen
|
- name: Renovate ausführen
|
||||||
run: renovate
|
run: renovate
|
||||||
@@ -19,7 +19,9 @@ jobs:
|
|||||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
|
RENOVATE_REPOSITORIES: ${{ gitea.repository || github.repository }}
|
||||||
RENOVATE_AUTODISCOVER: "false"
|
RENOVATE_AUTODISCOVER: "false"
|
||||||
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creative-dragonslayer.de>"
|
RENOVATE_ALLOW_CUSTOM_CRATE_REGISTRIES: "true"
|
||||||
|
RENOVATE_GIT_AUTHOR: "Renovate Bot <renovate-bot@creativedragonslayer.de>"
|
||||||
RENOVATE_HOST_RULES: >-
|
RENOVATE_HOST_RULES: >-
|
||||||
[{"hostType":"cargo","matchHost":"gitea.creative-dragonslayer.de","token":"${{ secrets.RENOVATE_TOKEN }}"}]
|
[{"hostType":"cargo","matchHost":"${{ gitea.server_url || github.server_url }}","token":"${{ secrets.RENOVATE_TOKEN }}"}]
|
||||||
|
GITHUB_COM_TOKEN: ${{ secrets.GH_RENOVATE_TOKEN }}
|
||||||
LOG_LEVEL: info
|
LOG_LEVEL: info
|
||||||
|
|||||||
@@ -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}"
|
||||||
@@ -6,15 +6,44 @@ on:
|
|||||||
- testing
|
- testing
|
||||||
|
|
||||||
jobs:
|
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/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
- '.gitea/workflows/testing.yaml'
|
||||||
|
|
||||||
build-and-publish:
|
build-and-publish:
|
||||||
name: Build, Publish Packages (Testing) & Create Preview Release
|
name: Build, Publish Packages (Testing) & Create Preview Release
|
||||||
|
needs: detect-changes
|
||||||
|
if: needs.detect-changes.outputs.code_changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
- name: Install Rust Toolchain
|
- name: Install Rust Toolchain
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v2
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
cache: false
|
cache: false
|
||||||
@@ -30,18 +59,50 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
cargo-${{ runner.os }}-
|
cargo-${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install Cross-Compilation Toolchains & Dependencies
|
- 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: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
sudo apt-get install -y gcc-aarch64-linux-gnu gcc-i686-linux-gnu
|
||||||
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
|
||||||
|
|
||||||
- name: Install Packaging Tools (Prebuilt Binaries)
|
- name: Ermittle Rust-Version für Rustup-Target-Cache-Key
|
||||||
|
run: echo "RUST_VERSION=$(rustc --version | awk '{print $2}')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Cache Rustup Cross-Compilation-Targets
|
||||||
|
id: cache-rustup-targets
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/aarch64-unknown-linux-gnu
|
||||||
|
~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/i686-unknown-linux-gnu
|
||||||
|
key: rustup-targets-${{ runner.os }}-${{ env.RUST_VERSION }}
|
||||||
|
|
||||||
|
- name: Add Rust Cross-Compilation Targets
|
||||||
|
if: steps.cache-rustup-targets.outputs.cache-hit != 'true'
|
||||||
|
run: rustup target add aarch64-unknown-linux-gnu i686-unknown-linux-gnu
|
||||||
|
|
||||||
|
- name: PATH um Cargo-bin-Verzeichnis ergänzen
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.cargo/bin
|
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"
|
||||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
|
||||||
~/.cargo/bin/cargo-binstall -y --no-symlinks cargo-deb cargo-generate-rpm
|
- 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
|
- name: Run Tests
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -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,85 @@
|
|||||||
|
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, src/ oder Dockerfile
|
||||||
|
uses: dorny/paths-filter@v4
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
base: ${{ gitea.base_ref || github.base_ref }}
|
||||||
|
filters: |
|
||||||
|
code:
|
||||||
|
- 'Cargo.toml'
|
||||||
|
- 'Cargo.lock'
|
||||||
|
- 'src/**'
|
||||||
|
- 'Dockerfile'
|
||||||
|
|
||||||
|
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 }}
|
||||||
Generated
+3
@@ -26,6 +26,9 @@
|
|||||||
<Item>
|
<Item>
|
||||||
<option name="path" value=".gitea/workflows/trufflehog-scan.yaml" />
|
<option name="path" value=".gitea/workflows/trufflehog-scan.yaml" />
|
||||||
</Item>
|
</Item>
|
||||||
|
<Item>
|
||||||
|
<option name="path" value=".gitea/workflows/code-quality.yaml" />
|
||||||
|
</Item>
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</SchemaInfo>
|
</SchemaInfo>
|
||||||
|
|||||||
@@ -31,8 +31,12 @@ Dieses Dokument dient als technischer Leitfaden und Kontextdokument für KI-Codi
|
|||||||
│ └── config.toml # Linker für Cross-Target-Kompilierung & Registry-Konfiguration
|
│ └── config.toml # Linker für Cross-Target-Kompilierung & Registry-Konfiguration
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ ├── main.yaml # CI/CD: Stabile Builds, Multi-Arch-Paketierung, Release & Upload
|
│ ├── main.yaml # CI/CD: Stabile Builds, Multi-Arch-Paketierung, Release & Upload
|
||||||
│ └── testing.yaml # CI/CD: Preview-Builds & Testing-Pakete
|
│ ├── testing.yaml # CI/CD: Preview-Builds & Testing-Pakete
|
||||||
|
│ ├── unit-tests.yaml # CI: cargo test bei PRs mit Ziel-Branch testing
|
||||||
|
│ ├── security-scan.yaml # CI: Trivy (vuln/secret/misconfig) & OSV-Scanner
|
||||||
|
│ ├── trufflehog-scan.yaml # CI: TruffleHog Secret-Scanning
|
||||||
|
│ └── renovate.yaml # CI: Renovate Dependency-Updates (self-hosted, wöchentlich)
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── get-build-number.py # Ermittelt automatisch die nächste Revisions-/Build-Nummer
|
│ ├── get-build-number.py # Ermittelt automatisch die nächste Revisions-/Build-Nummer
|
||||||
│ └── package-arch.py # Erzeugt native Arch Linux .pkg.tar.zst Pakete
|
│ └── package-arch.py # Erzeugt native Arch Linux .pkg.tar.zst Pakete
|
||||||
@@ -52,6 +56,7 @@ Dieses Dokument dient als technischer Leitfaden und Kontextdokument für KI-Codi
|
|||||||
├── Dockerfile # Minimales & gehärtetes Runtime-Container-Image
|
├── Dockerfile # Minimales & gehärtetes Runtime-Container-Image
|
||||||
├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose
|
├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose
|
||||||
├── Cargo.toml # Projekt-Manifest & Metadaten für deb, rpm und arch
|
├── Cargo.toml # Projekt-Manifest & Metadaten für deb, rpm und arch
|
||||||
|
├── renovate.json # Renovate-Konfiguration (baseBranches, Gruppierung, Custom-Manager)
|
||||||
├── LICENSE # Lizenztext
|
├── LICENSE # Lizenztext
|
||||||
├── README.md # Benutzerdokumentation
|
├── README.md # Benutzerdokumentation
|
||||||
└── AGENTS.md # Dieses Agenten-Handbuch
|
└── AGENTS.md # Dieses Agenten-Handbuch
|
||||||
@@ -77,7 +82,7 @@ Bei Änderungen an Binärnamen, Abhängigkeiten oder Beschreibungen müssen die
|
|||||||
- `pkgrel`, `arch`, `depends`, `optdepends`.
|
- `pkgrel`, `arch`, `depends`, `optdepends`.
|
||||||
|
|
||||||
### 3.3 Skripte in `scripts/`
|
### 3.3 Skripte in `scripts/`
|
||||||
- **Generizität**: Die Skripte dürfen keine hardcodierten Anwendungsnamen, spezifischen Abhängigkeiten oder projektspezifischen URLs enthalten. Alle Werte müssen dynamisch aus `Cargo.toml` oder Umgebungsvariablen (`BUILD_NUMBER`, `GITEA_URL`, `REPO`, `TOKEN`) ermittelt werden.
|
- **Generizität**: Die Skripte dürfen keine hardcodierten Anwendungsnamen, spezifischen Abhängigkeiten oder projektspezifischen URLs enthalten. Alle Werte müssen dynamisch aus `Cargo.toml` oder Umgebungsvariablen (`BUILD_NUMBER`, `GITEA_URL`, `REPO`, `REPO_OWNER`, `TOKEN`) ermittelt werden.
|
||||||
- **Python-Kompatibilität**: Verwende Standard-Python 3 ohne externe PyPI-Abhängigkeiten (nur Standardbibliothek).
|
- **Python-Kompatibilität**: Verwende Standard-Python 3 ohne externe PyPI-Abhängigkeiten (nur Standardbibliothek).
|
||||||
|
|
||||||
### 3.4 Container-Sicherheit & Persistenz
|
### 3.4 Container-Sicherheit & Persistenz
|
||||||
@@ -119,8 +124,33 @@ python3 scripts/package-arch.py --arch x86_64 --pkgrel 1
|
|||||||
|
|
||||||
## 5. CI/CD-Pipeline Details
|
## 5. CI/CD-Pipeline Details
|
||||||
|
|
||||||
- **Trigger**:
|
### 5.1 Branch-Flow & Trigger
|
||||||
- `push` auf `main`: Baut Binaries für alle 3 Architekturen, baut `.deb`, `.rpm` und `.pkg.tar.zst`, lädt sie in die Gitea Package Registry hoch, erstellt ein Gitea Release `v<VERSION>` und baut/veröffentlicht das Docker-Container-Image mit den Tags `:latest`, `:<VERSION>`, `:v<VERSION>` und `:<VERSION>.<BUILD_NUMBER>`.
|
Promotion-Flow: `dev` → `testing` → `main`, ausschließlich per Merge (nie direkt gepusht).
|
||||||
- `push` auf `testing`: Baut Binaries & Pakete für den `testing`-Kanal, erstellt ein Pre-Release `v<VERSION>-preview` und baut/veröffentlicht das Docker-Container-Image ausschließlich mit eindeutigen Testing-Tags (`:testing`, `:<VERSION>-preview`, `:<VERSION>-testing`, `:<VERSION>-preview.<BUILD_NUMBER>`, `:testing-<BUILD_NUMBER>`). Der Tag `:latest` ist strikt dem `main`-Workflow vorbehalten.
|
|
||||||
- **Secrets**:
|
- `pull_request` mit Ziel-Branch `testing` (`unit-tests.yaml`): führt `cargo test` aus (`types: opened, synchronize, reopened`).
|
||||||
- `PACKAGE_TOKEN` (bzw. Fallback-Token-Namen wie `RELEASE_TOKEN`, `GITEA_TOKEN`) wird für API-Zugriffe auf Gitea Packages, Container Registry und Releases verwendet.
|
- `push` auf `main` (`main.yaml`): Baut Binaries für alle 3 Architekturen, baut `.deb`, `.rpm` und `.pkg.tar.zst`, lädt sie in die Gitea Package Registry hoch, erstellt ein Gitea Release `v<VERSION>` und baut/veröffentlicht das Docker-Container-Image mit den Tags `:latest`, `:<VERSION>`, `:v<VERSION>` und `:<VERSION>.<BUILD_NUMBER>`.
|
||||||
|
- `push` auf `testing` (`testing.yaml`): Baut Binaries & Pakete für den `testing`-Kanal, erstellt ein Pre-Release `v<VERSION>-preview` und baut/veröffentlicht das Docker-Container-Image ausschließlich mit eindeutigen Testing-Tags (`:testing`, `:<VERSION>-preview`, `:<VERSION>-testing`, `:<VERSION>-preview.<BUILD_NUMBER>`, `:testing-<BUILD_NUMBER>`). Der Tag `:latest` ist strikt dem `main`-Workflow vorbehalten.
|
||||||
|
- `push`/`pull_request`/wöchentlich (`security-scan.yaml`, `trufflehog-scan.yaml`): Trivy (vuln/secret/misconfig) & OSV-Scanner laufen bei Push auf `main`/`testing`/`dev` sowie bei jedem PR; TruffleHog hat **keinen** `branches`-Filter und läuft bei Push auf **jeden** Branch (zusätzlich bei jedem PR). Funde werden per `scripts/report-security-issue.py` als Gitea-Issue gemeldet.
|
||||||
|
- stündlich, `workflow_dispatch` (`renovate.yaml`): Renovate (containerisiert via `ghcr.io/renovatebot/renovate`) prüft Dependency-Updates, siehe 5.5. Der stündliche CI-Lauf steuert nur, wie schnell Renovate reagiert (z.B. auf Dependency-Dashboard-Checkboxen); tatsächliche neue Update-PRs entstehen weiterhin nur innerhalb des in `renovate.json` gesetzten `schedule` (Montag vor 6 Uhr).
|
||||||
|
|
||||||
|
### 5.2 Caching (`actions/cache@v6`)
|
||||||
|
`main.yaml`/`testing.yaml` cachen mehrere Verzeichnisse, um wiederholte Cross-Compile-Builds zu beschleunigen:
|
||||||
|
- `~/.cargo/registry`, `~/.cargo/git`, `target` – Cache-Key basiert auf `hashFiles('Cargo.lock')`.
|
||||||
|
- `~/.rustup/.../lib/rustlib/<target>` für die zwei zusätzlichen Cross-Targets – Cache-Key basiert auf der **aufgelösten** `rustc --version`, nicht auf dem gleitenden `stable`-Label. Sonst könnte nach einem Rust-Update eine veraltete gecachte Std-Lib mit einem neueren Compiler kombiniert werden.
|
||||||
|
- `~/.cargo/bin/cargo-binstall`, `~/.cargo/bin/cargo-deb`, `~/.cargo/bin/cargo-generate-rpm` (gezielt die Binaries statt des gesamten Verzeichnisses, um alte Rust-Compiler-Proxys bei Toolchain-Updates nicht wiederherzustellen) – alle drei sind auf feste Versionen gepinnt (Job-`env`), nicht auf `latest`.
|
||||||
|
|
||||||
|
**Wichtige Falle:** `target/debian`, `target/generate-rpm` und `target/arch` hängen ebenfalls unter `target` und werden dadurch mitgecacht, aber von keinem Tool automatisch geleert. Vor jedem Paketbau werden sie daher explizit per `rm -rf` bereinigt – sonst werden alte, bereits hochgeladene Paket-Dateien aus früheren Builds erneut mit hochgeladen, und die Gitea Package Registry lehnt sie mit `409 Conflict` ab (Paket-Dateien sind dort unveränderlich). Bei neuen Paketierungs-Outputs außerhalb dieser drei Ordner muss diese Bereinigung entsprechend erweitert werden.
|
||||||
|
|
||||||
|
### 5.3 Build-Nummer (`scripts/get-build-number.py`)
|
||||||
|
Pro CI-Lauf wird genau **eine** Build-Nummer ermittelt und identisch an `cargo deb`, `cargo generate-rpm` und `package-arch.py --pkgrel` weitergereicht – `.deb`, `.rpm` und Arch-Paket tragen also immer dieselbe Nummer. Zur Ermittlung wird pro Paket-Typ (`debian`, `rpm`, `arch`) gezielt `GET /api/v1/packages/{owner}/{type}/{name}/-/latest` abgefragt (ein Request pro Typ, kein Paging, unbeeinflusst von Docker-Tags/anderen Paketen desselben Owners); das Maximum aller drei Typen + 1 ergibt die neue Nummer. Eine pauschale, ungefilterte Abfrage über alle Pakete des Owners (`GET /packages/{owner}`) darf hier nicht mehr verwendet werden, da sie durch Docker-Image-Tags & Co. verdrängt werden kann.
|
||||||
|
|
||||||
|
### 5.4 Secrets
|
||||||
|
- `PACKAGE_TOKEN` (bzw. Fallback-Token-Namen wie `RELEASE_TOKEN`, `GITEA_TOKEN`) wird für API-Zugriffe auf Gitea Packages, Container Registry und Releases verwendet.
|
||||||
|
- `SECURITY_TOKEN` für die Security-Scan-Workflows (Gitea-Issue-Erstellung).
|
||||||
|
- `RENOVATE_TOKEN` für den Renovate-Workflow.
|
||||||
|
- `GH_RENOVATE_TOKEN` (optional, Renovate-Workflow, als `GITHUB_COM_TOKEN` an Renovate durchgereicht) – GitHub-PAT (Public-Repo-Read genügt), damit Renovate Release-Infos für GitHub-gehostete Dependencies (u.a. `github-actions`-Manager) authentifiziert statt rate-limitiert abfragt.
|
||||||
|
|
||||||
|
### 5.5 Renovate (`renovate.json`)
|
||||||
|
- `baseBranches: ["dev"]` – Renovate liest Dependency-Dateien ausschließlich von `dev` und öffnet PRs nur dort, passend zum `dev → testing → main`-Promotion-Flow. Die Konfigurationsdatei selbst muss trotzdem über den Gitea-Default-Branch auffindbar sein.
|
||||||
|
- Drei Gruppen (`packageRules`), jeweils mit `separateMajorMinor: false`/`separateMinorPatch: false` (sonst reißt Renovate Major-Updates trotz `groupName` standardmäßig in einen eigenen PR): "Gitea Actions" (dateibasiert über `matchFileNames: [".gitea/workflows/**"]`, deckt auch die Custom-Manager unten ab), "Cargo Dependencies", "Docker-Images".
|
||||||
|
- `customManagers` (Regex) tracken Versionen, die als reine Strings in `run:`-Blöcken stecken und vom `github-actions`-Manager nicht erkannt werden: `TRIVY_VERSION`, `OSV_SCANNER_VERSION`, `TRUFFLEHOG_VERSION`, `CARGO_BINSTALL_VERSION`, `CARGO_DEB_VERSION`, `CARGO_GENERATE_RPM_VERSION`. Wird in einer Workflow-Datei eine weitere Tool-Version nach demselben Muster (`NAME_VERSION: "x.y.z"`) gepinnt, muss hier ein passender Eintrag ergänzt werden, sonst bleibt sie von Renovate unbemerkt veraltet.
|
||||||
|
|||||||
Generated
+19
-13
@@ -93,6 +93,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.13.1"
|
version = "2.13.1"
|
||||||
@@ -213,9 +219,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config-ctdra"
|
name = "config-ctdra"
|
||||||
version = "1.0.4"
|
version = "1.0.6"
|
||||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||||
checksum = "f7b3e25b95d4cdb6fc856f2af0106d4558b882c2f10798db46181e51827749a4"
|
checksum = "83eb23d473fce79e8234ad66baf210289b5c9e4c4a587273b4df7deceb73f416"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"confy",
|
"confy",
|
||||||
"program-ctdra",
|
"program-ctdra",
|
||||||
@@ -316,7 +322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.189",
|
"libc 0.2.189",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -565,7 +571,7 @@ version = "0.1.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -827,9 +833,9 @@ checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "logger-ctdra"
|
name = "logger-ctdra"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
source = "sparse+https://gitea.creative-dragonslayer.de/api/packages/Rust-Crates/cargo/"
|
||||||
checksum = "af9ea239d80163c80b12cae5f74c0c2824199921dedafba74678e96a11a8b15e"
|
checksum = "ce612b7d943b77060fa36eab0c85782e650c4cf023e9d560bef791951ed92cad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"program-ctdra",
|
"program-ctdra",
|
||||||
"time",
|
"time",
|
||||||
@@ -866,7 +872,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mirror-package"
|
name = "mirror-package"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -1035,7 +1041,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1090,11 +1096,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.4"
|
version = "0.13.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.23.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -1214,7 +1220,7 @@ dependencies = [
|
|||||||
"security-framework",
|
"security-framework",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
"webpki-root-certs",
|
"webpki-root-certs",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1892,7 +1898,7 @@ version = "0.1.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mirror-package"
|
name = "mirror-package"
|
||||||
version = "1.0.3"
|
version = "1.0.5"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ['DragonSlayer_14']
|
authors = ['DragonSlayer_14']
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# mirror-package
|
# mirror-package
|
||||||
|
|
||||||
|
[](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=main.yaml)
|
||||||
|
[](https://gitea.creative-dragonslayer.de/Linuxapps/MirrorPackage/actions?workflow=testing.yaml)
|
||||||
|
|
||||||
Automatisiertes Werkzeug zum Extrahieren, Herunterladen und Spiegeln vorkompilierter Linux-Pakete aus GitHub-Releases in eine selbstgehostete Gitea- / Forgejo-Paket-Registry.
|
Automatisiertes Werkzeug zum Extrahieren, Herunterladen und Spiegeln vorkompilierter Linux-Pakete aus GitHub-Releases in eine selbstgehostete Gitea- / Forgejo-Paket-Registry.
|
||||||
|
|
||||||
`mirror-package` überwacht konfigurierte GitHub-Repositories (wie z. B. `raspberrypi/rpi-imager` oder `Heroic-Games-Launcher/HeroicGamesLauncher`), identifiziert vorkompilierte Linux-Pakete (`.deb`, `.rpm`, `.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`) und veröffentlicht diese anhand von Release-Stabilitätsregeln automatisch in den passenden Distributionen der Gitea-Paket-Registry.
|
`mirror-package` überwacht konfigurierte GitHub-Repositories (wie z. B. `raspberrypi/rpi-imager` oder `Heroic-Games-Launcher/HeroicGamesLauncher`), identifiziert vorkompilierte Linux-Pakete (`.deb`, `.rpm`, `.pkg.tar.zst`, `.pkg.tar.xz`, `.pkg.tar.gz`, `.pacman`) und veröffentlicht diese anhand von Release-Stabilitätsregeln automatisch in den passenden Distributionen der Gitea-Paket-Registry.
|
||||||
@@ -199,8 +202,12 @@ services:
|
|||||||
│ └── config.toml # Linker- & Cargo-Konfiguration
|
│ └── config.toml # Linker- & Cargo-Konfiguration
|
||||||
├── .gitea/
|
├── .gitea/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ ├── main.yaml # CI/CD: Release, Pakete & Container (Stable)
|
│ ├── main.yaml # CI/CD: Release, Pakete & Container (Stable)
|
||||||
│ └── testing.yaml # CI/CD: Preview, Pakete & Container (Testing)
|
│ ├── testing.yaml # CI/CD: Preview, Pakete & Container (Testing)
|
||||||
|
│ ├── unit-tests.yaml # CI: cargo test bei PRs gegen testing
|
||||||
|
│ ├── security-scan.yaml # CI: Trivy & OSV-Scanner
|
||||||
|
│ ├── trufflehog-scan.yaml # CI: TruffleHog Secret-Scanning
|
||||||
|
│ └── renovate.yaml # CI: automatisierte Abhängigkeits-Updates (Renovate)
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ ├── get-build-number.py # Dynamische Ermittlung der Build-/Revisionsnummer
|
│ ├── get-build-number.py # Dynamische Ermittlung der Build-/Revisionsnummer
|
||||||
│ └── package-arch.py # Erstellung nativer Arch Linux-Pakete
|
│ └── package-arch.py # Erstellung nativer Arch Linux-Pakete
|
||||||
@@ -220,6 +227,7 @@ services:
|
|||||||
├── Dockerfile # Gehärtetes, minimales Runtime-Container-Image
|
├── Dockerfile # Gehärtetes, minimales Runtime-Container-Image
|
||||||
├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose
|
├── docker-compose.example.yml # Beispielkonfiguration für Docker Compose
|
||||||
├── Cargo.toml # Projekt-Manifest und Paketierungs-Metadaten
|
├── Cargo.toml # Projekt-Manifest und Paketierungs-Metadaten
|
||||||
|
├── renovate.json # Renovate-Konfiguration für automatisierte Abhängigkeits-Updates
|
||||||
├── LICENSE # GPL-3.0-or-later Lizenztext
|
├── LICENSE # GPL-3.0-or-later Lizenztext
|
||||||
├── AGENTS.md # Agenten- & Entwickler-Richtlinien
|
├── AGENTS.md # Agenten- & Entwickler-Richtlinien
|
||||||
└── README.md # Projektdokumentation
|
└── README.md # Projektdokumentation
|
||||||
@@ -248,6 +256,15 @@ cargo build --release
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## CI/CD & Contributing
|
||||||
|
|
||||||
|
- **Branch-Flow**: Änderungen durchlaufen `dev` → `testing` → `main`, jeweils per Merge (nie direkt gepusht).
|
||||||
|
- **Pull Requests gegen `testing`** lösen automatisch `cargo test` aus.
|
||||||
|
- **Sicherheits-Scans**: Trivy & OSV-Scanner laufen bei Push auf `main`/`testing`/`dev` sowie bei jedem Pull Request; TruffleHog läuft bei Push auf **jeden** Branch (kein `branches`-Filter) sowie ebenfalls bei jedem Pull Request. Funde werden als Gitea-Issue gemeldet.
|
||||||
|
- **Abhängigkeits-Updates** werden automatisiert über [Renovate](https://docs.renovatebot.com/) als PRs gegen `dev` vorgeschlagen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
Dieses Projekt ist unter der [GPL-3.0-or-later](LICENSE)-Lizenz lizenziert.
|
Dieses Projekt ist unter der [GPL-3.0-or-later](LICENSE)-Lizenz lizenziert.
|
||||||
|
|||||||
+46
-4
@@ -3,7 +3,9 @@
|
|||||||
"extends": ["config:recommended"],
|
"extends": ["config:recommended"],
|
||||||
"timezone": "Europe/Berlin",
|
"timezone": "Europe/Berlin",
|
||||||
"schedule": ["before 6am on monday"],
|
"schedule": ["before 6am on monday"],
|
||||||
"baseBranches": ["dev"],
|
"baseBranchPatterns": [
|
||||||
|
"dev"
|
||||||
|
],
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
"matchFileNames": [".gitea/workflows/**"],
|
"matchFileNames": [".gitea/workflows/**"],
|
||||||
@@ -27,7 +29,9 @@
|
|||||||
"customManagers": [
|
"customManagers": [
|
||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"fileMatch": ["^\\.gitea/workflows/.+\\.ya?ml$"],
|
"managerFilePatterns": [
|
||||||
|
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||||
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
"TRIVY_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||||
],
|
],
|
||||||
@@ -37,7 +41,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"fileMatch": ["^\\.gitea/workflows/.+\\.ya?ml$"],
|
"managerFilePatterns": [
|
||||||
|
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||||
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
"OSV_SCANNER_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||||
],
|
],
|
||||||
@@ -47,13 +53,49 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"customType": "regex",
|
"customType": "regex",
|
||||||
"fileMatch": ["^\\.gitea/workflows/.+\\.ya?ml$"],
|
"managerFilePatterns": [
|
||||||
|
"/^\\.gitea/workflows/.+\\.ya?ml$/"
|
||||||
|
],
|
||||||
"matchStrings": [
|
"matchStrings": [
|
||||||
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
"TRUFFLEHOG_VERSION:\\s*\"(?<currentValue>[^\"]+)\""
|
||||||
],
|
],
|
||||||
"depNameTemplate": "trufflesecurity/trufflehog",
|
"depNameTemplate": "trufflesecurity/trufflehog",
|
||||||
"datasourceTemplate": "github-releases",
|
"datasourceTemplate": "github-releases",
|
||||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-10
@@ -9,9 +9,14 @@ import os
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
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():
|
def get_cargo_pkg_info():
|
||||||
try:
|
try:
|
||||||
@@ -83,20 +88,29 @@ def query_existing_build_numbers(name, version, gitea_url, repo, owner, token=No
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
sys.stderr.write(f"[Hinweis] Konnte Gitea Releases nicht abfragen: {e}\n")
|
sys.stderr.write(f"[Hinweis] Konnte Gitea Releases nicht abfragen: {e}\n")
|
||||||
|
|
||||||
# 2. Gitea Packages API (falls Token vorhanden)
|
# 2. Gitea Packages API (falls Token vorhanden): pro Paket-Typ gezielt nur die
|
||||||
if gitea_url and owner and token:
|
# neueste Version abfragen (ein Request, keine Pagination noetig). Das haelt
|
||||||
try:
|
# die Abfrage schnell unabhaengig von der Historie und ist unbeeinflusst von
|
||||||
url = f"{gitea_url.rstrip('/')}/api/v1/packages/{owner}?limit=50"
|
# Docker-Tags/anderen Paket-Typen, die unter demselben Owner haengen.
|
||||||
req = urllib.request.Request(url, headers=headers)
|
if gitea_url and owner and token and name:
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
for pkg_type in PACKAGE_TYPES:
|
||||||
packages = json.loads(resp.read().decode())
|
try:
|
||||||
for pkg in packages:
|
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", "")
|
pkg_ver = pkg.get("version", "")
|
||||||
m = pattern.search(pkg_ver)
|
m = pattern.search(pkg_ver)
|
||||||
if m:
|
if m:
|
||||||
build_nums.add(int(m.group(1)))
|
build_nums.add(int(m.group(1)))
|
||||||
except Exception as e:
|
except urllib.error.HTTPError as e:
|
||||||
sys.stderr.write(f"[Hinweis] Konnte Gitea Packages nicht abfragen: {e}\n")
|
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
|
# 3. Lokales Target-Verzeichnis prüfen
|
||||||
target_dirs = ["target/debian", "target/generate-rpm", "target/arch"]
|
target_dirs = ["target/debian", "target/generate-rpm", "target/arch"]
|
||||||
|
|||||||
@@ -253,9 +253,9 @@ def main():
|
|||||||
osv_path = sys.argv[2] if len(sys.argv) > 2 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
|
trufflehog_path = sys.argv[3] if len(sys.argv) > 3 else None
|
||||||
|
|
||||||
gitea_url = os.environ["GITEA_URL"].rstrip("/")
|
gitea_url = os.environ.get("GITEA_URL", "").strip().rstrip("/")
|
||||||
repo = os.environ["REPO"]
|
repo = os.environ.get("REPO", "").strip()
|
||||||
token = os.environ["TOKEN"]
|
token = os.environ.get("TOKEN", "").strip()
|
||||||
run_url = os.environ.get("RUN_URL", "")
|
run_url = os.environ.get("RUN_URL", "")
|
||||||
osv_exit = int(os.environ.get("OSV_EXIT", "0"))
|
osv_exit = int(os.environ.get("OSV_EXIT", "0"))
|
||||||
trufflehog_exit = int(os.environ.get("TRUFFLEHOG_EXIT", "0"))
|
trufflehog_exit = int(os.environ.get("TRUFFLEHOG_EXIT", "0"))
|
||||||
@@ -263,28 +263,32 @@ def main():
|
|||||||
findings = sort_findings(load_trivy(trivy_path) + load_osv(osv_path) + load_trufflehog(trufflehog_path))
|
findings = sort_findings(load_trivy(trivy_path) + load_osv(osv_path) + load_trufflehog(trufflehog_path))
|
||||||
print_summary(findings)
|
print_summary(findings)
|
||||||
|
|
||||||
open_issue = find_open_issue(token, gitea_url, repo)
|
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]
|
||||||
if findings:
|
print(f"{', '.join(missing)} nicht gesetzt oder leer – überspringe Gitea-Issue-Synchronisation.")
|
||||||
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:
|
else:
|
||||||
print("Keine Funde und kein offenes Issue vorhanden.")
|
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}
|
osv_ok_exits = {0, 1}
|
||||||
trufflehog_ok_exits = {0, 183}
|
trufflehog_ok_exits = {0, 183}
|
||||||
|
|||||||
+15
-4
@@ -59,7 +59,11 @@ impl AppConfig {
|
|||||||
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
/// Fügt ein Repository zur Konfiguration hinzu oder aktualisiert ein bestehendes.
|
||||||
pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) {
|
pub fn add_or_update_repo(&mut self, mut repo: RepoConfig) {
|
||||||
repo.name = Self::normalize_repo_name(&repo.name);
|
repo.name = Self::normalize_repo_name(&repo.name);
|
||||||
if let Some(existing) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name)) {
|
if let Some(existing) = self
|
||||||
|
.repositories
|
||||||
|
.iter_mut()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&repo.name))
|
||||||
|
{
|
||||||
existing.name = repo.name;
|
existing.name = repo.name;
|
||||||
existing.include_prereleases = repo.include_prereleases;
|
existing.include_prereleases = repo.include_prereleases;
|
||||||
} else {
|
} else {
|
||||||
@@ -71,20 +75,27 @@ impl AppConfig {
|
|||||||
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
pub fn remove_repo(&mut self, repo_name: &str) -> bool {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
let before_len = self.repositories.len();
|
let before_len = self.repositories.len();
|
||||||
self.repositories.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized));
|
self.repositories
|
||||||
|
.retain(|r| !Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized));
|
||||||
self.repositories.len() < before_len
|
self.repositories.len() < before_len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sucht ein Repository anhand des Namens.
|
/// Sucht ein Repository anhand des Namens.
|
||||||
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
pub fn find_repo(&self, repo_name: &str) -> Option<&RepoConfig> {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
self.repositories.iter().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
self.repositories
|
||||||
|
.iter()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository.
|
/// Aktualisiert den zuletzt synchronisierten Tag für ein bestimmtes Repository.
|
||||||
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
pub fn update_last_synced_tag(&mut self, repo_name: &str, tag: String) {
|
||||||
let normalized = Self::normalize_repo_name(repo_name);
|
let normalized = Self::normalize_repo_name(repo_name);
|
||||||
if let Some(repo) = self.repositories.iter_mut().find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized)) {
|
if let Some(repo) = self
|
||||||
|
.repositories
|
||||||
|
.iter_mut()
|
||||||
|
.find(|r| Self::normalize_repo_name(&r.name).eq_ignore_ascii_case(&normalized))
|
||||||
|
{
|
||||||
repo.last_synced_tag = Some(tag);
|
repo.last_synced_tag = Some(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-7
@@ -1,5 +1,5 @@
|
|||||||
use crate::github::PackageType;
|
use crate::github::PackageType;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
|
|
||||||
@@ -12,7 +12,11 @@ pub struct GiteaConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GiteaConfig {
|
impl GiteaConfig {
|
||||||
pub fn new(base_url: impl Into<String>, token: impl Into<String>, owner: impl Into<String>) -> Self {
|
pub fn new(
|
||||||
|
base_url: impl Into<String>,
|
||||||
|
token: impl Into<String>,
|
||||||
|
owner: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
let mut base_url = base_url.into();
|
let mut base_url = base_url.into();
|
||||||
while base_url.ends_with('/') {
|
while base_url.ends_with('/') {
|
||||||
base_url.pop();
|
base_url.pop();
|
||||||
@@ -44,17 +48,29 @@ pub fn get_target_upload_urls(
|
|||||||
match pkg_type {
|
match pkg_type {
|
||||||
PackageType::Debian => {
|
PackageType::Debian => {
|
||||||
if prerelease {
|
if prerelease {
|
||||||
vec![format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner)]
|
vec![format!(
|
||||||
|
"{}/api/packages/{}/debian/pool/testing/main/upload",
|
||||||
|
base, owner
|
||||||
|
)]
|
||||||
} else {
|
} else {
|
||||||
vec![
|
vec![
|
||||||
format!("{}/api/packages/{}/debian/pool/stable/main/upload", base, owner),
|
format!(
|
||||||
format!("{}/api/packages/{}/debian/pool/testing/main/upload", base, owner),
|
"{}/api/packages/{}/debian/pool/stable/main/upload",
|
||||||
|
base, owner
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"{}/api/packages/{}/debian/pool/testing/main/upload",
|
||||||
|
base, owner
|
||||||
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PackageType::Rpm => {
|
PackageType::Rpm => {
|
||||||
if prerelease {
|
if prerelease {
|
||||||
vec![format!("{}/api/packages/{}/rpm/testing/upload", base, owner)]
|
vec![format!(
|
||||||
|
"{}/api/packages/{}/rpm/testing/upload",
|
||||||
|
base, owner
|
||||||
|
)]
|
||||||
} else {
|
} else {
|
||||||
vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)]
|
vec![format!("{}/api/packages/{}/rpm/stable/upload", base, owner)]
|
||||||
}
|
}
|
||||||
@@ -82,7 +98,10 @@ impl GiteaClient {
|
|||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::USER_AGENT,
|
reqwest::header::USER_AGENT,
|
||||||
reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))),
|
reqwest::header::HeaderValue::from_static(concat!(
|
||||||
|
"mirror-package/",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
|
|||||||
+14
-7
@@ -91,7 +91,10 @@ impl GitHubClient {
|
|||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::USER_AGENT,
|
reqwest::header::USER_AGENT,
|
||||||
reqwest::header::HeaderValue::from_static(concat!("mirror-package/", env!("CARGO_PKG_VERSION"))),
|
reqwest::header::HeaderValue::from_static(concat!(
|
||||||
|
"mirror-package/",
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
reqwest::header::ACCEPT,
|
reqwest::header::ACCEPT,
|
||||||
@@ -111,7 +114,6 @@ impl GitHubClient {
|
|||||||
self.token.as_deref()
|
self.token.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo").
|
/// Ruft Releases für ein angegebenes Repository ab (Format "owner/repo").
|
||||||
///
|
///
|
||||||
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
/// Wenn `history` false ist, werden nur die neuesten Releases abgerufen.
|
||||||
@@ -139,7 +141,10 @@ impl GitHubClient {
|
|||||||
req = req.bearer_auth(token);
|
req = req.bearer_auth(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = req.send().await.with_context(|| format!("Failed to send request to {}", url))?;
|
let resp = req
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to send request to {}", url))?;
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success() {
|
||||||
@@ -153,10 +158,12 @@ impl GitHubClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_releases: Vec<GhApiRelease> = resp
|
let raw_releases: Vec<GhApiRelease> = resp.json().await.with_context(|| {
|
||||||
.json()
|
format!(
|
||||||
.await
|
"Failed to parse GitHub releases JSON for {}/{}",
|
||||||
.with_context(|| format!("Failed to parse GitHub releases JSON for {}/{}", owner, name))?;
|
owner, name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if raw_releases.is_empty() {
|
if raw_releases.is_empty() {
|
||||||
break;
|
break;
|
||||||
|
|||||||
+59
-15
@@ -1,11 +1,11 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use logger_ctdra::{log, set_log_level, LogLevel};
|
use logger_ctdra::{LogLevel, log, set_log_level};
|
||||||
use mirror_package::cli::{Cli, Commands, ConfigAction};
|
use mirror_package::cli::{Cli, Commands, ConfigAction};
|
||||||
use mirror_package::config::{
|
use mirror_package::config::{
|
||||||
get_config_file_path, init_config_path, load_config, modify_config, AppConfig, RepoConfig,
|
AppConfig, RepoConfig, get_config_file_path, init_config_path, load_config, modify_config,
|
||||||
};
|
};
|
||||||
use mirror_package::pipeline::{sync_single_repository, SyncOptions, SyncReport};
|
use mirror_package::pipeline::{SyncOptions, SyncReport, sync_single_repository};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@@ -65,7 +65,9 @@ async fn main() -> Result<()> {
|
|||||||
let mut report = SyncReport::default();
|
let mut report = SyncReport::default();
|
||||||
|
|
||||||
for repo in repos_to_sync {
|
for repo in repos_to_sync {
|
||||||
if let Err(e) = sync_single_repository(&repo, &app_config, &options, &mut report).await {
|
if let Err(e) =
|
||||||
|
sync_single_repository(&repo, &app_config, &options, &mut report).await
|
||||||
|
{
|
||||||
log(
|
log(
|
||||||
"sync",
|
"sync",
|
||||||
&format!("Failed to sync repository '{}': {:#}", repo.name, e),
|
&format!("Failed to sync repository '{}': {:#}", repo.name, e),
|
||||||
@@ -128,23 +130,38 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"config",
|
"config",
|
||||||
&format!("Repository '{}' removed from configuration.", normalized_name),
|
&format!(
|
||||||
|
"Repository '{}' removed from configuration.",
|
||||||
|
normalized_name
|
||||||
|
),
|
||||||
LogLevel::Info,
|
LogLevel::Info,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Commands::List => {
|
Commands::List => {
|
||||||
println!("Configured repositories (Config file: {:?}):", get_config_file_path());
|
println!(
|
||||||
|
"Configured repositories (Config file: {:?}):",
|
||||||
|
get_config_file_path()
|
||||||
|
);
|
||||||
if app_config.repositories.is_empty() {
|
if app_config.repositories.is_empty() {
|
||||||
println!(" (None configured yet. Use 'mirror-package add <owner/repo>' to add one.)");
|
println!(
|
||||||
|
" (None configured yet. Use 'mirror-package add <owner/repo>' to add one.)"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
println!("{:<40} {:<15} {:<20}", "REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG");
|
println!(
|
||||||
|
"{:<40} {:<15} {:<20}",
|
||||||
|
"REPOSITORY", "PRE-RELEASES", "LAST SYNCED TAG"
|
||||||
|
);
|
||||||
println!("{:-<40} {:-<15} {:-<20}", "", "", "");
|
println!("{:-<40} {:-<15} {:-<20}", "", "", "");
|
||||||
for repo in &app_config.repositories {
|
for repo in &app_config.repositories {
|
||||||
println!(
|
println!(
|
||||||
"{:<40} {:<15} {:<20}",
|
"{:<40} {:<15} {:<20}",
|
||||||
repo.name,
|
repo.name,
|
||||||
if repo.include_prereleases { "yes" } else { "no" },
|
if repo.include_prereleases {
|
||||||
|
"yes"
|
||||||
|
} else {
|
||||||
|
"no"
|
||||||
|
},
|
||||||
repo.last_synced_tag.as_deref().unwrap_or("-")
|
repo.last_synced_tag.as_deref().unwrap_or("-")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,17 +171,40 @@ async fn main() -> Result<()> {
|
|||||||
Commands::Config(config_args) => match config_args.action {
|
Commands::Config(config_args) => match config_args.action {
|
||||||
None | Some(ConfigAction::Show) => {
|
None | Some(ConfigAction::Show) => {
|
||||||
println!("Configuration location: {:?}", get_config_file_path());
|
println!("Configuration location: {:?}", get_config_file_path());
|
||||||
println!("Gitea URL: {}", app_config.gitea_url.as_deref().unwrap_or("(not configured)"));
|
println!(
|
||||||
println!("Registry Owner: {}", app_config.registry_owner.as_deref().unwrap_or("(not configured)"));
|
"Gitea URL: {}",
|
||||||
|
app_config
|
||||||
|
.gitea_url
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("(not configured)")
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Registry Owner: {}",
|
||||||
|
app_config
|
||||||
|
.registry_owner
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("(not configured)")
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"Gitea Token: {}",
|
"Gitea Token: {}",
|
||||||
if app_config.gitea_token.is_some() { "******** (set)" } else { "(not configured)" }
|
if app_config.gitea_token.is_some() {
|
||||||
|
"******** (set)"
|
||||||
|
} else {
|
||||||
|
"(not configured)"
|
||||||
|
}
|
||||||
);
|
);
|
||||||
println!(
|
println!(
|
||||||
"GitHub Token: {}",
|
"GitHub Token: {}",
|
||||||
if app_config.github_token.is_some() { "******** (set)" } else { "(not configured, public access only)" }
|
if app_config.github_token.is_some() {
|
||||||
|
"******** (set)"
|
||||||
|
} else {
|
||||||
|
"(not configured, public access only)"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"Repositories: {} configured",
|
||||||
|
app_config.repositories.len()
|
||||||
);
|
);
|
||||||
println!("Repositories: {} configured", app_config.repositories.len());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(ConfigAction::Set {
|
Some(ConfigAction::Set {
|
||||||
@@ -188,7 +228,11 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
log("config", "Configuration successfully updated.", LogLevel::Info);
|
log(
|
||||||
|
"config",
|
||||||
|
"Configuration successfully updated.",
|
||||||
|
LogLevel::Info,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-26
@@ -1,11 +1,11 @@
|
|||||||
use crate::config::{modify_config, AppConfig, RepoConfig};
|
use crate::config::{AppConfig, RepoConfig, modify_config};
|
||||||
use crate::gitea::{get_target_upload_urls, GiteaClient, GiteaConfig, UploadStatus};
|
use crate::gitea::{GiteaClient, GiteaConfig, UploadStatus, get_target_upload_urls};
|
||||||
use crate::github::GitHubClient;
|
use crate::github::GitHubClient;
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use logger_ctdra::{log, LogLevel};
|
use logger_ctdra::{LogLevel, log};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::fs::{remove_file, File};
|
use tokio::fs::{File, remove_file};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
/// Zusammenfassende Statistiken eines Synchronisationsvorgangs.
|
/// Zusammenfassende Statistiken eines Synchronisationsvorgangs.
|
||||||
@@ -52,7 +52,9 @@ pub async fn sync_single_repository(
|
|||||||
let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default();
|
let gitea_token = app_config.gitea_token.as_deref().unwrap_or_default();
|
||||||
let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default();
|
let registry_owner = app_config.registry_owner.as_deref().unwrap_or_default();
|
||||||
|
|
||||||
if !options.dry_run && (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty()) {
|
if !options.dry_run
|
||||||
|
&& (gitea_url.is_empty() || gitea_token.is_empty() || registry_owner.is_empty())
|
||||||
|
{
|
||||||
bail!(
|
bail!(
|
||||||
"Missing Gitea configuration. Ensure --gitea-url, --gitea-token, and --registry-owner are provided or configured."
|
"Missing Gitea configuration. Ensure --gitea-url, --gitea-token, and --registry-owner are provided or configured."
|
||||||
);
|
);
|
||||||
@@ -61,12 +63,8 @@ pub async fn sync_single_repository(
|
|||||||
let github_client = GitHubClient::new(app_config.github_token.clone())
|
let github_client = GitHubClient::new(app_config.github_token.clone())
|
||||||
.context("Failed to initialize GitHub client")?;
|
.context("Failed to initialize GitHub client")?;
|
||||||
|
|
||||||
let gitea_client = GiteaClient::new(GiteaConfig::new(
|
let gitea_client = GiteaClient::new(GiteaConfig::new(gitea_url, gitea_token, registry_owner))
|
||||||
gitea_url,
|
.context("Failed to initialize Gitea client")?;
|
||||||
gitea_token,
|
|
||||||
registry_owner,
|
|
||||||
))
|
|
||||||
.context("Failed to initialize Gitea client")?;
|
|
||||||
|
|
||||||
let releases = github_client
|
let releases = github_client
|
||||||
.fetch_releases(repo_name, options.history, include_prereleases)
|
.fetch_releases(repo_name, options.history, include_prereleases)
|
||||||
@@ -97,7 +95,11 @@ pub async fn sync_single_repository(
|
|||||||
|
|
||||||
for release in &releases {
|
for release in &releases {
|
||||||
report.releases_processed += 1;
|
report.releases_processed += 1;
|
||||||
let release_type_str = if release.prerelease { "pre-release" } else { "stable release" };
|
let release_type_str = if release.prerelease {
|
||||||
|
"pre-release"
|
||||||
|
} else {
|
||||||
|
"stable release"
|
||||||
|
};
|
||||||
|
|
||||||
log(
|
log(
|
||||||
"sync",
|
"sync",
|
||||||
@@ -156,9 +158,10 @@ pub async fn sync_single_repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Asset in temporäre Datei herunterladen
|
// Asset in temporäre Datei herunterladen
|
||||||
let temp_file_path = download_to_temp_file(&github_client, &asset.download_url, &asset.name)
|
let temp_file_path =
|
||||||
.await
|
download_to_temp_file(&github_client, &asset.download_url, &asset.name)
|
||||||
.with_context(|| format!("Failed to download asset '{}'", asset.name))?;
|
.await
|
||||||
|
.with_context(|| format!("Failed to download asset '{}'", asset.name))?;
|
||||||
|
|
||||||
// Asset auf alle Ziel-Distributions-URLs hochladen
|
// Asset auf alle Ziel-Distributions-URLs hochladen
|
||||||
for url in &target_urls {
|
for url in &target_urls {
|
||||||
@@ -168,7 +171,10 @@ pub async fn sync_single_repository(
|
|||||||
LogLevel::Debug,
|
LogLevel::Debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
match gitea_client.upload_file(&temp_file_path, url, options.dry_run).await {
|
match gitea_client
|
||||||
|
.upload_file(&temp_file_path, url, options.dry_run)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(UploadStatus::Uploaded) => {
|
Ok(UploadStatus::Uploaded) => {
|
||||||
log(
|
log(
|
||||||
"upload",
|
"upload",
|
||||||
@@ -220,13 +226,13 @@ pub async fn sync_single_repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run
|
// Zuletzt synchronisierten Tag persistieren, wenn kein Dry-Run
|
||||||
if !options.dry_run {
|
if !options.dry_run
|
||||||
if let Some(tag) = latest_synced_tag {
|
&& let Some(tag) = latest_synced_tag
|
||||||
let name_copy = repo_name.to_string();
|
{
|
||||||
let _ = modify_config(move |cfg| {
|
let name_copy = repo_name.to_string();
|
||||||
cfg.update_last_synced_tag(&name_copy, tag);
|
let _ = modify_config(move |cfg| {
|
||||||
});
|
cfg.update_last_synced_tag(&name_copy, tag);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
report.repositories_processed += 1;
|
report.repositories_processed += 1;
|
||||||
@@ -254,7 +260,10 @@ async fn download_to_temp_file(
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
"download",
|
"download",
|
||||||
&format!("Downloading '{}' to temporary path {:?}...", filename, temp_path),
|
&format!(
|
||||||
|
"Downloading '{}' to temporary path {:?}...",
|
||||||
|
filename, temp_path
|
||||||
|
),
|
||||||
LogLevel::Debug,
|
LogLevel::Debug,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -263,7 +272,11 @@ async fn download_to_temp_file(
|
|||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to create temp file {:?}", temp_path))?;
|
.with_context(|| format!("Failed to create temp file {:?}", temp_path))?;
|
||||||
|
|
||||||
while let Some(chunk) = response.chunk().await.context("Error reading download stream")? {
|
while let Some(chunk) = response
|
||||||
|
.chunk()
|
||||||
|
.await
|
||||||
|
.context("Error reading download stream")?
|
||||||
|
{
|
||||||
file.write_all(&chunk)
|
file.write_all(&chunk)
|
||||||
.await
|
.await
|
||||||
.context("Error writing chunk to temp file")?;
|
.context("Error writing chunk to temp file")?;
|
||||||
|
|||||||
+10
-9
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/// Sanitizes a string by removing surrounding quotes (single or double).
|
/// Sanitizes a string by removing surrounding quotes (single or double).
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -12,7 +11,10 @@ pub fn sanitize_string(input: &str) -> String {
|
|||||||
let trimmed = input.trim();
|
let trimmed = input.trim();
|
||||||
if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
if let Some(stripped) = trimmed.strip_prefix('"').and_then(|s| s.strip_suffix('"')) {
|
||||||
stripped.to_string()
|
stripped.to_string()
|
||||||
} else if let Some(stripped) = trimmed.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
|
} else if let Some(stripped) = trimmed
|
||||||
|
.strip_prefix('\'')
|
||||||
|
.and_then(|s| s.strip_suffix('\''))
|
||||||
|
{
|
||||||
stripped.to_string()
|
stripped.to_string()
|
||||||
} else {
|
} else {
|
||||||
trimmed.to_string()
|
trimmed.to_string()
|
||||||
@@ -25,7 +27,7 @@ pub fn sanitize_string(input: &str) -> String {
|
|||||||
///
|
///
|
||||||
/// * `env_vars` - A reference to a mutable map of environment variables.
|
/// * `env_vars` - A reference to a mutable map of environment variables.
|
||||||
pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String>) {
|
pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String>) {
|
||||||
for (_key, value) in env_vars.iter_mut() {
|
for value in env_vars.values_mut() {
|
||||||
*value = sanitize_string(value);
|
*value = sanitize_string(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,15 +37,14 @@ pub fn sanitize_env_vars(env_vars: &mut std::collections::HashMap<String, String
|
|||||||
/// und umgebende Schrägstriche entfernt werden.
|
/// und umgebende Schrägstriche entfernt werden.
|
||||||
pub fn clean_repo_input(input: &str) -> &str {
|
pub fn clean_repo_input(input: &str) -> &str {
|
||||||
let mut trimmed = input.trim();
|
let mut trimmed = input.trim();
|
||||||
if (trimmed.starts_with('"') && trimmed.ends_with('"'))
|
if ((trimmed.starts_with('"') && trimmed.ends_with('"'))
|
||||||
|| (trimmed.starts_with('\'') && trimmed.ends_with('\''))
|
|| (trimmed.starts_with('\'') && trimmed.ends_with('\'')))
|
||||||
|
&& trimmed.len() >= 2
|
||||||
{
|
{
|
||||||
if trimmed.len() >= 2 {
|
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
||||||
trimmed = trimmed[1..trimmed.len() - 1].trim();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let without_query_or_fragment = match trimmed.find(|c| c == '?' || c == '#') {
|
let without_query_or_fragment = match trimmed.find(['?', '#']) {
|
||||||
Some(idx) => &trimmed[..idx],
|
Some(idx) => &trimmed[..idx],
|
||||||
None => trimmed,
|
None => trimmed,
|
||||||
};
|
};
|
||||||
|
|||||||
+47
-14
@@ -5,14 +5,23 @@ use std::collections::HashMap;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_env_vars() {
|
fn test_sanitize_env_vars() {
|
||||||
let mut env_vars = HashMap::new();
|
let mut env_vars = HashMap::new();
|
||||||
env_vars.insert("GITEA_URL".to_string(), "\"https://gitea.example.com\"".to_string());
|
env_vars.insert(
|
||||||
|
"GITEA_URL".to_string(),
|
||||||
|
"\"https://gitea.example.com\"".to_string(),
|
||||||
|
);
|
||||||
env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string());
|
env_vars.insert("GITEA_TOKEN".to_string(), "\"token123\"".to_string());
|
||||||
env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string());
|
env_vars.insert("REGISTRY_OWNER".to_string(), "\"owner\"".to_string());
|
||||||
env_vars.insert("GITHUB_TOKEN".to_string(), "\"github_token123\"".to_string());
|
env_vars.insert(
|
||||||
|
"GITHUB_TOKEN".to_string(),
|
||||||
|
"\"github_token123\"".to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
sanitize_env_vars(&mut env_vars);
|
sanitize_env_vars(&mut env_vars);
|
||||||
|
|
||||||
assert_eq!(env_vars.get("GITEA_URL").unwrap(), "https://gitea.example.com");
|
assert_eq!(
|
||||||
|
env_vars.get("GITEA_URL").unwrap(),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123");
|
assert_eq!(env_vars.get("GITEA_TOKEN").unwrap(), "token123");
|
||||||
assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner");
|
assert_eq!(env_vars.get("REGISTRY_OWNER").unwrap(), "owner");
|
||||||
assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123");
|
assert_eq!(env_vars.get("GITHUB_TOKEN").unwrap(), "github_token123");
|
||||||
@@ -31,7 +40,10 @@ fn test_load_config_with_env_vars() {
|
|||||||
|
|
||||||
// Only assert if the environment variables are set
|
// Only assert if the environment variables are set
|
||||||
if std::env::var("GITEA_URL").is_ok() {
|
if std::env::var("GITEA_URL").is_ok() {
|
||||||
assert_eq!(config.gitea_url, Some("https://gitea.example.com".to_string()));
|
assert_eq!(
|
||||||
|
config.gitea_url,
|
||||||
|
Some("https://gitea.example.com".to_string())
|
||||||
|
);
|
||||||
assert_eq!(config.gitea_token, Some("token123".to_string()));
|
assert_eq!(config.gitea_token, Some("token123".to_string()));
|
||||||
assert_eq!(config.registry_owner, Some("owner".to_string()));
|
assert_eq!(config.registry_owner, Some("owner".to_string()));
|
||||||
assert_eq!(config.github_token, Some("github_token123".to_string()));
|
assert_eq!(config.github_token, Some("github_token123".to_string()));
|
||||||
@@ -40,8 +52,14 @@ fn test_load_config_with_env_vars() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sanitize_string() {
|
fn test_sanitize_string() {
|
||||||
assert_eq!(sanitize_string("\"https://gitea.example.com\""), "https://gitea.example.com");
|
assert_eq!(
|
||||||
assert_eq!(sanitize_string("'https://gitea.example.com'"), "https://gitea.example.com");
|
sanitize_string("\"https://gitea.example.com\""),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
sanitize_string("'https://gitea.example.com'"),
|
||||||
|
"https://gitea.example.com"
|
||||||
|
);
|
||||||
assert_eq!(sanitize_string("\"token123\""), "token123");
|
assert_eq!(sanitize_string("\"token123\""), "token123");
|
||||||
assert_eq!(sanitize_string("'token123'"), "token123");
|
assert_eq!(sanitize_string("'token123'"), "token123");
|
||||||
assert_eq!(sanitize_string("\"owner\""), "owner");
|
assert_eq!(sanitize_string("\"owner\""), "owner");
|
||||||
@@ -68,11 +86,23 @@ fn test_sanitize_string() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_clean_repo_input() {
|
fn test_clean_repo_input() {
|
||||||
assert_eq!(clean_repo_input("owner/repo"), "owner/repo");
|
assert_eq!(clean_repo_input("owner/repo"), "owner/repo");
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo"), "owner/repo");
|
assert_eq!(
|
||||||
assert_eq!(clean_repo_input("http://github.com/owner/repo"), "owner/repo");
|
clean_repo_input("https://github.com/owner/repo"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clean_repo_input("http://github.com/owner/repo"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo");
|
assert_eq!(clean_repo_input("github.com/owner/repo"), "owner/repo");
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo.git"), "owner/repo");
|
assert_eq!(
|
||||||
assert_eq!(clean_repo_input("https://github.com/owner/repo/"), "owner/repo");
|
clean_repo_input("https://github.com/owner/repo.git"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
clean_repo_input("https://github.com/owner/repo/"),
|
||||||
|
"owner/repo"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"),
|
clean_repo_input("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file"),
|
||||||
"raspberrypi/rpi-imager"
|
"raspberrypi/rpi-imager"
|
||||||
@@ -123,14 +153,14 @@ fn test_add_or_update_repo() {
|
|||||||
config.add_or_update_repo(repo.clone());
|
config.add_or_update_repo(repo.clone());
|
||||||
assert_eq!(config.repositories.len(), 1);
|
assert_eq!(config.repositories.len(), 1);
|
||||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||||
assert_eq!(config.repositories[0].include_prereleases, true);
|
assert!(config.repositories[0].include_prereleases);
|
||||||
|
|
||||||
// Updating existing repo with a URL with query param
|
// Updating existing repo with a URL with query param
|
||||||
let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false);
|
let repo_updated = RepoConfig::new("https://github.com/owner/repo#readme", false);
|
||||||
config.add_or_update_repo(repo_updated);
|
config.add_or_update_repo(repo_updated);
|
||||||
assert_eq!(config.repositories.len(), 1);
|
assert_eq!(config.repositories.len(), 1);
|
||||||
assert_eq!(config.repositories[0].name, "owner/repo");
|
assert_eq!(config.repositories[0].name, "owner/repo");
|
||||||
assert_eq!(config.repositories[0].include_prereleases, false);
|
assert!(!config.repositories[0].include_prereleases);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -159,5 +189,8 @@ fn test_update_last_synced_tag() {
|
|||||||
let repo = RepoConfig::new("owner/repo", true);
|
let repo = RepoConfig::new("owner/repo", true);
|
||||||
config.add_or_update_repo(repo.clone());
|
config.add_or_update_repo(repo.clone());
|
||||||
config.update_last_synced_tag("owner/repo", "v1.0.0".to_string());
|
config.update_last_synced_tag("owner/repo", "v1.0.0".to_string());
|
||||||
assert_eq!(config.find_repo("owner/repo").unwrap().last_synced_tag, Some("v1.0.0".to_string()));
|
assert_eq!(
|
||||||
}
|
config.find_repo("owner/repo").unwrap().last_synced_tag,
|
||||||
|
Some("v1.0.0".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+20
-8
@@ -41,7 +41,10 @@ fn test_package_classification() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Nicht unterstützte Paketformate sollten ignoriert werden
|
// Nicht unterstützte Paketformate sollten ignoriert werden
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.AppImage"), None);
|
assert_eq!(
|
||||||
|
PackageType::from_filename("rpi-imager-1.8.5.AppImage"),
|
||||||
|
None
|
||||||
|
);
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None);
|
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.dmg"), None);
|
||||||
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);
|
assert_eq!(PackageType::from_filename("rpi-imager-1.8.5.exe"), None);
|
||||||
assert_eq!(PackageType::from_filename("source-code.tar.gz"), None);
|
assert_eq!(PackageType::from_filename("source-code.tar.gz"), None);
|
||||||
@@ -57,19 +60,25 @@ fn test_parse_repo_owner_name() {
|
|||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher").unwrap(),
|
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher")
|
||||||
|
.unwrap(),
|
||||||
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git").unwrap(),
|
parse_repo_owner_name("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher.git")
|
||||||
|
.unwrap(),
|
||||||
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
("Heroic-Games-Launcher", "HeroicGamesLauncher")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file").unwrap(),
|
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager?tab=readme-ov-file")
|
||||||
|
.unwrap(),
|
||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_repo_owner_name("https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install").unwrap(),
|
parse_repo_owner_name(
|
||||||
|
"https://github.com/raspberrypi/rpi-imager/?tab=readme-ov-file#install"
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
("raspberrypi", "rpi-imager")
|
("raspberrypi", "rpi-imager")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -86,14 +95,17 @@ async fn test_github_client_empty_token() {
|
|||||||
use mirror_package::github::GitHubClient;
|
use mirror_package::github::GitHubClient;
|
||||||
|
|
||||||
// Test that a client with an empty or whitespace token sanitizes it to None
|
// Test that a client with an empty or whitespace token sanitizes it to None
|
||||||
let client = GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token");
|
let client =
|
||||||
|
GitHubClient::new(Some("".to_string())).expect("Failed to create client with empty token");
|
||||||
assert_eq!(client.token(), None);
|
assert_eq!(client.token(), None);
|
||||||
|
|
||||||
let client_whitespace = GitHubClient::new(Some(" ".to_string())).expect("Failed to create client with whitespace token");
|
let client_whitespace = GitHubClient::new(Some(" ".to_string()))
|
||||||
|
.expect("Failed to create client with whitespace token");
|
||||||
assert_eq!(client_whitespace.token(), None);
|
assert_eq!(client_whitespace.token(), None);
|
||||||
|
|
||||||
// Test with a valid token
|
// Test with a valid token
|
||||||
let client_with_token = GitHubClient::new(Some("valid_token".to_string())).expect("Failed to create client with valid token");
|
let client_with_token = GitHubClient::new(Some("valid_token".to_string()))
|
||||||
|
.expect("Failed to create client with valid token");
|
||||||
assert_eq!(client_with_token.token(), Some("valid_token"));
|
assert_eq!(client_with_token.token(), Some("valid_token"));
|
||||||
|
|
||||||
// Test with no token
|
// Test with no token
|
||||||
|
|||||||
Reference in New Issue
Block a user