Repository navigation
Clean-host image: pass the host architecture to every builder #36
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Native Linux release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| concurrency: | |
| group: native-release-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| # Owner provisioning is intentionally required. No environment, key, or | |
| # reviewed tooling commit is fabricated by this workflow. | |
| configuration: | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| outputs: | |
| tooling: ${{ steps.validate.outputs.tooling }} | |
| environment: ${{ steps.validate.outputs.environment }} | |
| steps: | |
| - id: validate | |
| name: Require approved signing configuration and repository identity | |
| env: | |
| SIGNING_ENVIRONMENT: ${{ vars.NATIVE_RELEASE_SIGNING_ENVIRONMENT }} | |
| TOOLING_COMMIT: ${{ vars.NATIVE_RELEASE_TOOLING_COMMIT }} | |
| REPOSITORY_ID: ${{ github.repository_id }} | |
| OWNER_ID: ${{ github.repository_owner_id }} | |
| run: | | |
| node --input-type=module <<'NODE' | |
| import assert from 'node:assert/strict'; | |
| import { appendFileSync } from 'node:fs'; | |
| const env = process.env; | |
| assert.equal(env.GITHUB_REPOSITORY, 'ShadowfetchStudio/ShadowCode'); | |
| assert.equal(env.REPOSITORY_ID, '1377099349'); | |
| assert.equal(env.OWNER_ID, '209457103'); | |
| assert.equal(env.GITHUB_EVENT_NAME, 'push'); | |
| assert.equal(env.GITHUB_REF_TYPE, 'tag'); | |
| assert.match(env.GITHUB_REF_NAME, /^v(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})$/); | |
| assert.match(env.TOOLING_COMMIT || '', /^[a-f0-9]{40}$/, 'Provision a reviewed immutable tooling commit'); | |
| assert.match(env.SIGNING_ENVIRONMENT || '', /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,254}$/, 'Provision an existing protected signing environment'); | |
| appendFileSync(env.GITHUB_OUTPUT, `tooling=${env.TOOLING_COMMIT}\nenvironment=${env.SIGNING_ENVIRONMENT}\n`); | |
| NODE | |
| build: | |
| needs: configuration | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 150 | |
| env: | |
| RUSTUP_TOOLCHAIN: '1.95.0' | |
| SHADOWCODE_BUILD_JOBS: '4' | |
| # The Debian package must pass lintian (errors fail; warnings are reported). | |
| SHADOWCODE_REQUIRE_LINTIAN: '1' | |
| outputs: | |
| artifact-id: ${{ steps.unsigned.outputs.artifact-id }} | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: ui/package-lock.json | |
| - name: Native package dependencies | |
| run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev libayatana-appindicator3-dev libasound2-dev patchelf webkit2gtk-driver xvfb appstream dbus-daemon dbus-bin lintian desktop-file-utils | |
| - name: Test release staging and immutable retry behavior | |
| run: node scripts/native-release-verification.mjs run release-tests | |
| - name: Verify publisher authentication and private release snapshots | |
| run: node scripts/native-release-verification.mjs run release-auth | |
| - name: Validate native release tag | |
| run: node scripts/native-release-verification.mjs run release-tag | |
| - name: Managed llama.cpp build dependencies | |
| run: sudo apt-get install -y cmake ninja-build libssl-dev libvulkan-dev glslc spirv-headers | |
| - name: Install the pinned Rust toolchain | |
| run: rustup toolchain install 1.95.0 --profile minimal --component rustfmt --component clippy | |
| - name: Build and test the embedded interface | |
| run: node scripts/native-release-verification.mjs run interface | |
| - name: Verify the exact GLib backport and optimized iterator behavior | |
| run: node scripts/native-release-verification.mjs run glib-backport | |
| - name: Verify native source | |
| run: node scripts/native-release-verification.mjs run native-source | |
| - name: Qualify cleanup over actual built UI dependencies and output | |
| run: node scripts/native-release-verification.mjs run built-project-cleanup | |
| - name: Preserve built-project cleanup diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: built-project-cleanup-diagnostics | |
| path: artifacts/built-project-cleanup | |
| - name: Exercise native application behavior | |
| run: node scripts/native-release-verification.mjs run native-behavior | |
| - name: Exercise the native desktop window | |
| run: node scripts/native-release-verification.mjs run native-window | |
| - name: Qualify 100 sequential tasks through the native window | |
| run: node scripts/native-release-verification.mjs run native-endurance | |
| - name: Preserve native window and Markdown worker diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: native-window-diagnostics | |
| path: | | |
| artifacts/native | |
| artifacts/native-markdown | |
| artifacts/native-endurance | |
| artifacts/native-x11 | |
| - name: Build the managed llama.cpp runtime (Vulkan + all CPU variants) | |
| run: node scripts/native-release-verification.mjs run managed-runtime | |
| - name: Build and inspect release packages | |
| run: node scripts/native-release-verification.mjs run packages | |
| - name: Install and first-launch both release packages in an offline clean host | |
| run: node scripts/native-release-verification.mjs run clean-host-packages | |
| - name: Exercise the packaged application | |
| run: node scripts/native-release-verification.mjs run packaged-behavior | |
| - name: Preserve final X11 session diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: native-x11-session-diagnostics | |
| path: artifacts/native-x11 | |
| - name: Create release checksums | |
| run: cp artifacts/native-package/SHA256SUMS target/release/bundle/SHA256SUMS | |
| - name: Verify installer replacement and checksum refusal | |
| run: node scripts/native-release-verification.mjs run installer | |
| - name: Preserve exact release verification receipts | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: native-release-verification | |
| path: artifacts/release-verification | |
| - name: Prepare immutable unsigned handoff with all required receipts | |
| run: node scripts/prepare-native-release.mjs "$RUNNER_TEMP/unsigned-release" | |
| - id: unsigned | |
| name: Retain this run's exact unsigned handoff | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: unsigned-release-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/unsigned-release | |
| if-no-files-found: error | |
| compression-level: 0 | |
| # aarch64 packages are built and qualified natively on GitHub's Arm runner | |
| # with the same required gates and handed off unsigned exactly like x86_64. | |
| # The signer authenticates each target's bundle; its release metadata carries | |
| # an -aarch64 suffix so both targets publish into one release. | |
| build-aarch64: | |
| needs: configuration | |
| runs-on: ubuntu-24.04-arm | |
| timeout-minutes: 180 | |
| env: | |
| RUSTUP_TOOLCHAIN: '1.95.0' | |
| SHADOWCODE_BUILD_JOBS: '4' | |
| SHADOWCODE_REQUIRE_LINTIAN: '1' | |
| outputs: | |
| artifact-id: ${{ steps.unsigned.outputs.artifact-id }} | |
| steps: | |
| - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '22' | |
| cache: npm | |
| cache-dependency-path: ui/package-lock.json | |
| - name: Native package dependencies | |
| run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev libayatana-appindicator3-dev libasound2-dev patchelf webkit2gtk-driver xvfb appstream dbus-daemon dbus-bin lintian desktop-file-utils xdg-utils cmake ninja-build libssl-dev libvulkan-dev glslc spirv-headers gcc-14 g++-14 | |
| - name: Install the pinned Rust toolchain | |
| run: rustup toolchain install 1.95.0 --profile minimal --component rustfmt --component clippy | |
| - name: Run every required release gate in order on aarch64 | |
| run: | | |
| set -euo pipefail | |
| gates=$(node --input-type=module -e "const { REQUIRED_GATES } = await import('./scripts/native-release-verification.mjs'); console.log(REQUIRED_GATES.join(' '))") | |
| for gate in $gates; do | |
| # The installer gate verifies the checksums the packages gate wrote. | |
| if [ "$gate" = installer ]; then cp artifacts/native-package/SHA256SUMS target/release/bundle/SHA256SUMS; fi | |
| node scripts/native-release-verification.mjs run "$gate" | |
| done | |
| - name: Preserve aarch64 qualification diagnostics | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: native-release-aarch64-diagnostics | |
| path: | | |
| artifacts/release-verification | |
| artifacts/built-project-cleanup | |
| artifacts/native | |
| artifacts/native-markdown | |
| artifacts/native-endurance | |
| artifacts/native-x11 | |
| artifacts/native-package | |
| - name: Prepare immutable unsigned aarch64 handoff with all required receipts | |
| run: node scripts/prepare-native-release.mjs "$RUNNER_TEMP/unsigned-release" | |
| - id: unsigned | |
| name: Retain this run's exact unsigned aarch64 handoff | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: unsigned-release-aarch64-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/unsigned-release | |
| if-no-files-found: error | |
| compression-level: 0 | |
| sign: | |
| needs: [configuration, build, build-aarch64] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| actions: read | |
| environment: ${{ needs.configuration.outputs.environment }} | |
| outputs: | |
| artifact-id: ${{ steps.signed.outputs.artifact-id }} | |
| steps: | |
| - name: Checkout independently approved signing tooling and public trust | |
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.configuration.outputs.tooling }} | |
| path: trusted | |
| persist-credentials: false | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '22' | |
| - name: Require one build artifact identity per target | |
| env: | |
| RELEASE_ARTIFACT_ID: ${{ needs.build.outputs.artifact-id }} | |
| AARCH64_ARTIFACT_ID: ${{ needs.build-aarch64.outputs.artifact-id }} | |
| run: node -e "const assert = require('node:assert/strict'); for (const id of [process.env.RELEASE_ARTIFACT_ID, process.env.AARCH64_ARTIFACT_ID]) assert.match(id || '', /^[1-9][0-9]*$/); assert.notEqual(process.env.RELEASE_ARTIFACT_ID, process.env.AARCH64_ARTIFACT_ID)" | |
| - name: Download only the immutable artifact ID produced by this build | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| artifact-ids: ${{ needs.build.outputs.artifact-id }} | |
| path: ${{ runner.temp }}/unsigned-download | |
| merge-multiple: true | |
| - name: Download only the immutable artifact ID produced by the aarch64 build | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| artifact-ids: ${{ needs.build-aarch64.outputs.artifact-id }} | |
| path: ${{ runner.temp }}/unsigned-download-aarch64 | |
| merge-multiple: true | |
| - name: Sign and reverify each target's qualified data with the protected key | |
| working-directory: trusted | |
| env: | |
| NATIVE_RELEASE_SIGNING_KEY_PEM: ${{ secrets.NATIVE_RELEASE_SIGNING_KEY_PEM }} | |
| run: | | |
| mkdir -m 700 "$RUNNER_TEMP/signed-release" | |
| node scripts/sign-native-release.mjs "$RUNNER_TEMP/unsigned-download" release/trust "$RUNNER_TEMP/signed-release/x86_64" | |
| node scripts/sign-native-release.mjs "$RUNNER_TEMP/unsigned-download-aarch64" release/trust "$RUNNER_TEMP/signed-release/aarch64" aarch64-unknown-linux-gnu | |
| - id: signed | |
| name: Retain only authenticated public release data | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: signed-release-${{ github.run_id }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/signed-release | |
| if-no-files-found: error | |
| compression-level: 0 | |
| publish: | |
| needs: [configuration, build, sign] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| actions: read | |
| steps: | |
| - name: Checkout independently approved publication tooling and public trust | |
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.configuration.outputs.tooling }} | |
| path: trusted | |
| persist-credentials: false | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '22' | |
| - name: Require one signed artifact identity | |
| env: | |
| RELEASE_ARTIFACT_ID: ${{ needs.sign.outputs.artifact-id }} | |
| run: node -e "require('node:assert/strict').match(process.env.RELEASE_ARTIFACT_ID || '', /^[1-9][0-9]*$/)" | |
| - name: Download only this signing job's immutable public bundle | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| artifact-ids: ${{ needs.sign.outputs.artifact-id }} | |
| path: ${{ runner.temp }}/signed-download | |
| merge-multiple: true | |
| - name: Authenticate private snapshots before any release operation | |
| working-directory: trusted | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: node scripts/publish-native-release.mjs "$RUNNER_TEMP/signed-download/x86_64" release/trust "$RUNNER_TEMP/signed-download/aarch64" | |
| # Signed APT repository on GitHub Pages (gh-pages branch, path apt/). Only | |
| # the .deb that passed publisher authentication is indexed; the repository | |
| # signing key is a separate OpenPGP key held in the protected environment. | |
| apt-repository: | |
| needs: [configuration, sign, publish] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| permissions: | |
| contents: write | |
| environment: ${{ needs.configuration.outputs.environment }} | |
| steps: | |
| - name: Checkout independently approved tooling and public trust | |
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| with: | |
| ref: ${{ needs.configuration.outputs.tooling }} | |
| path: trusted | |
| persist-credentials: false | |
| - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: '22' | |
| - name: Require a pinned APT signing fingerprint | |
| env: | |
| APT_FPR: ${{ vars.NATIVE_APT_SIGNING_FINGERPRINT }} | |
| run: node -e "require('node:assert/strict').match(process.env.APT_FPR || '', /^[0-9A-F]{40}$/, 'Provision NATIVE_APT_SIGNING_FINGERPRINT')" | |
| - name: Download only this signing job's immutable public bundle | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| artifact-ids: ${{ needs.sign.outputs.artifact-id }} | |
| path: ${{ runner.temp }}/signed-download | |
| merge-multiple: true | |
| - name: Authenticate each target's .deb against the tracked trust bundle | |
| working-directory: trusted | |
| run: | | |
| version="${GITHUB_REF_NAME#v}" | |
| mkdir -m 700 "$RUNNER_TEMP/verified-deb" | |
| for pair in x86_64:amd64 aarch64:arm64; do | |
| bash scripts/verify-native-release.sh \ | |
| --bundle-dir "$RUNNER_TEMP/signed-download/${pair%%:*}" \ | |
| --trust-dir release/trust \ | |
| --artifact "ShadowCode_${version}_${pair#*:}.deb" \ | |
| --stage-dir "$RUNNER_TEMP/verified-deb/${pair#*:}" \ | |
| --expect-version "$version" | |
| done | |
| - name: Checkout the published Pages branch | |
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 | |
| continue-on-error: true | |
| id: pages | |
| with: | |
| ref: gh-pages | |
| path: pages | |
| - name: Build and sign the repository, keeping earlier pool packages | |
| working-directory: trusted | |
| env: | |
| APT_REPO_SIGNING_KEY: ${{ secrets.NATIVE_APT_SIGNING_KEY }} | |
| APT_FPR: ${{ vars.NATIVE_APT_SIGNING_FINGERPRINT }} | |
| run: | | |
| previous=() | |
| [ -d "$GITHUB_WORKSPACE/pages/apt" ] && previous=(--previous "$GITHUB_WORKSPACE/pages/apt") | |
| node scripts/build-apt-repo.mjs --out "$RUNNER_TEMP/apt" --fingerprint "$APT_FPR" "${previous[@]}" "$RUNNER_TEMP"/verified-deb/*/*.deb | |
| gpgv --keyring "$RUNNER_TEMP/apt/shadowcode-archive-keyring.gpg" "$RUNNER_TEMP/apt/dists/stable/InRelease" | |
| - name: Publish to gh-pages | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [ "${{ steps.pages.outcome }}" != success ]; then | |
| rm -rf pages && mkdir pages && cd pages | |
| git init -q -b gh-pages | |
| git remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | |
| touch .nojekyll | |
| else | |
| cd pages | |
| fi | |
| rm -rf apt && cp -a "$RUNNER_TEMP/apt" apt | |
| git add -A | |
| git -c user.name=github-actions -c user.email=github-actions@users.noreply.github.com \ | |
| commit -q -m "APT repository for ${GITHUB_REF_NAME}" | |
| git push origin HEAD:gh-pages |