Files
opencode/.github/workflows/bot-nix-hashes.yml

204 lines
6.9 KiB
YAML

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 }}