Skip to content
Draft
Show file tree
Hide file tree
Changes from 11 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ mkosi.tools.manifest
.claudesync/
.claudeignore
tmp/
logs/

!/**/.gitkeep
.vscode/

.venv/
78 changes: 78 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Makefile for flashbots-images
# Build VM images using mkosi
#
# Also:
# - manages a Python virtual environment with a pinned version of mkosi.

.DEFAULT_GOAL := help

VERSION := $(shell git describe --tags --always --dirty="-dev" 2>/dev/null || echo "dev")
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c

# mkosi version
MKOSI_COMMIT := a425313c5811d2ed840630dbfc45c6bc296bfd48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets avoid putting project dependency versions in the Makefile. main branch is using Nix which I think is too heavy for bnet images use-case (we don't need to install system tools).

Below you're also opting to use pip for dependency management. Originally, I intentionally avoided relying on any single tool for Python dependency management as there is no one blessed tool in the Python ecosystem and people use whatever (pip, pipx, poetry, uv).

Let's first discuss which dependency management tool we want to use for the project. I personally like uv.

To not block the PR on the tool selection decision let's proceed with the current implementation of the Makefile and come back to it later.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added TODO to the Makefile to revisit tooling choice.


# Virtual environment paths
VENV := .venv
VENV_BIN := $(VENV)/bin
VENV_MARKER := $(VENV)/.installed-$(MKOSI_COMMIT)
MKOSI := $(VENV_BIN)/mkosi

# Build logs
LOGS_DIR := logs
TIMESTAMP := $(shell date +%Y%m%d-%H%M%S)

##@ Help

# Awk script from https://github.com/paradigmxyz/reth/blob/main/Makefile
.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

.PHONY: v
v: ## Show the version
@echo "Version: $(VERSION)"

##@ Build

.PHONY: build
build: $(VENV_MARKER) ## Build VM image
@mkdir -p $(LOGS_DIR)
$(MKOSI) --force -I buildernet.conf 2>&1 | tee $(LOGS_DIR)/build-$(TIMESTAMP).log

.PHONY: build-playground
build-playground: $(VENV_MARKER) ## Build VM image for playground
@mkdir -p $(LOGS_DIR)
$(MKOSI) --force -I buildernet.conf --profile="devtools,playground" 2>&1 | tee $(LOGS_DIR)/build-playground-$(TIMESTAMP).log

##@ Setup

# Create venv only if it doesn't exist
$(VENV_BIN)/activate:
@echo "Creating Python virtual environment at $(VENV)..."
python3 -m venv $(VENV)

# Install/update dependencies when mkosi version changed
$(VENV_MARKER): $(VENV_BIN)/activate
@echo "Installing mkosi (commit: $(MKOSI_COMMIT))..."
@rm -f $(VENV)/.installed-*
$(VENV_BIN)/pip install -q --upgrade pip
$(VENV_BIN)/pip install -q git+https://github.com/systemd/mkosi.git@$(MKOSI_COMMIT)
@touch $@
@echo "Installed: $$($(MKOSI) --version)"

.PHONY: setup
setup: $(VENV_MARKER) ## Setup build environment (venv + mkosi)
@echo "Environment ready. mkosi: $$($(MKOSI) --version)"

##@ Utilities

.PHONY: clean
clean: ## Remove build artifacts (keeps venv)
git clean -fdX mkosi.output mkosi.builddir
rm -rf $(LOGS_DIR)

.PHONY: clean-all
clean-all: clean ## Remove all artifacts including venv
rm -rf $(VENV)
1 change: 0 additions & 1 deletion mkosi.images/buildernet-qemu/mkosi.postoutput
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ rm -rf ${OUTPUTDIR}/esp

cd ${OUTPUTDIR}
qemu-img convert -f raw -O qcow2 ${OUTPUTDIR}/${IMAGE_ID}_${IMAGE_VERSION}.raw ${OUTPUTDIR}/${IMAGE_ID}_${IMAGE_VERSION}.qcow2
rm -f ${OUTPUTDIR}/${IMAGE_ID}_${IMAGE_VERSION}.raw
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using .raw image in playground.
Hmm, let me switch to qcow2. Is that ok?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted and switched to qcow2.

5 changes: 5 additions & 0 deletions mkosi.images/buildernet/mkosi.conf.d/playground.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Match]
Profiles=playground

