Bootstrap Node Setup
Bootstrap Node Setup
Bootstrap nodes run the full stack: xe-node, Caddy, explorer, wallet, and docs. They serve as the network's public-facing infrastructure with web interfaces and TLS. This guide covers setting up a new bootstrap node — for provider-only nodes (bare metal, no web interface), see Provider Node Setup.
Each node runs Ubuntu 24.04 with Caddy, Node.js, pm2, QEMU, and Lima installed. The setup-node.sh script automates the installation on a fresh host.
#Prerequisites
- Ubuntu 24.04 LTS (or compatible Debian-based system)
- Root SSH access
- Ports 80, 443, and 9000 open in the firewall
#setup-node.sh
The setup script (explorer/deploy/setup-node.sh) installs all required software:
#!/bin/bashset -euo pipefail # Caddy (reverse proxy + TLS)apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curlcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \ gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpgcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \ tee /etc/apt/sources.list.d/caddy-stable.listapt-get update && apt-get install -y caddysystemctl disable caddy # pm2 manages itsystemctl stop caddy # Node.js 22 + pm2curl -fsSL https://deb.nodesource.com/setup_22.x | bash -apt-get install -y nodejsnpm install -g pm2pm2 startup # auto-start on boot # QEMU + Lima (for compute leasing)apt-get install -y qemu-system-x86 qemu-utilsLIMA_VERSION="1.0.6"curl -fsSL "https://github.com/lima-vm/lima/releases/download/v${LIMA_VERSION}/lima-${LIMA_VERSION}-Linux-x86_64.tar.gz" | \ tar xz -C /usr/local # Firewallufw allow 80/tcpufw allow 443/tcpufw allow 9000/tcp # Service user (non-root, required for Lima)id xe 2>/dev/null || useradd -r -m -d /home/xe -s /bin/bash xeusermod -aG kvm xe # KVM access for VM provisioning # KVM permissions (pm2 uid/gid drops supplementary groups)echo 'KERNEL=="kvm", MODE="0666"' > /etc/udev/rules.d/99-kvm.ruleschmod 666 /dev/kvm 2>/dev/null || true # Directoriesmkdir -p /opt/xe/{deploy,web/explorer,web/wallet,web/docs}mkdir -p /etc/caddymkdir -p /var/lib/xe-nodechown -R xe:xe /var/lib/xe-node#Directory layout
After setup, the host has the following structure:
/opt/xe/├── deploy/│ ├── .env # DOMAIN, CORE_DOMAIN, NODE_FLAGS│ └── ecosystem.config.js # pm2 process config├── web/│ ├── explorer/ # Explorer SPA (rsync'd by CI)│ ├── wallet/ # Wallet SPA (rsync'd by CI)│ └── docs/ # MkDocs site (rsync'd by CI)/etc/caddy/└── Caddyfile # Caddy routing config/usr/local/bin/└── xe-node # Core node binary (scp'd by CI)/var/lib/xe-node/├── badger/ # BadgerDB database├── host.key # libp2p ed25519 identity├── node.key # Node account ed25519 key├── ssh_host_key # SSH gateway host key├── images/ # VM base images (auto-downloaded)│ └── ubuntu-24.04-x86_64.img # Ubuntu cloud image (~600 MB)├── lima/ # Lima VM state (LIMA_HOME)│ └── xe-<hash>/ # Per-VM directory└── lima-templates/ # Lima YAML templates └── xe-<hash>.yaml/home/xe/ # Service user home#Per-node .env
Each node has a .env file at /opt/xe/deploy/.env. This file is created manually per node and is not managed by CI (rsync excludes it).
Standard node (no compute provider):
DOMAIN=ldn.test.networkCORE_DOMAIN=ldn.core.test.networkNODE_FLAGS=-port 9000 -api-port 8080 -api-bind 0.0.0.0 -data /var/lib/xe-node -dial /ip4/<peer1-ip>/tcp/9000/p2p/<peer1-id>,/ip4/<peer2-ip>/tcp/9000/p2p/<peer2-id>Provider node (requires bare metal or KVM-enabled host):
DOMAIN=bm1.test.networkCORE_DOMAIN=bm1.core.test.networkNODE_FLAGS=-port 9000 -api-port 8080 -api-bind 0.0.0.0 -data /var/lib/xe-node -provide -vcpus 2 -memory 2048 -disk 20 -dial /ip4/<peer1-ip>/tcp/9000/p2p/<peer1-id>,/ip4/<peer2-ip>/tcp/9000/p2p/<peer2-id>| Variable | Description |
|---|---|
DOMAIN | Primary domain for Caddy TLS (e.g., ldn.test.network) |
CORE_DOMAIN | Direct API domain (e.g., ldn.core.test.network) |
NODE_FLAGS | All xe-node CLI flags as a single string |
Caddy reads DOMAIN and CORE_DOMAIN via --envfile. The ecosystem.config.js reads NODE_FLAGS and passes it as the xe-node args.
#Initial deployment
After running setup-node.sh:
-
Create .env at
/opt/xe/deploy/.envwith the node's domain and peer dial addresses -
Copy Caddyfile to
/etc/caddy/Caddyfile -
Copy ecosystem.config.js to
/opt/xe/deploy/ -
Deploy xe-node binary to
/usr/local/bin/xe-nodeand set capabilities:shell chmod +x /usr/local/bin/xe-nodesetcap cap_net_bind_service=+ep /usr/local/bin/xe-node -
Deploy static sites to
/opt/xe/web/explorer/,/opt/xe/web/wallet/,/opt/xe/web/docs/ -
Start services:
shell cd /opt/xe/deploy && pm2 start ecosystem.config.js && pm2 save
#Updating after deployment
CI handles all updates automatically. On push to master:
- Core node: CI builds the binary, SCPs it to each host, pm2 stop/start restarts the process.
setcapis reapplied after SCP since it's lost when the binary is replaced. - Explorer/Wallet/Docs: CI builds static files and rsyncs them. No restart needed — Caddy serves them directly from disk.
- Config changes: The explorer CI rsyncs
deploy/files (Caddyfile, ecosystem.config.js) and restarts Caddy if the config changed.
#Port summary
| Port | Protocol | Service | Firewall |
|---|---|---|---|
| 9000 | TCP | libp2p (peer-to-peer networking) | Open (ufw allow) |
| 8080 | TCP | HTTP API (xe-node, localhost only via Caddy) | Not exposed directly |
| 80 | TCP | Caddy (HTTP → HTTPS redirect, ACME challenges) | Open (ufw allow) |
| 443 | TCP | Caddy (HTTPS, TLS termination) | Open (ufw allow) |
| 2222 | TCP | SSH gateway (optional, if -ssh-port set) | Open if used |
#Peer identity
The node's peer ID is derived from the ed25519 private key at {dataDir}/host.key. If this file is deleted (e.g., during a data wipe), the node generates a new identity. The -dial flags in NODE_FLAGS must then be updated on all other nodes to reference the new peer ID.
To get a node's current peer ID:
curl -s https://ldn.core.test.network/node | jq -r '.id'#See also
- Deployment Overview -- architecture and CI/CD pipeline
- Configuration -- all node flags and environment variables
- Provider Node Setup -- bare-metal provider setup (no web interface)