name: Release build & publish on: release: types: [created] workflow_dispatch: inputs: tag: description: "Existing release tag to (re)build and publish (e.g. v0.5.0)" required: true type: string permissions: contents: write env: BIN_NAME: nyx RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} jobs: frontend: name: build-frontend runs-on: ubuntu-latest steps: - name: Check out sources uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - uses: actions/setup-node@v6 with: node-version: 20 cache: npm cache-dependency-path: frontend/package-lock.json - name: Install frontend dependencies working-directory: frontend run: npm ci - name: Build frontend working-directory: frontend run: npm run build - name: Upload frontend dist uses: actions/upload-artifact@v7 with: name: frontend-dist path: src/server/assets/dist/ if-no-files-found: error retention-days: 1 build: needs: frontend strategy: matrix: include: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - target: aarch64-unknown-linux-gnu os: ubuntu-latest - target: x86_64-pc-windows-msvc os: windows-latest - target: x86_64-apple-darwin os: macos-14 - target: aarch64-apple-darwin os: macos-14 runs-on: ${{ matrix.os }} steps: - name: Check out sources uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Download prebuilt frontend dist uses: actions/download-artifact@v8 with: name: frontend-dist path: src/server/assets/dist/ - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable target: ${{ matrix.target }} cache: true - name: Install cross-compilation tools (ARM Linux) if: matrix.target == 'aarch64-unknown-linux-gnu' run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config.toml echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml - name: Install target run: rustup target add ${{ matrix.target }} - name: Build run: cargo build --release --bin ${{ env.BIN_NAME }} --target ${{ matrix.target }} - name: Package (Linux & macOS) if: runner.os != 'Windows' shell: bash run: | set -euo pipefail BIN=${{ env.BIN_NAME }} TARGET=${{ matrix.target }} EXT=$([[ "$TARGET" == *windows* ]] && echo ".exe" || echo "") BIN_PATH=target/$TARGET/release/$BIN$EXT mkdir -p dist ARCHIVE=$BIN-$TARGET.zip files=("$BIN_PATH" THIRDPARTY-LICENSES.html) shopt -s nullglob license_files=(LICENSE* COPYING*) shopt -u nullglob files+=("${license_files[@]}") zip -9 "dist/$ARCHIVE" "${files[@]}" echo "ASSET=$ARCHIVE" >> "$GITHUB_ENV" - name: Package (Windows) if: runner.os == 'Windows' shell: pwsh run: | $Bin = '${{ env.BIN_NAME }}' $Target = '${{ matrix.target }}' $Ext = '.exe' $BinPath = "target/$Target/release/$Bin$Ext" New-Item -ItemType Directory -Path dist -Force | Out-Null $Archive = "$Bin-$Target.zip" $LicenseFiles = @(Get-ChildItem -Path 'LICENSE*', 'COPYING*' -File -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName }) $Files = @($BinPath, 'THIRDPARTY-LICENSES.html') + $LicenseFiles Compress-Archive ` -Path $Files ` -DestinationPath "dist/$Archive" ` -CompressionLevel Optimal Add-Content -Path $env:GITHUB_ENV -Value "ASSET=$Archive" - name: Upload build artifact uses: actions/upload-artifact@v7 with: name: release-${{ matrix.target }} path: dist/${{ env.ASSET }} if-no-files-found: error retention-days: 1 reproducibility: name: reproducibility-check needs: frontend runs-on: ubuntu-latest continue-on-error: true steps: - name: Check out sources uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Download prebuilt frontend dist uses: actions/download-artifact@v8 with: name: frontend-dist path: src/server/assets/dist/ - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable target: x86_64-unknown-linux-gnu cache: true - name: Build twice and diff hashes shell: bash env: RUSTFLAGS: "--remap-path-prefix=${{ github.workspace }}=/build" run: | set -euo pipefail TARGET=x86_64-unknown-linux-gnu BIN=${{ env.BIN_NAME }} BIN_PATH="target/$TARGET/release/$BIN" SOURCE_DATE_EPOCH=$(git log -1 --format=%ct HEAD) export SOURCE_DATE_EPOCH echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" cargo build --release --bin "$BIN" --target "$TARGET" HASH1=$(sha256sum "$BIN_PATH" | awk '{print $1}') echo "first build: $HASH1" cargo clean --release --target "$TARGET" cargo build --release --bin "$BIN" --target "$TARGET" HASH2=$(sha256sum "$BIN_PATH" | awk '{print $1}') echo "second build: $HASH2" if [ "$HASH1" != "$HASH2" ]; then echo "::error::Reproducibility check failed: builds are not bit-identical" echo " first: $HASH1" echo " second: $HASH2" exit 1 fi echo "::notice::Reproducible build verified (sha256=$HASH1)" publish: name: publish-release runs-on: ubuntu-latest needs: [build] permissions: contents: write id-token: write attestations: write steps: - name: Check out sources uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Generate CycloneDX SBOM uses: anchore/sbom-action@v0 with: path: . format: cyclonedx-json output-file: nyx-${{ env.RELEASE_TAG }}.cdx.json upload-artifact: false upload-release-assets: false - name: Download all build artifacts uses: actions/download-artifact@v8 with: path: release-artifacts pattern: release-* merge-multiple: true - name: Generate SHA256SUMS run: | set -euo pipefail cd release-artifacts ls -lh sha256sum *.zip > SHA256SUMS cat SHA256SUMS # Sigstore keyless signing. Verify with: # cosign verify-blob --bundle .bundle \ # --certificate-identity-regexp 'https://github.com/elicpeter/nyx/.*' \ # --certificate-oidc-issuer https://token.actions.githubusercontent.com \ # - name: Install cosign uses: sigstore/cosign-installer@v4.1.2 - name: Cosign keyless sign release artifacts shell: bash run: | set -euo pipefail SBOM="nyx-${{ env.RELEASE_TAG }}.cdx.json" ( cd release-artifacts for f in *.zip SHA256SUMS; do cosign sign-blob --yes \ --bundle "$f.bundle" \ "$f" done ) cosign sign-blob --yes \ --bundle "$SBOM.bundle" \ "$SBOM" # SLSA v1 provenance. Verify with `gh attestation verify --repo `. - name: Generate SLSA build provenance uses: actions/attest-build-provenance@v4 with: subject-path: | release-artifacts/*.zip release-artifacts/SHA256SUMS nyx-${{ env.RELEASE_TAG }}.cdx.json - name: Upload to the release uses: softprops/action-gh-release@v3 with: tag_name: ${{ env.RELEASE_TAG }} files: | release-artifacts/*.zip release-artifacts/*.zip.bundle release-artifacts/SHA256SUMS release-artifacts/SHA256SUMS.bundle nyx-${{ env.RELEASE_TAG }}.cdx.json nyx-${{ env.RELEASE_TAG }}.cdx.json.bundle env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}