[Include]
Include=../../mkosi.profiles/playground
11 changes: 11 additions & 0 deletions mkosi.profiles/playground/mkosi.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Content]
# Playground profile - disables services that require external dependencies
# Use with devtools profile for full debugging capabilities:
# mkosi --profile=playground,devtools

Packages=login

# BuilderHub URL override for playground
# 10.0.2.2 is the QEMU user networking default gateway that maps to host
# 8888 is the port of the builder-hub-proxy service
Environment=BUILDERNET_BUILDERHUB_URL=http://10.0.2.2:8888
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Playground profile - disables services requiring external dependencies
#
# Services can be enabled at runtime with:
# systemctl enable --now <service>.service

# Downloads Reth state snapshot from S3
disable reth-sync.service

# Ships logs to CloudWatch, metrics to Prometheus
disable vector.service

# ACME services
# - Issues TLS certs via Let's Encrypt
disable acme-le.service
# - Renews Let's Encrypt TLS certs
disable acme-le-renewal.service
# - Metrics exporter
disable acme-le-textfile-collector.service

# REST API for node operator management
# Note: keep it enabled, and explore if playground cli can take advantage of it
# disable operator-api.service

# Fetches rbuilder-bidding from private GitHub
# (static unit - no [Install] section, cannot be disabled via preset)
# disable rbuilder-bidding-downloader.service

# Rebalances ETH across builder wallets
disable rbuilder-rebalancer.service

# Watches external source, reloads rbuilder config
disable config-watchdog.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Service]
Environment=
ExecStart=
ExecStart=/usr/bin/persistent-setup --device-lun 10 --key-file /etc/disk-encryption-key
TimeoutStartSec=120
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Fetch configuration files from playground
Wants=network-online.target render-config.service
After=network.target network-online.target render-config.service persistent-setup.service
Before=reth.service lighthouse.service
ConditionPathIsDirectory=!/var/lib/persistent/playground

[Service]
Type=oneshot
User=root
StateDirectory=persistent/playground
ExecStart=/usr/local/bin/playground-config-fetch

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[Service]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this?

[Content]
Autologin=true

should solve it as in the local profile

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check this approach

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to mkosi configuration.

ExecStart=
ExecStart=-/sbin/agetty --autologin bnet --noclear %I $TERM
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[general]
listen_addr = "0.0.0.0:3535"
pipe_file = "/run/operator-api/journal.fifo"
pprof = false
log_json = true
log_debug = false

# HTTP Basic Auth: the salt, and where to persistently store the hashed secret
basic_auth_secret_path = "/var/lib/persistent/operator-api/basic-auth-hash"
basic_auth_secret_salt = "bb36ef0b6643"

# HTTP server timeouts
# http_read_timeout_ms = 2500
# http_write_timeout_ms = 2500

# TLS configuration
tls_enabled = true
tls_create_if_missing = true
tls_cert_path = "/var/lib/persistent/operator-api/cert.pem"
tls_key_path = "/var/lib/persistent/operator-api/key.pem"

# Make sure to update buildernet/mkosi.extra/etc/sudoers.d/operator-api
# when adding a new action
[actions]
reboot = "sudo systemctl --no-pager --no-ask-password reboot"
rbuilder_restart = "sudo systemctl --no-pager --no-ask-password restart rbuilder-operator"
rbuilder_stop = "sudo systemctl --no-pager --no-ask-password stop rbuilder-operator"
fetch_config = "sudo systemctl --no-pager --no-ask-password start fetch-config"
rbuilder_bidding_restart = "sudo systemctl --no-pager --no-ask-password restart rbuilder-bidding"
ssh_stop = "sudo systemctl --no-pager --no-ask-password stop ssh.service ssh.socket"
ssh_start = "sudo systemctl --no-pager --no-ask-password start ssh.service ssh.socket"
haproxy_restart = "sudo systemctl --no-pager --no-ask-password restart haproxy"

