Skip to content

Repository files navigation

iac — WorkInABox host on xcp-ng

Terraform that provisions a single Ubuntu 24.04 VM on an xcp-ng pool (via Xen Orchestra) running the full WorkInABox stack:

  • KVM + Firecracker inside the guest (nested virtualization), with a boot smoke test that fails provisioning if /dev/kvm or a real microVM boot is not available.
  • Backend (wiab) installed from its latest GitHub Release, run as a systemd service on :8080.
  • Frontend installed from its latest GitHub Release, served by nginx over HTTPS (Let's Encrypt), with /api proxied to the backend.

All in-guest setup is done by cloud-init running scripts/provision.sh.

Files

File Purpose
versions.tf Terraform + vatesfr/xenorchestra provider pin
providers.tf XO connection (url/token/insecure)
variables.tf All inputs
main.tf Template/network/SR data sources + the xenorchestra_vm
outputs.tf host_ip, url
terraform.tfvars.example Sample inputs — copy to terraform.tfvars
templates/cloud-init.yaml.tftpl cloud-config (writes env + provision.sh)
templates/network-config.yaml.tftpl Static-IP netplan config
scripts/provision.sh The actual in-guest setup
scripts/wiab-deploy.sh Release deploy + model reconcile + health-gated rollback (installed as /usr/local/bin/wiab-deploy)
scripts/wiab-cert.sh Let's Encrypt DNS-01 issue/renew (installed as /usr/local/bin/wiab-cert)
images/ Firecracker guest image builders: base/, developer/, agent/, team/, kernel/, initramfs/, and the guest systemd units under guest/

Usage

cp terraform.tfvars.example terraform.tfvars
# edit terraform.tfvars
terraform init
terraform plan
terraform apply

Watch in-guest progress: ssh ubuntu@<host_ip> then tail -f /var/log/wiab-provision.log (or /var/log/cloud-init-output.log). Note … | tee buffers, so the log can lag — cloud-init status --wait is a truer "is it done" signal.

Updating backend/frontend (no host rebuild)

The host carries a /usr/local/bin/wiab-deploy script that pulls a release, swaps the binary+libs / static bundle, restarts the backend (with a /health check and auto-rollback to the previous build on failure), and atomically repoints the frontend. cloud-init and Terraform both drive it.

To ship a new version in place (no VM recreation, no cert re-issue):

# pin the new tag(s) in terraform.tfvars
backend_version  = "v0.2.0"     # and/or frontend_version
terraform apply                 # only null_resource.deploy_app runs; VM untouched

terraform plan after a bump should show only null_resource.deploy_app being replaced — never xenorchestra_vm.host. Updates take seconds.

Notes:

  • Pin explicit tags to drive updates. A constant "latest" never changes the trigger, so to re-pull latest use terraform apply -replace=null_resource.deploy_app.
  • Rollback = set the version back to the older tag and apply (it re-downloads), or on the host wiab-deploy --backend <oldtag>.
  • Deployed versions are recorded in /etc/wiab/versions; re-deploying the same tag is a no-op.

TLS certificate (manual DNS-01)

The host serves HTTPS with a Let's Encrypt certificate obtained over the DNS-01 challenge, because it has no inbound port 80 for the usual HTTP-01 challenge. DNS for workinabox.ai is hosted at one.com, which has no API certbot can drive, so the challenge record is added by hand — issuance and renewal are manual.

The host carries /usr/local/bin/wiab-cert. Run it to obtain or renew the certificate:

sudo wiab-cert

It pauses and prints a TXT record. In the one.com DNS panel for workinabox.ai, add:

host type value
_acme-challenge.demo TXT the string wiab-cert prints (different every time)

Wait ~1–2 min for it to propagate, press Enter, and certbot verifies, issues, and installs the cert into nginx (adds listen 443 ssl + an 80→443 redirect). On success it prints the expiry date.

When to run it

Trigger Action
First time / TLS not yet configured sudo wiab-cert
Certificate nearing expiry (within the last ~30 days of its 90 days) sudo wiab-cert again
After any terraform destroy + apply (a rebuild wipes /etc/letsencrypt) sudo wiab-cert again

The cert does not auto-renew — certbot's manual authenticator can't run unattended against one.com, so its renewal timer will attempt daily and fail harmlessly; that failure is expected and is not your signal to act. Check the expiry any time with sudo certbot certificates, or set a calendar reminder ~60 days out.

Current certificate: issued 2026-08-09, expires 2026-11-07. (Renew before then; this line is updated by hand at each renewal — sudo certbot certificates is the live source of truth.)

Reaching the host by name (hosts file)

The public DNS record for demo.workinabox.ai points at the site's public IP. With no port forward, reach the host on the LAN by pointing the name at its LAN IP (192.168.1.4) in the hosts file of each machine you use — per-device, persistent across reboots. Add:

192.168.1.4  demo.workinabox.ai
  • Windows — edit C:\Windows\System32\drivers\etc\hosts as Administrator, then ipconfig /flushdns in an elevated terminal.
  • Windows + WSL — WSL2 mirrors the Windows hosts file into its own /etc/hosts on start, so the Windows entry above appears in WSL automatically. (To keep a WSL-only entry, set generateHosts=false under [network] in /etc/wsl.conf first.)
  • Linux — append the line to /etc/hosts with sudo. Flush only if a cache runs: sudo resolvectl flush-caches (systemd-resolved); otherwise /etc/hosts is read live.
  • macOS — append the line to /etc/hosts with sudo, then sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder.

Caveats: phones/tablets can't edit hosts without root, so a plain mobile device can't reach the demo this way (a LAN DNS server would be needed); and a browser with "Secure DNS"/DoH enabled bypasses the hosts file.

Notes / caveats

  • Ubuntu 24.04 template required. The backend release is built on Debian bookworm (glibc 2.36); 22.04 (glibc 2.35) may fail to run it.
  • Nested virt is experimental on xcp-ng (domain crashes / reboots reported; Intel hosts fare better than AMD). exp_nested_hvm = true enables it; the Firecracker smoke test verifies it actually works.
  • certbot is non-fatal. If DNS/NAT aren't ready at apply time the host comes up HTTP-only; re-run the certbot --nginx ... line from the provision log once ready.
  • HSTS deliberately not set (deferred future work). The host serves HTTPS with an http→https redirect but sends no Strict-Transport-Security header. HSTS wouldn't let us close port 80 anyway (still needed for Let's Encrypt HTTP-01 renewal and the first-visit redirect), and it only protects repeat visits per browser — the first visit still hits port 80 unless you also preload, which is a sticky, hard-to-undo commitment not worth it for a demo. Revisit if this becomes a real production surface or you want SSL-strip hardening for repeat visitors: add add_header Strict-Transport-Security "max-age=..." always; to the nginx 443 block (ramp max-age up from a low value), and consider preload at that point.
  • WebRTC media (mediasoup) is UDP straight to the host. The backend pins its transport range (WIAB_MEDIASOUP_MIN_PORT/MAX_PORT, default 40000-40999), so the firewall opens 40000-40999/udp — ~1,000 ports, not the ~50,000 an unbounded range would need (provision.sh:479; defaults in sfu.rs). WebRTC does not go through nginx.
  • The smoke-test kernel/rootfs URLs (fc_test_*_url) point at Firecracker CI artifacts and may need bumping over time.

About

All infrastructure as code for workinabox

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages