Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions .github/workflows/_release-buildernet-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Build and publish mkosi image

on:
workflow_call:
inputs:
mkosi-profile:
description: "mkosi profiles (e.g. 'devtools,playground'). Empty for production."
type: string
default: ""
r2-path-prefix:
description: "R2 destination prefix (e.g. 'buildernet-images')"
type: string
required: true
checksum-globs:
description: "Bash glob for sha256sum (e.g. 'buildernet-*.{efi,qcow2}')"
type: string
required: true
r2-include:
description: "rclone --include glob for R2 upload"
type: string
required: true
cache-name:
description: "R2 cache object name (e.g. 'cache' or 'cache-playground')"
type: string
required: true

jobs:
build:
name: Build image
runs-on: warp-ubuntu-latest-x64-32x
steps:
- uses: actions/checkout@v5

- name: Install tools
run: |
sudo apt-get update && sudo apt-get install -y \
debian-archive-keyring \
minisign\
pigz \
rclone
pip3 install git+https://github.com/systemd/mkosi.git@$(cat .mkosi_version)

- name: Create rclone config (artifacts upload)
env:
R2_FLASHBOTS_PUBLIC_ARTIFACTS_ACCESS_KEY: ${{ secrets.R2_FLASHBOTS_PUBLIC_ARTIFACTS_ACCESS_KEY }}
R2_FLASHBOTS_PUBLIC_ARTIFACTS_SECRET_KEY: ${{ secrets.R2_FLASHBOTS_PUBLIC_ARTIFACTS_SECRET_KEY }}
R2_FLASHBOTS_PUBLIC_ARTIFACTS_ENDPOINT: ${{ secrets.R2_FLASHBOTS_PUBLIC_ARTIFACTS_ENDPOINT }}
run: |
mkdir -p ~/.config/rclone
cat << EOF > ~/.config/rclone/rclone.conf
[r2-flashbots-public-artifacts]
type = s3
provider = Cloudflare

access_key_id = $R2_FLASHBOTS_PUBLIC_ARTIFACTS_ACCESS_KEY
secret_access_key = $R2_FLASHBOTS_PUBLIC_ARTIFACTS_SECRET_KEY
region = auto
endpoint = $R2_FLASHBOTS_PUBLIC_ARTIFACTS_ENDPOINT
acl = private
EOF

- name: Create rclone config (cache)
env:
R2_MKOSI_BUILDERNET_CACHE_ACCESS_KEY: ${{ secrets.R2_MKOSI_BUILDERNET_CACHE_ACCESS_KEY }}
R2_MKOSI_BUILDERNET_CACHE_SECRET_KEY: ${{ secrets.R2_MKOSI_BUILDERNET_CACHE_SECRET_KEY }}
R2_MKOSI_BUILDERNET_CACHE_ENDPOINT: ${{ secrets.R2_MKOSI_BUILDERNET_CACHE_ENDPOINT }}
run: |
cat << EOF >> ~/.config/rclone/rclone.conf
[r2-mkosi-buildernet-cache]
type = s3
provider = Cloudflare

access_key_id = $R2_MKOSI_BUILDERNET_CACHE_ACCESS_KEY
secret_access_key = $R2_MKOSI_BUILDERNET_CACHE_SECRET_KEY
region = auto
endpoint = $R2_MKOSI_BUILDERNET_CACHE_ENDPOINT
acl = private
EOF

- name: Download cache
run: |
rclone copy -P --retries 3 --retries-sleep 10s \
--transfers=2 --multi-thread-streams 10 --contimeout=1m \
r2-mkosi-buildernet-cache:mkosi-buildernet-cache/${{ inputs.cache-name }}.tar.gz .

- name: Extract cache
run: |
if [[ -f ${{ inputs.cache-name }}.tar.gz ]]; then
unpigz -c ${{ inputs.cache-name }}.tar.gz | sudo tar -xf -
sudo rm -f ${{ inputs.cache-name }}.tar.gz
fi

