Skip to content

Add Hetzner bare-metal provisioning for the public demo - #15

Merged
ErenAri merged 5 commits into
mainfrom
hetzner-migration
Jun 18, 2026
Merged

ErenAri merged 5 commits into
mainfrom
hetzner-migration

Conversation

@ErenAri

@ErenAri ErenAri commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Why

The Azure credit expired, so the public demo (demo.kernelguard.net) needs a new home. This adds the tooling to host it on a Hetzner bare-metal (Server Auction) box.

Why bare metal, not Hetzner Cloud: the demo boots real x86 QEMU/KVM guests on each validation. Hetzner Cloud — every tier, including dedicated-vCPU and ARM/CAX — exposes no nested virtualization (no /dev/kvm), so QEMU would fall back to TCG (~10× slower). Only dedicated/auction bare metal gives native KVM.

What

Mirrors the existing azure-* scripts but runs over plain SSH (Hetzner has no az run-command equivalent):

  • scripts/hetzner-bootstrap-vm.sh (make hetzner-bootstrap-vm) — installs toolchain + qemu-kvm, hard-fails if /dev/kvm is missing, builds bpfcompat + static validator + examples, creates the bpfcompat-demo service user (in the kvm group), installs the serve unit.
  • scripts/hetzner-configure-tls.sh (make hetzner-configure-tls) — Caddy + Let's Encrypt; ufw opens only 22/80/443 (bare metal has no cloud firewall/NSG); backend stays on 127.0.0.1:8080.
  • packaging/systemd/bpfcompat-serve.{service,env.example} — first systemd unit for the demo web server (Azure ran serve by hand). Hardening is deliberately looser than the agent unit since qemu needs /dev/kvm and the TCG JIT needs W^X.
  • docs/hetzner-runbook.md — end-to-end runbook.
  • Makefile — hetzner-bootstrap-vm / hetzner-configure-tls targets.

Security posture (unchanged from the demo today)

  • Backend never public: --addr 127.0.0.1:8080 behind Caddy + ufw.
  • BPFCOMPAT_API_ENABLE_RUNTIME_EXECUTE=false — no host eBPF loading.
  • Reports already sanitized server-side (internal/api/sanitize.go).
  • .bpfcompat/runs/** (per-run SSH keys) stays under /var/lib/bpfcompat-demo.

Test plan

Scripts are shellcheck-clean and bash -n-valid. Full validation is the live deploy once the auction box is ordered (manual, Robot panel) and its IP is known.

🤖 Generated with Claude Code

ErenAri and others added 5 commits June 18, 2026 17:13
Move the demo off the expired Azure VM onto a Hetzner bare-metal (Server
Auction) host. Bare metal is required because Hetzner Cloud (every tier,
including ARM) exposes no nested virtualization, so QEMU would fall back to
TCG; the demo boots real x86 KVM guests on each validation.

Mirrors the existing azure-* scripts but runs over plain SSH (Hetzner has no
az run-command equivalent):

- scripts/hetzner-bootstrap-vm.sh: toolchain + qemu-kvm, hard-fails if
  /dev/kvm is missing, builds bpfcompat + static validator + examples, creates
  the bpfcompat-demo service user (in the kvm group), installs the serve unit.
- scripts/hetzner-configure-tls.sh: Caddy + Let's Encrypt; ufw opens only
  22/80/443 (bare metal has no cloud firewall/NSG); backend stays on
  127.0.0.1:8080.
- packaging/systemd/bpfcompat-serve.{service,env.example}: first systemd unit
  for the demo web server (Azure ran serve by hand). Hardening is deliberately
  looser than the agent unit since qemu needs /dev/kvm and the TCG JIT needs
  W^X.
- docs/hetzner-runbook.md: end-to-end runbook.
- Makefile: hetzner-bootstrap-vm / hetzner-configure-tls targets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a credentials section so deploy secrets never land in the repo:
real *.env files (BPFCOMPAT_API_WRITE_KEY etc.; .example templates stay
tracked), SSH private keys (id_rsa/id_ed25519/*_rsa/*_ed25519), TLS material
(*.pem/*.key/*.ppk), known_hosts, and local deploy scratch (/.hetzner/,
/.secrets/). Verified the .example templates and existing *-secret* docs/scripts
remain tracked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two corrections found during the live deploy:
- bootstrap installed apt golang (1.22), but the project needs Go 1.25
  (go.mod). Install the official toolchain pinned via GO_VERSION instead.
- the serve unit's WorkingDirectory was the empty state dir, so relative data
  paths (matrices/, vm profiles) did not resolve. Point WorkingDirectory at the
  cloned repo and pass --matrix; run artifacts still go to the writable
  --workdir state dir.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Hetzner demo is served at bpfcompat.kernelguard.net; update the README
live-demo link and the runbook references accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The serve unit runs as bpfcompat-demo with WorkingDirectory=/opt/bpfcompat-src
(root-owned). It writes UI report copies to reports/ and API state to
.bpfcompat-api/ relative to that dir; create both owned by the service user so
validations can finalize. Found via a live end-to-end run where all 8 profiles
passed but the final report write hit permission denied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ErenAri
ErenAri merged commit d67c50f into main Jun 18, 2026
7 of 8 checks passed
@ErenAri
ErenAri deleted the hetzner-migration branch June 18, 2026 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant