Skip to content

Home-lab onboarding

How a person at home — call them provider@home — joins the OpenInfra fleet and starts billing usage from a customer like coledex.com.

This document assumes the operator has already brought up the control plane (deploy/local-pilot/control-plane-up.sh on a LAN, or the Hetzner CX22 stack in docs/HETZNER-MIGRATION.md). What follows is the provider side.

[provider@home] [control plane VPS]
──────────────── ───────────────────
- openinfra-agent (systemd) - api-server (gRPC :9092)
- docker daemon - postgres
- wg-quick@openinfra ───────WireGuard────▶ wg0 (10.77.0.1)
- LUKS2 /var/lib/openinfra - OCI registry (TLS via Caddy)
- Caddy on :443 / :80

The provider host has no inbound exposure. Everything reaches it through the WireGuard tunnel it initiates outbound to the CP. That is why the install script does not ask for a public DNS name for the home host.

Before the provider runs anything, the operator mints a single-use invite tied to the provider’s expected payout wallet:

Terminal window
curl -sS -X POST https://cp.coledex.com/api/v1/admin/host-invites \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{"ttl_hours": 24, "note": "provider@home — Kuma"}' \
| jq -r '.token'
# → returns the plaintext token EXACTLY ONCE. Send it to the
# provider over a secure channel (Signal, encrypted email).

The operator also pre-allocates a WireGuard IP for this provider (e.g. 10.77.0.42) and hands the provider:

  • the invite token
  • the WG IP they should claim
  • the CP’s WG public key (printed by wg show wg0 public-key on the CP)
  • the CP’s WG endpoint (cp.coledex.com:51820)

2. Provider side — encrypted data partition

Section titled “2. Provider side — encrypted data partition”

A dedicated, LUKS2-encrypted partition for the agent’s data dir is required for any host that may end up running customer secrets. Pick a partition (the example uses /dev/sdb1) and run:

Terminal window
sudo deploy/home-lab/setup-luks2.sh /dev/sdb1

You’ll be prompted for a passphrase (twice). The script:

  • formats the partition as LUKS2 with argon2id KDF (~1 s unlock cost on a typical desktop, ~100 ms on a server)
  • creates an ext4 filesystem inside the container, labelled openinfra-data
  • adds /etc/crypttab and /etc/fstab entries with nofail so a detached/failed disk doesn’t lock the host out at boot
  • mounts at /var/lib/openinfra with 0700 perms

The script is idempotent: re-running on an already-LUKS2 device reuses the container and only refreshes crypttab/fstab.

Headless hosts: on first boot you’ll be prompted for the LUKS passphrase on the console. If the host has no console, set up a keyfile or use systemd-cryptenroll --tpm2-device=auto to bind the unlock to the TPM — that’s a per-host policy decision left to the operator and is intentionally out of scope here.

Terminal window
sudo deploy/home-lab/install-agent.sh

The installer is interactive by default and asks for:

  • Control plane public hostname (e.g. cp.coledex.com) — used to download the agent binary
  • Control plane gRPC endpoint — default 10.77.0.1:9092 (the CP’s address inside the WG tunnel)
  • Invite token — from step 1
  • Solana payout pubkey — your wallet for receiving the OINFRA SPL rewards
  • WireGuard CP endpoint / pubkey / your assigned IP — from step 1

It then:

  1. Downloads openinfra-agent-linux-<arch> from the CP’s /dist/ path (override with OPENINFRA_AGENT_URL=...)
  2. Generates a WireGuard keypair under /etc/wireguard/, writes openinfra.conf, enables wg-quick@openinfra
  3. Writes /etc/openinfra/agent.env (mode 0600) with INVITE_TOKEN, SOLANA_PAYOUT_PUBKEY, CONTROL_PLANE, DATA_DIR
  4. Drops a hardened systemd unit (ProtectSystem=strict, ReadWritePaths=/var/lib/openinfra, NoNewPrivileges=yes)
  5. Enables and starts openinfra-agent.service

At the end it prints your WireGuard public key. Send it to the operator so they can add a [Peer] entry on the CP’s wg0.

On the CP:

Terminal window
sudo wg set wg0 \
peer <provider-wg-pubkey> \
allowed-ips 10.77.0.42/32
# persist across reboots
sudo wg-quick save wg0

Within seconds the provider’s agent should complete its first Heartbeat and the host row in postgres flips to online.

On the provider host:

Terminal window
sudo systemctl status openinfra-agent
sudo journalctl -u openinfra-agent -f --since "5 min ago"
sudo wg show openinfra

Expected log lines (Block K — host_id persistence):

INFO agent starting control_plane=10.77.0.1:9092 ...
INFO connected to control plane addr=10.77.0.1:9092
INFO registered with control plane host_id=<uuid> onchain_payout=true
INFO agent running, sending heartbeats every 30s

On a subsequent restart you’ll see the host_id reused (no fresh invite needed):

INFO resuming with persisted host_id (skipping Register) host_id=<uuid>

On the CP:

Terminal window
curl -sS https://cp.coledex.com/api/v1/admin/hosts?limit=100 \
-H "Authorization: Bearer $ADMIN_JWT" | jq '.hosts[] | select(.hostname=="...")'
SymptomAction
WG handshake never completescheck operator added your pubkey to wg0; check cp.coledex.com:51820/UDP is open on the CP host
Agent logs “invite already used”the host_id file is gone or was never persisted — operator must mint a fresh invite; the old one is dead
LUKS unlock prompt loops on bootthe disk wasn’t found — boot will eventually proceed thanks to nofail, then look at journalctl -u systemd-cryptsetup@openinfra_data
Agent runs but workloads never assigncheck the provider’s payout pubkey matches what the operator registered with the invite — mismatched pubkeys land in solana:<pubkey> namespaces the CP doesn’t know about

7. What this onboarding flow does not do (yet)

Section titled “7. What this onboarding flow does not do (yet)”
  • Automatic WG peer registration: the provider’s pubkey is printed to the operator and added manually. The proto-level Register-with-pubkey-exchange path is on the platform roadmap (ARCHITECTURE.md “WG outbound” follow-up).
  • Auto-update of the agent: scoped under ARCHITECTURE.md → “Agent self-update mechanism (Post-beta)”.
  • Multi-user / shared-machine setups: the agent runs as root. Hardening for shared hosts (per-workload user namespaces) is Phase 6 work.