[file_uploads]
rbuilder_blocklist = "/var/lib/persistent/rbuilder-operator/rbuilder.blocklist.json"
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[Service]
# Clear the default ExecStart and use playground testnet config (no checkpoint sync)
ExecStart=
ExecStart=/usr/bin/lighthouse bn \
--testnet-dir /var/lib/persistent/playground/testnet \
--execution-endpoint http://localhost:8551 \
--execution-jwt /run/eth-jwt/jwt \
--suggested-fee-recipient 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990 \
--always-prepare-payload \
--prepare-payload-lookahead 8000 \
--http \
--http-port 3500 \
--http-address 127.0.0.1 \
--port 9000 \
--metrics \
--metrics-address 127.0.0.1 \
--metrics-port 5054 \
--datadir %S/persistent/lighthouse \
--enable-private-discovery \
--disable-peer-scoring \
--disable-packet-filter \
--libp2p-addresses "{{{playground.cl_libp2p_addr}}}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[Service]
# Clear the default ExecStart and use playground genesis
ExecStart=
ExecStart=/usr/bin/reth node \
--full \
--chain /var/lib/persistent/playground/genesis.json \
--datadir %S/persistent/reth \
--authrpc.addr 127.0.0.1 \
--authrpc.jwtsecret /run/eth-jwt/jwt \
--authrpc.port 8551 \
--http \
--http.addr 127.0.0.1 \
--http.port 8545 \
--http.api "eth,net,web3,trace,rpc,debug,txpool" \
--ws \
--ws.addr 127.0.0.1 \
--ws.port 8546 \
--ws.api "eth,net,trace,web3,rpc,debug,txpool" \
--log.stdout.format json \
--log.file.max-files 0 \
--metrics "127.0.0.1:9001" \
--engine.persistence-threshold=0 \
--engine.memory-block-buffer-target=0 \
--ipcpath=%t/reth/reth.ipc \
--trusted-peers "{{{playground.el_bootnode}}}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/bash
#
# Fetches devnet configuration files from playground HTTP service.
# Base URL is injected via mustache from builderhub config.
#

set -eu -o pipefail

source /usr/bin/helper-functions.sh

# Base URL from BuilderHub config (injected via mustache)
BASE_URL="{{{playground.artifacts_url}}}"

# Target directory
PLAYGROUND_DIR="/var/lib/persistent/playground"

# File mappings: source_path -> target_path
# Source paths are relative to BASE_URL
declare -A FILES=(
["genesis.json"]="${PLAYGROUND_DIR}/genesis.json"
["testnet/config.yaml"]="${PLAYGROUND_DIR}/testnet/config.yaml"
["testnet/genesis.ssz"]="${PLAYGROUND_DIR}/testnet/genesis.ssz"
["testnet/genesis_validators_root.txt"]="${PLAYGROUND_DIR}/testnet/genesis_validators_root.txt"
["testnet/deploy_block.txt"]="${PLAYGROUND_DIR}/testnet/deploy_block.txt"
["testnet/deposit_contract_block.txt"]="${PLAYGROUND_DIR}/testnet/deposit_contract_block.txt"
["testnet/boot_enr.yaml"]="${PLAYGROUND_DIR}/testnet/boot_enr.yaml"
)

log "playground-config-fetch: Starting download from ${BASE_URL}"

# Create directories
mkdir -p "${PLAYGROUND_DIR}/testnet"

# Download each file
for src in "${!FILES[@]}"; do
target="${FILES[$src]}"
url="${BASE_URL}/${src}"

log "playground-config-fetch: Downloading ${url} -> ${target}"

if ! curl -fsSL --retry 5 --retry-delay 2 -o "${target}" "${url}"; then
log "playground-config-fetch: ERROR: Failed to download ${url}"
exit 1
fi
done

log "playground-config-fetch: Successfully downloaded all config files"
13 changes: 13 additions & 0 deletions mkosi.profiles/playground/mkosi.finalize.chroot
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -euo pipefail

# Mask static units that cannot be disabled via preset
# (units without [Install] section)

# Fetches rbuilder-bidding from private GitHub
# Failing to mask with systemctl mask --force
# systemctl mask --force rbuilder-bidding-downloader.service
# error: Failed to mask unit: File '/etc/systemd/system/rbuilder-bidding-downloader.service' already exists
# Remove existing file first, then create mask symlink
rm -f /etc/systemd/system/rbuilder-bidding-downloader.service
ln -sf /dev/null /etc/systemd/system/rbuilder-bidding-downloader.service