name: CI on: push: branches: - main - "task/**" tags: - "v*" workflow_dispatch: permissions: contents: read concurrency: group: ci-${{ github.ref }} cancel-in-progress: false jobs: gate: runs-on: ubuntu-latest timeout-minutes: 75 steps: - uses: actions/checkout@v7 with: fetch-depth: 20 - uses: ./.github/actions/toolchain - name: gate run: | set -euo pipefail timeout -k 10 600 go mod download timeout -k 10 900 go build ./... timeout -k 10 900 go vet ./... timeout -k 10 2400 deploy/check.sh --fast test -z "$(git status --porcelain)" pins: runs-on: [self-hosted, linux, x64, socialweb-computer-kcp] timeout-minutes: 45 steps: - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - uses: ./.github/actions/toolchain - uses: ./.github/actions/containers - name: pins run: | set -euo pipefail timeout -k 10 2580 make check-pins BUILD_ROOT="$CI_STATE/pins-$GITHUB_RUN_ID-$GITHUB_JOB" binaries: needs: pins if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' runs-on: [self-hosted, linux, x64, socialweb-computer-kcp] timeout-minutes: 90 steps: - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - uses: ./.github/actions/toolchain - uses: ./.github/actions/containers - name: binaries run: | set -euo pipefail timeout -k 10 5280 scripts/build-socialweb-computer.sh --platform linux/amd64 --platform linux/arm64 --commit "$GITHUB_SHA" --build-root "$CI_STATE/release-build-$GITHUB_RUN_ID-$GITHUB_JOB" rel="$CI_STATE/release-build-$GITHUB_RUN_ID-$GITHUB_JOB/release/$GITHUB_SHA" ls -la "$rel" want="$(jq -r '.assets.deno.version' internal/versions/versions.json)" jq -e --arg v "$want" '(.builds | length) > 0 and all(.builds[]; .denoEmbedded.version == $v and (.denoEmbedded.zips | keys | sort) == ["linux/amd64","linux/arm64"])' "$rel/release.json" rm -rf "$OUT/binaries" && mkdir -p "$OUT/binaries" && cp -a "$rel"/. "$OUT/binaries/" (cd "$OUT/binaries" && sha256sum -c --quiet SHA256SUMS) - name: node downloads by sha256 run: | set -euo pipefail blob="$CI_STATE/release-build-$GITHUB_RUN_ID-$GITHUB_JOB/src/1/internal/assets/blobs/linux_amd64/socialweb-computer-k8s-downloads.tar.zst" tree="$(mktemp -d "$RUNNER_TEMP/k8s-downloads.XXXXXX")" zstd -dc "$blob" | tar -x -C "$tree" rm -rf "$OUT/downloads" && mkdir -p "$OUT/downloads" for side in "$tree"/*.sha256; do name="$(basename "$side" .sha256)" want="$(cut -d' ' -f1 "$side")" got="$(sha256sum "$tree/$name" | cut -d' ' -f1)" echo "downloads/$want/$name (sha256 $got)" test "$got" = "$want" mkdir -p "$OUT/downloads/$want" && cp "$tree/$name" "$OUT/downloads/$want/$name" done test "$(find "$OUT/downloads" -type f | wc -l)" -gt 0 - uses: actions/upload-artifact@v7 with: name: binaries path: ${{ runner.temp }}/ci/out/${{ github.sha }}/binaries/ if-no-files-found: error retention-days: 7 - uses: actions/upload-artifact@v7 with: name: node-downloads path: ${{ runner.temp }}/ci/out/${{ github.sha }}/downloads/ if-no-files-found: error retention-days: 7 guest-image: if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 120 env: TARGET: firecracker-x86_64 outputs: published: ${{ steps.published.outputs.published }} key: ${{ steps.published.outputs.key }} target: ${{ steps.published.outputs.target }} steps: - uses: actions/checkout@v7 - name: is the pinned guest image published id: published env: SPACES_BUCKET: ${{ vars.SPACES_BUCKET }} SPACES_REGION: ${{ vars.SPACES_REGION }} run: | set -euo pipefail pin="$(jq -er --arg t "$TARGET" '.guestImages.targets[$t].outputs["image.json"].sha256' internal/versions/versions.json)" key="guest-images/$TARGET/$pin" img="$RUNNER_TEMP/ci/out/$GITHUB_SHA/guest-image" echo "key=$key" >>"$GITHUB_OUTPUT" echo "target=$TARGET" >>"$GITHUB_OUTPUT" build() { echo "guest-image: building $TARGET: $*" rm -rf "$img" echo "published=false" >>"$GITHUB_OUTPUT" exit 0 } { [ -n "$SPACES_BUCKET" ] && [ -n "$SPACES_REGION" ]; } || build "no SPACES_BUCKET or SPACES_REGION to look $key up in" url="https://${SPACES_BUCKET}.${SPACES_REGION}.digitaloceanspaces.com/$key" code="$(curl -s -o /dev/null -I -w '%{http_code}' --max-time 60 "$url/image.json" || true)" [ "$code" = 200 ] || build "$url/image.json answered ${code:-nothing}, so the image of these pins is not published" rm -rf "$img" && mkdir -p "$img/$TARGET" for f in image.json vmlinux rootfs.ext4; do curl -sSfL --retry 3 --max-time 1800 -o "$img/$TARGET/$f" "$url/$f" || build "fetching $url/$f failed" want="$(jq -er --arg t "$TARGET" --arg f "$f" '.guestImages.targets[$t].outputs[$f].sha256' internal/versions/versions.json)" got="$(sha256sum "$img/$TARGET/$f" | cut -d' ' -f1)" echo "$TARGET/$f sha256 $got (pinned $want) from $url/$f" [ "$got" = "$want" ] || build "$url/$f hashes to $got, not its pin $want" [ "$f" = image.json ] || continue while read -r name recorded current; do echo "$TARGET input $name sha256 $current (recorded in the published image.json $recorded)" [ "$recorded" = "$current" ] || build "input $name changed since $key was built and its pins did not, so it is built to check them" done < <(jq -r --slurpfile v internal/versions/versions.json --arg t "$TARGET" ' .inputs.kernelSource.sha256 as $ks | .inputs.base.sha256 as $bs | "kernelSource \($ks) \($v[0].guestImages.kernel.source.sha256)", "base \($bs) \($v[0].guestImages.targets[$t].base.sha256)", (.inputs[] | select(.path != null) | "\(.path) \(.sha256) PATH")' "$img/$TARGET/image.json" | while read -r name recorded current; do if [ "$current" = PATH ]; then current="$(sha256sum "$name" 2>/dev/null | cut -d' ' -f1 || true)"; fi echo "$name $recorded ${current:-absent}" done) done echo "guest-image: not building $TARGET: every output is published at $url and matches its pin" echo "published=true" >>"$GITHUB_OUTPUT" - if: steps.published.outputs.published != 'true' uses: ./.github/actions/toolchain - if: steps.published.outputs.published != 'true' uses: ./.github/actions/containers - name: guest-image if: steps.published.outputs.published != 'true' run: | set -euo pipefail work="$CI_STATE/guest-image-work" && img="$OUT/guest-image" rm -rf "$img" && mkdir -p "$work" "$img" timeout -k 10 7000 scripts/build-guest-image.sh --target "$TARGET" --work "$work" --out "$img" for f in vmlinux rootfs.ext4 image.json; do want="$(jq -er --arg t "$TARGET" --arg f "$f" '.guestImages.targets[$t].outputs[$f].sha256' internal/versions/versions.json)" got="$(sha256sum "$img/$TARGET/$f" | cut -d' ' -f1)" echo "$TARGET/$f sha256 $got (pinned $want)" test "$got" = "$want" done - if: steps.published.outputs.published != 'true' uses: actions/upload-artifact@v7 with: name: guest-image path: ${{ runner.temp }}/ci/out/${{ github.sha }}/guest-image/ if-no-files-found: error retention-days: 7 publish: if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 90 needs: [gate, binaries, guest-image] steps: - uses: actions/checkout@v7 - uses: ./.github/actions/toolchain - uses: actions/download-artifact@v8 with: name: binaries path: ${{ runner.temp }}/ci/out/${{ github.sha }}/binaries - uses: actions/download-artifact@v8 with: name: node-downloads path: ${{ runner.temp }}/ci/out/${{ github.sha }}/downloads - if: needs.guest-image.outputs.published != 'true' uses: actions/download-artifact@v8 with: name: guest-image path: ${{ runner.temp }}/ci/guest-image - name: publish the guest image env: SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} SPACES_BUCKET: ${{ vars.SPACES_BUCKET }} SPACES_REGION: ${{ vars.SPACES_REGION }} PUBLISHED: ${{ needs.guest-image.outputs.published }} KEY: ${{ needs.guest-image.outputs.key }} TARGET: ${{ needs.guest-image.outputs.target }} run: | set -euo pipefail test -n "$KEY" && test -n "$TARGET" if [ "$PUBLISHED" = true ]; then echo "guest-image: $KEY is already published and the guest-image job verified it against the pins, so nothing is uploaded" exit 0 fi test -n "$SPACES_ACCESS_KEY_ID" && test -n "$SPACES_SECRET_ACCESS_KEY" gi="$RUNNER_TEMP/ci/guest-image/$TARGET" for f in vmlinux rootfs.ext4 image.json; do want="$(jq -er --arg t "$TARGET" --arg f "$f" '.guestImages.targets[$t].outputs[$f].sha256' internal/versions/versions.json)" got="$(sha256sum "$gi/$f" | cut -d' ' -f1)" echo "$TARGET/$f sha256 $got (pinned $want)" test "$got" = "$want" done echo "guest-image: uploading the built $TARGET to $KEY" timeout -k 10 4800 deno run -A --no-lock scripts/publish-spaces.ts --dir "$gi" --prefix "$KEY" --content-addressed - name: publish env: SPACES_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_KEY_ID }} SPACES_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_ACCESS_KEY }} SPACES_BUCKET: ${{ vars.SPACES_BUCKET }} SPACES_REGION: ${{ vars.SPACES_REGION }} run: | set -euo pipefail test -n "$SPACES_ACCESS_KEY_ID" && test -n "$SPACES_SECRET_ACCESS_KEY" if [ "$GITHUB_REF_TYPE" = tag ]; then prefix="releases/$GITHUB_REF_NAME"; also="" else prefix="main/$GITHUB_SHA"; also="main/latest" fi dl="$OUT/downloads" timeout -k 10 1800 deno run -A --no-lock scripts/publish-spaces.ts --dir "$dl" --prefix downloads --content-addressed rm -rf "$dl" timeout -k 10 4800 deno run -A --no-lock scripts/publish-spaces.ts --dir "$OUT" --prefix "$prefix" ${also:+--also "$also"} - name: the published guest image is where the binary looks for it env: SPACES_BUCKET: ${{ vars.SPACES_BUCKET }} SPACES_REGION: ${{ vars.SPACES_REGION }} KEY: ${{ needs.guest-image.outputs.key }} TARGET: ${{ needs.guest-image.outputs.target }} run: | set -euo pipefail base="https://${SPACES_BUCKET}.${SPACES_REGION}.digitaloceanspaces.com" for f in image.json vmlinux rootfs.ext4; do url="$base/$KEY/$f" want="$(jq -er --arg t "$TARGET" --arg f "$f" '.guestImages.targets[$t].outputs[$f].bytes' internal/versions/versions.json)" head="$(curl -sS -I --max-time 60 "$url")" code="$(printf '%s\n' "$head" | awk 'NR==1 {print $2}')" size="$(printf '%s\n' "$head" | tr -d '\r' | awk 'tolower($1)=="content-length:" {print $2}')" echo "$code $url $size bytes (pinned $want; readable without credentials: a bidder fetches its own image from here)" test "$code" = 200 test "$size" = "$want" done if [ "$GITHUB_REF_TYPE" != tag ]; then latest="$base/main/latest/binaries/socialweb-computer-linux-amd64" code="$(curl -s -o /dev/null -w '%{http_code}' --max-time 60 -r 0-0 "$latest")" echo "$code $latest (the newest main binary, readable without credentials)" test "$code" = 206 fi - name: the published binary fetches its own guest image env: SPACES_BUCKET: ${{ vars.SPACES_BUCKET }} SPACES_REGION: ${{ vars.SPACES_REGION }} run: | set -euo pipefail base="https://${SPACES_BUCKET}.${SPACES_REGION}.digitaloceanspaces.com" if [ "$GITHUB_REF_TYPE" = tag ]; then bin="$base/releases/$GITHUB_REF_NAME/binaries/socialweb-computer-linux-amd64" else bin="$base/main/latest/binaries/socialweb-computer-linux-amd64" fi probe="$RUNNER_TEMP/probe" mkdir -p "$probe/run" curl -sSfL --max-time 900 -o "$probe/socialweb-computer" "$bin" chmod 0755 "$probe/socialweb-computer" XDG_RUNTIME_DIR="$probe/run" "$probe/socialweb-computer" run bidder \ --handle 'NOT A HANDLE' --host-backend proc --profile dev-local --state-dir "$probe/state" \ >"$probe/out.txt" 2>&1 || true cat "$probe/out.txt" if grep -qE 'guestimagerelease|guest-image-source is required' "$probe/out.txt"; then echo "the published binary cannot fetch the guest image of its own build" exit 1 fi echo "the published binary resolved its own guest image"