- name: Enable user namespaces
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Build image
run: |
set -x
umask 022
ARGS=(--force -I buildernet.conf)
ARGS+=(--image-version="${GITHUB_REF_NAME#buildernet-v}-${GITHUB_SHA::8}")
if [[ -n "${{ inputs.mkosi-profile }}" ]]; then
ARGS+=(--profile="${{ inputs.mkosi-profile }}")
fi
mkosi "${ARGS[@]}"

- name: Prepare cache
run: |
sudo find . \( -name "mkosi.builddir" -o -name "mkosi.cache" -o -name "mkosi.tools" \) -type d -print0 | \
sudo tar --null -cf - -T - 2>/dev/null | pigz > ${{ inputs.cache-name }}.tar.gz

- name: Upload cache
run: |
rclone copy -P --retries 3 --retries-sleep 10s --error-on-no-transfer \
--transfers=2 --s3-upload-concurrency=10 \
${{ inputs.cache-name }}.tar.gz r2-mkosi-buildernet-cache:mkosi-buildernet-cache/

- name: Generate SHA256 checksums
run: |
cd mkosi.output
sha256sum ${{ inputs.checksum-globs }} | tee buildernet-${GITHUB_REF_NAME#buildernet-v}-${GITHUB_SHA::8}.sha256

- name: Sign artifacts
env:
MINISIGN_SECRET_KEY: ${{ secrets.MINISIGN_SECRET_KEY }}
MINISIGN_SECRET_KEY_PASSWORD: ${{ secrets.MINISIGN_SECRET_KEY_PASSWORD }}
run: |
mkdir -p ~/.minisign
echo "$MINISIGN_SECRET_KEY" > ~/.minisign/minisign.key
chmod 600 ~/.minisign/minisign.key
echo "$MINISIGN_SECRET_KEY_PASSWORD" | minisign -Sm mkosi.output/buildernet-${GITHUB_REF_NAME#buildernet-v}-${GITHUB_SHA::8}.sha256 \
-t "github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}"

- name: Upload to R2
run: |
rclone copy -P --retries 3 --retries-sleep 20s --error-on-no-transfer \
--s3-upload-concurrency=8 --transfers=8 --include "${{ inputs.r2-include }}" \
mkosi.output r2-flashbots-public-artifacts:flashbots-public-artifacts/${{ inputs.r2-path-prefix }}/${GITHUB_REF_NAME#buildernet-}/
179 changes: 179 additions & 0 deletions .github/workflows/playground-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: playground-test

on:
workflow_dispatch:
inputs:
image-run-id:
description: "Reuse image from a previous run (skip build). Leave empty to build fresh."
type: string
default: ""
builder-playground-version:
description: "builder-playground version (e.g. v0.3.2-alpha.5 or latest)"
default: "latest"
type: string

jobs:
build:
name: Build playground image
if: inputs.image-run-id == ''
runs-on: warp-ubuntu-latest-x64-32x
timeout-minutes: 30
steps:
- uses: actions/checkout@v5

- name: Install mkosi
run: |
sudo apt-get update && sudo apt-get install -y debian-archive-keyring
pip3 install git+https://github.com/systemd/mkosi.git@$(cat .mkosi_version)

- name: Enable user namespaces
run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

- name: Build image
run: |
umask 022
mkosi --force -I buildernet.conf --profile="devtools,playground"

- name: Show output
run: |
ls -lah mkosi.output/
sha256sum mkosi.output/buildernet-qemu_*.qcow2

- name: Upload image
uses: actions/upload-artifact@v4
with:
name: buildernet-playground-image
path: mkosi.output/buildernet-qemu_*.qcow2
retention-days: 3

test:
name: Test playground image
needs: build
if: always() && (needs.build.result == 'success' || needs.build.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
BUILDERNET_IMAGE: ${{ github.workspace }}/image/buildernet-qemu_latest.qcow2
QEMU_CPU: "2"
QEMU_RAM: 8G
VM_DATA_DISK_SIZE: 10G
steps:
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Check runner environment
run: |
echo "--- CPU ---"
nproc
echo "--- Memory ---"
free -h
echo "--- Disk ---"
df -h
echo "--- KVM ---"
ls -la /dev/kvm 2>/dev/null && echo "KVM available" || echo "KVM NOT available"
echo "--- Docker ---"
docker --version 2>/dev/null || echo "Docker not installed"

- name: Download image
uses: actions/download-artifact@v4
with:
name: buildernet-playground-image
path: ./image
run-id: ${{ inputs.image-run-id || github.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Verify image
run: |
ls -lah ./image/
sha256sum ./image/*.qcow2

- name: Install QEMU and OVMF
run: |
sudo apt-get update && sudo apt-get install -y \
qemu-system-x86 ovmf jq unzip
qemu-system-x86_64 --version

- name: Install builder-playground
uses: flashbots/builder-playground-action@v1
with:
version: ${{ inputs.builder-playground-version }}

- name: Generate and start playground
run: |
builder-playground generate buildernet/mkosi
builder-playground start --detached playground.yaml

- name: Wait for VM readyz
timeout-minutes: 12
run: |
WAIT_START=$(date +%s)
WAIT_TIMEOUT=600

echo "Waiting for /readyz on localhost:10443 (timeout: ${WAIT_TIMEOUT}s)..."
while true; do
if curl -sfk -m 1 -o /dev/null https://localhost:10443/readyz 2>/dev/null; then
echo "/readyz OK after $(( $(date +%s) - WAIT_START ))s"
break
fi
elapsed=$(( $(date +%s) - WAIT_START ))
if [ "$elapsed" -ge "$WAIT_TIMEOUT" ]; then
echo "Timed out after ${elapsed}s waiting for /readyz"
exit 1
fi
echo " ${elapsed}s - not ready, retrying in 5s..."
sleep 5
done

echo "--- console log (last 50 lines) ---"
tail -50 .runtime/console.log || true

- name: Run integration test
run: |
builder-playground test \
--rpc http://localhost:18645 \
--el-rpc http://localhost:8545 \
--insecure \
--expected-extra-data "Playground VM Builder ⚡🤖"

- name: Debug info
if: always()
run: |
echo "=== QEMU process ==="
ps aux | grep qemu || echo "no qemu process"

echo "=== .runtime/ directory ==="
ls -lah .runtime/ 2>/dev/null || echo "no .runtime/ directory"

echo "=== QEMU console log ==="
cat .runtime/console.log 2>/dev/null || echo "no console.log"

echo "=== QEMU PID file ==="
cat .runtime/qemu.pid 2>/dev/null || echo "no qemu.pid"

echo "=== Playground session logs ==="
SESSION_DIR=$(ls -td ~/.local/state/builder-playground/sessions/*/ 2>/dev/null | head -1)
if [ -n "$SESSION_DIR" ]; then
echo "Session dir: $SESSION_DIR"
find "$SESSION_DIR/logs" -type f 2>/dev/null | while read -r f; do
echo "--- ${f#$SESSION_DIR/logs/} ---"
cat "$f"
done
else
echo "no session directory found"
fi

echo "=== VM service logs (via operator-api) ==="
curl -sk https://localhost:13535/logs 2>/dev/null || echo "operator-api not reachable"

echo "=== Docker containers ==="
docker ps -a 2>/dev/null || true

echo "=== Disk ==="
df -h

- name: Cleanup
if: always()
run: builder-playground clean all 2>/dev/null || true
19 changes: 19 additions & 0 deletions .github/workflows/release-playground.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: release-playground

on:
push:
tags:
- buildernet-v*
workflow_dispatch: {}

jobs:
build:
name: build playground image
uses: ./.github/workflows/_release-buildernet-image.yml
with:
mkosi-profile: "devtools,playground"
r2-path-prefix: "buildernet-playground-images"
checksum-globs: "buildernet-*.qcow2"
r2-include: "buildernet-*.{qcow2,minisig,sha256}"
cache-name: "cache-playground"
secrets: inherit
Loading