From a84b2ccf9ff1ccb03c0b73bda2bb06b7e82f49ae Mon Sep 17 00:00:00 2001 From: harald Date: Thu, 22 Oct 2026 10:08:37 +0200 Subject: [PATCH] ci(release): migrate release workflow to ubuntu-latest with cargo-zigbuild cross-compilation --- .gitea/workflows/release.yaml | 224 +++++++++++++++++++--------------- RELEASE_PROCESS.md | 17 +-- build.rs | 32 ++++- 3 files changed, 159 insertions(+), 114 deletions(-) diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index a8e6cc6..0ed1e1b 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -9,7 +9,7 @@ jobs: # SA-02 & SA-04: Entkoppelte Build- & Test-Umgebung ohne Zugriff auf Signatur-Secrets build: name: Build & Test (Windows x86_64 & Linux musl) - runs-on: windows-latest + runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -18,101 +18,120 @@ jobs: uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master commit pinned with: toolchain: "1.85.0" - targets: x86_64-pc-windows-msvc,x86_64-unknown-linux-musl + targets: x86_64-pc-windows-gnu,x86_64-unknown-linux-musl - - name: Install cargo-zigbuild (pinned) - run: cargo install cargo-zigbuild --locked --version 0.23.4 + - name: Install cargo-zigbuild (pinned, hash-verified) + shell: bash + run: | + set -euo pipefail + ZIGBUILD_VERSION="0.23.4" + ZIGBUILD_URL="https://github.com/rust-cross/cargo-zigbuild/releases/download/v${ZIGBUILD_VERSION}/cargo-zigbuild-x86_64-unknown-linux-musl.tar.xz" + EXPECTED_HASH="9e3cf73485edbd45905c8aadbc0fdf869c7ddc3848f0c898229f2680db52e44b" + + curl -fsSL "$ZIGBUILD_URL" -o cargo-zigbuild.tar.xz + ACTUAL_HASH=$(sha256sum cargo-zigbuild.tar.xz | awk '{print $1}') + if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then + echo "KRITISCHER SICHERHEITSFEHLER: cargo-zigbuild SHA-256 Pruefsumme ungueltig! Erwartet: $EXPECTED_HASH, Erhalten: $ACTUAL_HASH" >&2 + exit 1 + fi + tar -xJf cargo-zigbuild.tar.xz --strip-components=1 -C "$HOME/.cargo/bin" cargo-zigbuild-x86_64-unknown-linux-musl/cargo-zigbuild + rm -f cargo-zigbuild.tar.xz - name: Install Zig (pinned, hash-verified) - shell: pwsh + shell: bash run: | - $ZigVersion = "0.16.0" - $ZigUrl = "https://ziglang.org/download/$ZigVersion/zig-windows-x86_64-$ZigVersion.zip" - $ExpectedZigHash = "68659eb5f1e4eb1437a722f1dd889c5a322c9954607f5edcf337bc3684a75a7e" - Invoke-WebRequest -Uri $ZigUrl -OutFile "zig.zip" - $ActualHash = (Get-FileHash -Path "zig.zip" -Algorithm SHA256).Hash.ToLower() - if ($ActualHash -ne $ExpectedZigHash) { - Write-Error "KRITISCHER SICHERHEITSFEHLER: Zig SHA-256 Pruefsumme ungueltig! Erwartet: $ExpectedZigHash, Erhalten: $ActualHash" - exit 1 - } - Expand-Archive -Path "zig.zip" -DestinationPath "zig-bin" - echo "$PWD/zig-bin/zig-windows-x86_64-$ZigVersion" >> $env:GITHUB_PATH + set -euo pipefail + ZIG_VERSION="0.16.0" + ZIG_URL="https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" + EXPECTED_ZIG_HASH="70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" - - name: Run Tests + curl -fsSL "$ZIG_URL" -o zig.tar.xz + ACTUAL_HASH=$(sha256sum zig.tar.xz | awk '{print $1}') + if [ "$ACTUAL_HASH" != "$EXPECTED_ZIG_HASH" ]; then + echo "KRITISCHER SICHERHEITSFEHLER: Zig SHA-256 Pruefsumme ungueltig! Erwartet: $EXPECTED_ZIG_HASH, Erhalten: $ACTUAL_HASH" >&2 + exit 1 + fi + mkdir -p zig-bin + tar -xJf zig.tar.xz -C zig-bin + rm -f zig.tar.xz + echo "$PWD/zig-bin/zig-x86_64-linux-${ZIG_VERSION}" >> "$GITHUB_PATH" + + - name: Run Native Tests run: cargo test --all --verbose - - name: Build Windows Release Binary - run: cargo build --release + - name: Verify Windows Tests Compile + run: cargo-zigbuild zigbuild --target x86_64-pc-windows-gnu --tests - - name: Cross-build Linux musl binary + - name: Cross-build Windows x86_64 Release Binary + run: cargo-zigbuild zigbuild --target x86_64-pc-windows-gnu --release + + - name: Cross-build Linux musl Release Binary run: cargo-zigbuild zigbuild --target x86_64-unknown-linux-musl --release - name: Package Artifacts id: package - shell: pwsh + shell: bash run: | - $Tag = "${{ gitea.ref_name }}" - $DistDir = "dist" - if (-not (Test-Path $DistDir)) { New-Item -ItemType Directory -Path $DistDir -Force | Out-Null } + set -euo pipefail + TAG="${{ gitea.ref_name }}" + DIST_DIR="dist" + mkdir -p "$DIST_DIR" + + # Ensure zip is available + if ! command -v zip >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y zip + fi # 1. Windows Package - $PackageNameWin = "sanctum-${Tag}-windows-x86_64" - $StagingDirWin = "${DistDir}/${PackageNameWin}" - $ZipFile = "${DistDir}/${PackageNameWin}.zip" + PACKAGE_NAME_WIN="sanctum-${TAG}-windows-x86_64" + STAGING_DIR_WIN="${DIST_DIR}/${PACKAGE_NAME_WIN}" + ZIP_FILE="${DIST_DIR}/${PACKAGE_NAME_WIN}.zip" - New-Item -ItemType Directory -Path $StagingDirWin -Force | Out-Null - Copy-Item "target/release/sanctum.exe" "$StagingDirWin/" - Copy-Item "README.md" "$StagingDirWin/" - Copy-Item "LICENSE" "$StagingDirWin/" - Copy-Item "CHANGELOG.md" "$StagingDirWin/" - Copy-Item "QUICKSTART.md" "$StagingDirWin/" - Copy-Item "INSTALL.md" "$StagingDirWin/" - Copy-Item "LEGAL.md" "$StagingDirWin/" - Copy-Item "THIRD_PARTY_LICENSES.md" "$StagingDirWin/" - if (Test-Path "assets") { Copy-Item "assets" "$StagingDirWin/" -Recurse } + mkdir -p "$STAGING_DIR_WIN" + cp "target/x86_64-pc-windows-gnu/release/sanctum.exe" "$STAGING_DIR_WIN/" + cp README.md LICENSE CHANGELOG.md QUICKSTART.md INSTALL.md LEGAL.md THIRD_PARTY_LICENSES.md "$STAGING_DIR_WIN/" + if [ -d "assets" ]; then + cp -r assets "$STAGING_DIR_WIN/" + fi - Compress-Archive -Path "$StagingDirWin/*" -DestinationPath $ZipFile -Force - Remove-Item $StagingDirWin -Recurse -Force + (cd "$STAGING_DIR_WIN" && zip -r "../${PACKAGE_NAME_WIN}.zip" .) + rm -rf "$STAGING_DIR_WIN" # Standalone Windows EXE nach dist/ kopieren - Copy-Item "target/release/sanctum.exe" "${DistDir}/sanctum.exe" -Force + cp "target/x86_64-pc-windows-gnu/release/sanctum.exe" "${DIST_DIR}/sanctum.exe" # 2. Linux Package - $PackageNameLinux = "sanctum-${Tag}-linux-x86_64" - $StagingDirLinux = "${DistDir}/${PackageNameLinux}" - $TarGzFile = "${DistDir}/${PackageNameLinux}.tar.gz" + PACKAGE_NAME_LINUX="sanctum-${TAG}-linux-x86_64" + STAGING_DIR_LINUX="${DIST_DIR}/${PACKAGE_NAME_LINUX}" + TAR_GZ_FILE="${DIST_DIR}/${PACKAGE_NAME_LINUX}.tar.gz" - New-Item -ItemType Directory -Path $StagingDirLinux -Force | Out-Null - Copy-Item "target/x86_64-unknown-linux-musl/release/sanctum" "$StagingDirLinux/sanctum" - Copy-Item "README.md" "$StagingDirLinux/" - Copy-Item "LICENSE" "$StagingDirLinux/" - Copy-Item "CHANGELOG.md" "$StagingDirLinux/" - Copy-Item "QUICKSTART.md" "$StagingDirLinux/" - Copy-Item "INSTALL.md" "$StagingDirLinux/" - Copy-Item "LEGAL.md" "$StagingDirLinux/" - Copy-Item "THIRD_PARTY_LICENSES.md" "$StagingDirLinux/" + mkdir -p "$STAGING_DIR_LINUX" + cp "target/x86_64-unknown-linux-musl/release/sanctum" "$STAGING_DIR_LINUX/sanctum" + chmod +x "$STAGING_DIR_LINUX/sanctum" + cp README.md LICENSE CHANGELOG.md QUICKSTART.md INSTALL.md LEGAL.md THIRD_PARTY_LICENSES.md "$STAGING_DIR_LINUX/" - tar.exe -czf $TarGzFile -C $DistDir $PackageNameLinux - Remove-Item $StagingDirLinux -Recurse -Force + tar -czf "$TAR_GZ_FILE" -C "$DIST_DIR" "$PACKAGE_NAME_LINUX" + rm -rf "$STAGING_DIR_LINUX" # Standalone Linux ELF nach dist/ kopieren - Copy-Item "target/x86_64-unknown-linux-musl/release/sanctum" "${DistDir}/sanctum" -Force + cp "target/x86_64-unknown-linux-musl/release/sanctum" "${DIST_DIR}/sanctum" + chmod +x "${DIST_DIR}/sanctum" # 3. SHA-256 Checksums für alle 4 Assets - $ZipHash = (Get-FileHash -Path $ZipFile -Algorithm SHA256).Hash.ToLower() - $ExeHash = (Get-FileHash -Path "${DistDir}/sanctum.exe" -Algorithm SHA256).Hash.ToLower() - $TarHash = (Get-FileHash -Path $TarGzFile -Algorithm SHA256).Hash.ToLower() - $ElfHash = (Get-FileHash -Path "${DistDir}/sanctum" -Algorithm SHA256).Hash.ToLower() + ZIP_HASH=$(sha256sum "$ZIP_FILE" | awk '{print $1}') + EXE_HASH=$(sha256sum "${DIST_DIR}/sanctum.exe" | awk '{print $1}') + TAR_HASH=$(sha256sum "$TAR_GZ_FILE" | awk '{print $1}') + ELF_HASH=$(sha256sum "${DIST_DIR}/sanctum" | awk '{print $1}') - @( - "$ZipHash ${PackageNameWin}.zip", - "$ExeHash sanctum.exe", - "$TarHash ${PackageNameLinux}.tar.gz", - "$ElfHash sanctum" - ) | Set-Content -Path "${DistDir}/SHA256SUMS.txt" -Encoding utf8 + cat < "${DIST_DIR}/SHA256SUMS.txt" + ${ZIP_HASH} ${PACKAGE_NAME_WIN}.zip + ${EXE_HASH} sanctum.exe + ${TAR_HASH} ${PACKAGE_NAME_LINUX}.tar.gz + ${ELF_HASH} sanctum + EOF - echo "ZIP_FILE=$ZipFile" >> $env:GITHUB_OUTPUT - echo "PACKAGE_NAME=$PackageNameWin" >> $env:GITHUB_OUTPUT + echo "ZIP_FILE=$ZIP_FILE" >> "$GITHUB_OUTPUT" + echo "PACKAGE_NAME=$PACKAGE_NAME_WIN" >> "$GITHUB_OUTPUT" - name: Upload Build Artifacts uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -124,9 +143,12 @@ jobs: sign-and-release: name: Sign & Release needs: [build] - runs-on: windows-latest + runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/v') steps: + - name: Checkout Code (for CHANGELOG.md) + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Download Build Artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: @@ -134,49 +156,51 @@ jobs: path: dist - name: Verify and Sign Checksums with Minisign - shell: pwsh + shell: bash env: MINISIGN_SECRET_KEY: ${{ secrets.MINISIGN_SECRET_KEY }} run: | + set -euo pipefail # SA-02: Secret-Isolation — MINISIGN_SECRET_KEY ist ausschließlich in diesem dedizierten Signier-Job verfügbar - if (-not $env:MINISIGN_SECRET_KEY) { - Write-Error "Secret MINISIGN_SECRET_KEY ist nicht gesetzt! Release ohne Signatur verboten." + if [ -z "${MINISIGN_SECRET_KEY:-}" ]; then + echo "Secret MINISIGN_SECRET_KEY ist nicht gesetzt! Release ohne Signatur verboten." >&2 exit 1 - } + fi - # SA-03: Pinned Download-Integrität für minisign.exe mit zwingender SHA-256 Validierung - $MinisignUrl = "https://github.com/jedisct1/minisign/releases/download/0.11/minisign-0.11-win64.zip" - $ExpectedMinisignHash = "b9c31c2c3034f81f0e5f5d92cbcc20e67a9671b6e5455661588638848dc58031" + # SA-03: Pinned Download-Integrität für minisign mit zwingender SHA-256 Validierung + MINISIGN_URL="https://github.com/jedisct1/minisign/releases/download/0.11/minisign-0.11-linux.tar.gz" + EXPECTED_MINISIGN_HASH="f0a0954413df8531befed169e447a66da6868d79052ed7e892e50a4291af7ae0" - if (-not (Get-Command minisign -ErrorAction SilentlyContinue)) { - Write-Host "Lade gepinnten Minisign-Release herunter: $MinisignUrl" - Invoke-WebRequest -Uri $MinisignUrl -OutFile "minisign.zip" + if ! command -v minisign >/dev/null 2>&1; then + echo "Lade gepinnten Minisign-Release herunter: $MINISIGN_URL" + curl -fsSL "$MINISIGN_URL" -o minisign.tar.gz - $ActualHash = (Get-FileHash -Path "minisign.zip" -Algorithm SHA256).Hash.ToLower() - if ($ActualHash -ne $ExpectedMinisignHash) { - Write-Error "KRITISCHER SICHERHEITSFEHLER: Minisign SHA-256 Prüfsumme ungültig!`nErwartet: $ExpectedMinisignHash`nErhalten: $ActualHash`nAbbruch." + ACTUAL_HASH=$(sha256sum minisign.tar.gz | awk '{print $1}') + if [ "$ACTUAL_HASH" != "$EXPECTED_MINISIGN_HASH" ]; then + echo "KRITISCHER SICHERHEITSFEHLER: Minisign SHA-256 Pruefsumme ungueltig! Erwartet: $EXPECTED_MINISIGN_HASH, Erhalten: $ACTUAL_HASH" >&2 exit 1 - } - Write-Host "Minisign SHA-256 Integrität erfolgreich verifiziert ($ActualHash)." + fi + echo "Minisign SHA-256 Integritaet erfolgreich verifiziert ($ACTUAL_HASH)." - Expand-Archive -Path "minisign.zip" -DestinationPath "minisign-bin" - $MinisignExe = "minisign-bin/minisign-win64/minisign.exe" - } else { - $MinisignExe = "minisign" - } + mkdir -p minisign-bin + tar -xzf minisign.tar.gz -C minisign-bin + rm -f minisign.tar.gz + MINISIGN_EXE="./minisign-bin/minisign-linux/x86_64/minisign" + chmod +x "$MINISIGN_EXE" + else + MINISIGN_EXE="minisign" + fi - $Tag = "${{ gitea.ref_name }}" - $KeyFile = "sanctum-ci-release.key" - [System.IO.File]::WriteAllText($KeyFile, $env:MINISIGN_SECRET_KEY) - try { - & $MinisignExe -S -s $KeyFile -m "dist/SHA256SUMS.txt" -W -x "dist/SHA256SUMS.txt.minisig" -t "version:$Tag" - if ($LASTEXITCODE -ne 0 -or -not (Test-Path "dist/SHA256SUMS.txt.minisig")) { - Write-Error "Minisign-Signierung fehlgeschlagen!" - exit 1 - } - } finally { - Remove-Item $KeyFile -Force -ErrorAction SilentlyContinue - } + TAG="${{ gitea.ref_name }}" + KEY_FILE="sanctum-ci-release.key" + trap 'rm -f "$KEY_FILE"' EXIT + printf "%s" "$MINISIGN_SECRET_KEY" > "$KEY_FILE" + + "$MINISIGN_EXE" -S -s "$KEY_FILE" -m "dist/SHA256SUMS.txt" -W -x "dist/SHA256SUMS.txt.minisig" -t "version:${TAG}" + if [ ! -f "dist/SHA256SUMS.txt.minisig" ]; then + echo "Minisign-Signierung fehlgeschlagen!" >&2 + exit 1 + fi - name: Create Gitea Release uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index fe7306b..54bfa03 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -6,22 +6,23 @@ Dieses Dokument beschreibt den Release-Workflow von **Sanctum**, die Absicherung ## 1. Automatisierter CI/CD-Workflow (Primärer Release-Pfad) -Ab Version **0.9.2** bildet der CI-Workflow unter [`.gitea/workflows/release.yaml`](.gitea/workflows/release.yaml) den gesamten Release-Prozess für alle Zielplattformen vollautomatisch auf dem `windows-latest`-Runner ab: +Der CI-Workflow unter [`.gitea/workflows/release.yaml`](.gitea/workflows/release.yaml) bildet den gesamten Release-Prozess für alle Zielplattformen vollautomatisch auf dem `ubuntu-latest`-Runner (`farmersdog-runner`) ab: ### Pipeline-Architektur: 1. **Trigger**: - - Push eines Git-Release-Tags nach dem Schema `v*` (z. B. `v0.9.2`). + - Push eines Git-Release-Tags nach dem Schema `v*` (z. B. `v0.9.6`). 2. **Build & Test Job** (`build`): - - **Plattform**: `windows-latest`. + - **Plattform**: `ubuntu-latest`. - **Toolchains**: - Rust Toolchain gepinnt (`1.85.0`). - - Rust Targets: `x86_64-pc-windows-msvc` und `x86_64-unknown-linux-musl`. - - `cargo-zigbuild` gepinnt auf `0.23.4`. - - `Zig` Compiler gepinnt auf `0.16.0` mit zwingender SHA-256-Integritätsprüfung (`68659eb5f1e4eb1437a722f1dd889c5a322c9954607f5edcf337bc3684a75a7e`). + - Rust Targets: `x86_64-pc-windows-gnu` und `x86_64-unknown-linux-musl`. + - `cargo-zigbuild` gepinnt auf `0.23.4` mit zwingender SHA-256-Integritätsprüfung (`9e3cf73485edbd45905c8aadbc0fdf869c7ddc3848f0c898229f2680db52e44b`). + - `Zig` Compiler gepinnt auf `0.16.0` mit zwingender SHA-256-Integritätsprüfung (`70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00`). - **Verifikation**: - Vollständige Ausführung der Testsuite (`cargo test --all --verbose`). + - Kompilier-Verifikation aller Windows-spezifischen Tests (`cargo-zigbuild zigbuild --target x86_64-pc-windows-gnu --tests`). - **Kompilierung**: - - Nativer Windows x86_64 Release-Build mit LTO (`cargo build --release`). + - Windows x86_64 Release-Build mit LTO und PE-Icon via `zig rc` (`cargo-zigbuild zigbuild --target x86_64-pc-windows-gnu --release`). - Statischer Linux musl Cross-Build (`cargo-zigbuild zigbuild --target x86_64-unknown-linux-musl --release`). - **Paketierung**: - Erstellung von `sanctum-v-windows-x86_64.zip` (inkl. `sanctum.exe`, Dokumentation und Assets). @@ -31,7 +32,7 @@ Ab Version **0.9.2** bildet der CI-Workflow unter [`.gitea/workflows/release.yam 3. **Sign & Release Job** (`sign-and-release`): - **Geheimnis-Isolation (SA-02)**: Das Signiergeheimnis `MINISIGN_SECRET_KEY` ist ausschließlich in diesem isolierten Job verfügbar, nicht in der Build- oder Testumgebung. - **Signierung**: - - Gepinntes `minisign.exe` (SHA-256 geprüft). + - Gepinntes `minisign` Binary (`0.11`, SHA-256 geprüft: `f0a0954413df8531befed169e447a66da6868d79052ed7e892e50a4291af7ae0`). - Signiert `SHA256SUMS.txt` mit `trusted comment: version:`. - **Gitea Release**: - Veröffentlicht das Release unter `https://gitea.pansi.eu/harald/sanctum/releases/tag/v` mit allen 6 Assets: diff --git a/build.rs b/build.rs index 62e7707..67847cf 100644 --- a/build.rs +++ b/build.rs @@ -14,14 +14,34 @@ fn main() { if std::path::Path::new(&ico_path).exists() { let rc_content = format!("1 ICON \"{}\"\n", ico_path); if std::fs::write(&rc_path, rc_content).is_ok() { - let status = std::process::Command::new("windres") - .args(["-i", &rc_path, "-O", "coff", "-o", &res_path]) - .status(); - if let Ok(s) = status { - if s.success() { - println!("cargo:rustc-link-arg={}", res_path); + let mut compiled = false; + + for tool in ["windres", "x86_64-w64-mingw32-windres"] { + if let Ok(s) = std::process::Command::new(tool) + .args(["-i", &rc_path, "-O", "coff", "-o", &res_path]) + .status() + { + if s.success() { + compiled = true; + break; + } } } + + if !compiled { + if let Ok(s) = std::process::Command::new("zig") + .args(["rc", "--", &rc_path, &res_path]) + .status() + { + if s.success() { + compiled = true; + } + } + } + + if compiled { + println!("cargo:rustc-link-arg={}", res_path); + } } } }