name: bot-nix-hashes # Keeps nix/hashes.json in step with the lockfile through a pull request, using only GITHUB_TOKEN. # - hash and compute run repository code (nix build) with a read-only token and no secrets. compute uploads only the # new nix/hashes.json. # - publish holds the write token. It runs only .github/nix-hashes/ from this commit, treats the artifact as data, # validates its exact shape and commits only that file to bot/nix-hashes. It never runs repository code. # - check-bot-scope.yml, a required check, fails the bot's pull request unless it changes only a valid nix/hashes.json, # so a tampered branch cannot auto-merge. # This file, check-bot-scope.yml and .github/nix-hashes/ are the security boundary. permissions: contents: read on: workflow_dispatch: push: branches: [dev] # Mirrored in .github/nix-hashes/publish.sh. paths: - "bun.lock" - "package.json" - "packages/*/package.json" - "services/*/package.json" - "flake.lock" - "nix/node_modules.nix" - "nix/scripts/**" - "patches/**" - ".github/workflows/bot-nix-hashes.yml" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # Native runners required: bun install cross-compilation flags (--os/--cpu) # do not produce byte-identical node_modules as native installs. hash: strategy: fail-fast: false matrix: include: - system: x86_64-linux runner: blacksmith-4vcpu-ubuntu-2404 - system: aarch64-linux runner: blacksmith-4vcpu-ubuntu-2404-arm - system: aarch64-darwin runner: macos-latest runs-on: ${{ matrix.runner }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Setup Nix uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34 - name: Compute node_modules hash id: hash env: SYSTEM: ${{ matrix.system }} run: | set -euo pipefail BUILD_LOG=$(mktemp) trap 'rm -f "$BUILD_LOG"' EXIT HASH="" MAX_ATTEMPTS=3 for ((ATTEMPT = 1; ATTEMPT <= MAX_ATTEMPTS; ATTEMPT++)); do # Build with fakeHash to trigger hash mismatch and reveal correct hash nix build ".#packages.${SYSTEM}.node_modules_updater" --no-link 2>&1 | tee "$BUILD_LOG" || true HASH="$(nix run --inputs-from . nixpkgs#gnugrep -- -oP 'got:\s*\Ksha256-[A-Za-z0-9+/=]+' "$BUILD_LOG" | tail -n1 || true)" [ -n "$HASH" ] && break if [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; then echo "::warning::Attempt ${ATTEMPT}/${MAX_ATTEMPTS} produced no hash for ${SYSTEM}; retrying in $((ATTEMPT * 10))s" sleep $((ATTEMPT * 10)) fi done if [ -z "$HASH" ]; then echo "::error::Failed to compute hash for ${SYSTEM} after ${MAX_ATTEMPTS} attempts" cat "$BUILD_LOG" exit 1 fi echo "$HASH" > hash.txt echo "Computed hash for ${SYSTEM}: $HASH" - name: Upload hash uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: hash-${{ matrix.system }} path: hash.txt retention-days: 1 # Assembles nix/hashes.json from the per-system hashes and uploads it as the only input publish accepts. compute: needs: hash runs-on: ubuntu-24.04 outputs: changed: ${{ steps.update.outputs.changed }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false - name: Download hash artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: ${{ runner.temp }}/hashes pattern: hash-* - name: Update hashes.json id: update env: HASHES: ${{ runner.temp }}/hashes run: | set -euo pipefail HASH_FILE="nix/hashes.json" [ -f "$HASH_FILE" ] || echo '{"nodeModules":{}}' > "$HASH_FILE" for SYSTEM in x86_64-linux aarch64-linux aarch64-darwin; do HASH="$(tr -d '[:space:]' < "$HASHES/hash-${SYSTEM}/hash.txt")" echo "${SYSTEM}: ${HASH}" jq --arg sys "$SYSTEM" --arg h "$HASH" '.nodeModules[$sys] = $h' "$HASH_FILE" > tmp.json mv tmp.json "$HASH_FILE" done cat "$HASH_FILE" bash .github/nix-hashes/validate.sh "$HASH_FILE" if git diff --quiet -- "$HASH_FILE"; then echo "changed=false" >> "$GITHUB_OUTPUT" echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" echo "Status: no changes" >> "$GITHUB_STEP_SUMMARY" else echo "changed=true" >> "$GITHUB_OUTPUT" echo "### Nix hashes" >> "$GITHUB_STEP_SUMMARY" echo "Status: changed" >> "$GITHUB_STEP_SUMMARY" fi - name: Upload hashes.json uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: nix-hashes path: nix/hashes.json if-no-files-found: error retention-days: 1 # Runs even when compute found no change, so an open bot pull request with stale hashes is closed, not merged. publish: needs: compute if: github.repository == 'anomalyco/opencode' && github.ref == 'refs/heads/dev' runs-on: ubuntu-24.04 concurrency: group: bot-nix-hashes-publish cancel-in-progress: false permissions: contents: write pull-requests: write actions: write steps: - name: Check out publish scripts uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: ${{ github.sha }} path: trusted sparse-checkout: /.github/nix-hashes/ sparse-checkout-cone-mode: false persist-credentials: false # Holds the push credential; only nix/hashes.json is checked out and nothing in it runs. - name: Check out dev uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: ref: dev path: repo sparse-checkout: /nix/hashes.json sparse-checkout-cone-mode: false - name: Download hashes.json uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: nix-hashes path: ${{ runner.temp }}/nix-hashes - name: Publish working-directory: repo run: bash ../trusted/.github/nix-hashes/publish.sh "$RUNNER_TEMP/nix-hashes/hashes.json" env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }}