#!/usr/bin/env bash # PUBLIC stage-0 onboarding — served verbatim by the enroll Cloudflare Worker. # Contains NO secrets. Joins the NetBird mesh, then runs the master-served # enroll.sh over the mesh. # # Run in a fresh container, as root: # curl -fsSL https://enroll.cloud.bendavies.space | sudo bash -s -- --role project-vm --project blog # The NetBird key is prompted (hidden) unless --key or NB_SETUP_KEY is given. set -euo pipefail ROLE="project-vm" PROJECT="unassigned" KEY="${NB_SETUP_KEY:-}" # Enroll endpoint: try the master's DNS name first (survives the master # changing mesh IP), then the pinned mesh IP. --enroll-url overrides both. ENROLL_URL="" ENROLL_CANDIDATES="http://st2-ackermann.mesh.bendavies.space:8080/enroll.sh http://100.85.203.248:8080/enroll.sh" while [ $# -gt 0 ]; do case "$1" in --role) [ $# -ge 2 ] || { echo "$1 requires a value" >&2; exit 2; } ROLE="$2"; shift 2;; --project) [ $# -ge 2 ] || { echo "$1 requires a value" >&2; exit 2; } PROJECT="$2"; shift 2;; --key) [ $# -ge 2 ] || { echo "$1 requires a value" >&2; exit 2; } KEY="$2"; shift 2;; --enroll-url) [ $# -ge 2 ] || { echo "$1 requires a value" >&2; exit 2; } ENROLL_URL="$2"; shift 2;; *) echo "unknown arg: $1" >&2; exit 2;; esac done [ "$(id -u)" -eq 0 ] || { echo "run as root" >&2; exit 1; } have(){ command -v "$1" >/dev/null 2>&1; } have curl || { echo "curl is required" >&2; exit 1; } have ip || { echo "iproute2 (ip) is required" >&2; exit 1; } # Prompt for the key from the terminal (stdin is the piped script under curl|bash). if [ -z "$KEY" ]; then printf 'NetBird setup key: ' > /dev/tty read -rs KEY < /dev/tty printf '\n' > /dev/tty fi [ -n "$KEY" ] || { echo "no NetBird setup key provided" >&2; exit 1; } if ! have netbird; then echo "==> installing NetBird" curl -fsSL https://pkgs.netbird.io/install.sh | sh fi echo "==> joining the mesh" # Pass the key via a root-only temp file, not argv (argv is visible in `ps`). tmp_key="$(umask 077; mktemp)" printf '%s' "$KEY" > "$tmp_key" netbird up --setup-key-file "$tmp_key" rm -f "$tmp_key" echo "==> waiting for the mesh interface" for _ in $(seq 1 30); do ip -4 -o addr show wt0 2>/dev/null | grep -q 'inet ' && break sleep 2 done ip -4 -o addr show wt0 2>/dev/null | grep -q 'inet ' \ || { echo "not on the mesh (wt0 has no IP) — check the setup key" >&2; exit 1; } echo "==> enrolling with the Salt master" tmp_enroll="$(mktemp)" fetched="" # The mesh needs a moment after `netbird up`: DNS config lands just after wt0 # gets its IP, and the WireGuard tunnel to the master takes a few seconds to # establish — so retry for up to ~60s instead of one-shot. # -4: the enroll endpoint listens on the master's mesh IPv4 only, but NetBird # DNS also returns AAAA records; without -4 curl may try the dead v6 path. for _ in $(seq 1 12); do for url in ${ENROLL_URL:-$ENROLL_CANDIDATES}; do if curl -4fsSL --connect-timeout 5 "$url" -o "$tmp_enroll"; then fetched="$url"; break 2; fi done sleep 5 done [ -n "$fetched" ] || { echo "could not reach the enroll endpoint after ~60s (tried: ${ENROLL_URL:-$ENROLL_CANDIDATES}) — is the master up on the mesh?" >&2; exit 1; } bash "$tmp_enroll" --role "$ROLE" --project "$PROJECT" || { echo "enrolment failed (the endpoint ${fetched} was reachable, but enroll.sh errored)" >&2; rm -f "$tmp_enroll"; exit 1; } rm -f "$tmp_enroll"