obmp-docker/deploy.sh

932 lines
45 KiB
Bash
Raw Permalink Normal View History

2026-07-20 15:23:37 -07:00
#!/usr/bin/env bash
#
# deploy.sh - unified interactive OpenBMP stack deploy.
#
# One front door for standing up (or rebuilding) the obmp-docker stack on:
# - a WSL2 test box,
# - a native-Linux Docker host (central "store" node), or
# - a native-Linux REMOTE COLLECTOR that ingests BMP near the routers and
# forwards to a central store's Kafka (scale-out ingestion).
#
# It reconciles the host-specific values in .env FIRST (the "works everywhere"
# part), shows you a plan, and only then runs setup.sh + brings the stack up
# in stages. Interactive by default; fully scriptable with flags + --yes.
#
# ---------------------------------------------------------------------------
# USAGE
# ./deploy.sh # interactive; auto-detect + prompts
# ./deploy.sh --wsl | --prod # force host type (still prompts rest)
# ./deploy.sh --host-ip 10.0.0.50 # preset HOST_IP (internal stack IP)
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# ./deploy.sh --router-ip 10.0.0.9 --router-port 1790 # BMP-facing target
2026-07-20 15:23:37 -07:00
# ./deploy.sh --auth local|authelia
# ./deploy.sh --scope full-stack|remote|central-store
# ./deploy.sh --central-kafka 10.0.0.50:9092 # for --scope remote
2026-07-20 15:23:37 -07:00
# ./deploy.sh --reset # wipe data tree first (guarded on prod)
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# ./deploy.sh --install-prereqs # allow installing docker-ce unattended
2026-07-20 15:23:37 -07:00
# ./deploy.sh --prod --yes # unattended (needs enough presets)
# REPO_DIR=/opt/obmp-docker ./deploy.sh
#
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# ./deploy.sh teardown # stop + remove EVERYTHING, leave it down
# --wipe-data # also wipe $OBMP_DATA_ROOT (guarded;
# # backups/ preserved by default)
# --purge-images # also remove stack images
# --purge-backups # do not preserve backups/ on wipe
# --yes # unattended (requires explicit flags)
#
2026-07-20 15:23:37 -07:00
# SCOPE / DEPLOYMENT LEVELS (what each includes, excludes, ballpark sysreqs):
#
# core Collector core: zookeeper, kafka, psql, collector, psql-app,
# grafana, whois. A complete single-node store.
# INCLUDES: BMP ingest + Kafka bus + Postgres/TimescaleDB + UI.
# EXCLUDES: lab feeders, Authelia.
# SYSREQS: DIMENSION FOR BURST, NOT STEADY STATE. Postgres write
# latency is the bottleneck that saturates the whole host - the
# collector process itself is light, but it is NOT the sizing
# driver. See "BMP burst sizing" below.
# - small/lab (few peers, partial tables): 4 vCPU, 8-16 GB,
# 80+ GB SSD.
# - FULL-TABLE lab (GoBGP full v4/v6 reflected through RRs to
# several clients - the observed hammer case): 8+ vCPU,
# 32-64 GB RAM, NVMe with real IOPS headroom (>=5000). The
# load is full-table x monitored-sessions, not peer count.
# - 50-100 peers / full tables: 8-16 vCPU, 32-64 GB RAM,
# NVMe 1-2 TB.
# Storage scales with PREFIXES: ~1 GB/peer full internet table
# (+~50 MB/day timeseries); internal peers far less.
#
# feeders core + --profile test lab feeders (exabgp, gobgp, traffic-gen,
# influxdb, telegraf, rib-poller, churn/kafka-lag monitors).
# INCLUDES: everything in core + synthetic BGP/BMP generators +
# gNMI telemetry sink for lab exercise.
# EXCLUDES: Authelia.
# SYSREQS: core + ~4-8 GB RAM headroom for feeders (exabgp full
# table can hold ~900K route objects). 6+ vCPU comfortable. NOTE:
# synthetic feeders let you REPRODUCE the burst that sizes core -
# use them to load-test before pointing real routers at it.
#
# full feeders + --profile auth (Authelia + portal). Front-door auth.
# INCLUDES: everything in feeders + SSO/reverse-proxy.
# EXCLUDES: nothing.
# REQUIRES: auth mode = authelia and a real OBMP_DOMAIN.
# SYSREQS: as feeders (+ negligible for Authelia/nginx).
#
# standalone REMOTE COLLECTOR only: zookeeper + kafka + collector locally,
# forwarding parsed/raw data to a CENTRAL store's Kafka. No local
# Postgres/psql-app/Grafana - the central node owns the store.
# INCLUDES: BMP ingest near the routers; produces to central bus.
# EXCLUDES: Postgres, psql-app, Grafana, whois, feeders, Authelia.
# WHY: the collector is CPU-light and stateless; the DB is the
# heavy tier that saturates under burst. Splitting collectors out
# near the routers spreads the connect/flap burst across nodes so
# each Kafka spool absorbs only its own routers' dumps, instead of
# 18 full tables hitting one host at once (the exact failure mode
# observed on a single-node lab). Converge all data in one central
# Kafka -> one Postgres.
# SYSREQS (per remote node): 2-4 vCPU, 4-8 GB RAM, ~20-40 GB fast
# disk for the Kafka spool (must buffer a full connect/flap burst
# from its local routers without dropping). The CENTRAL store is
# still sized per the core burst guidance above.
# REQUIRES: --central-kafka HOST:PORT (the store node's Kafka).
#
# ---------------------------------------------------------------------------
# BMP BURST SIZING (why steady-state numbers mislead)
# BMP is event-driven and bursty. Steady state is trivial; the sizing events
# are (1) initial RIB dump on PEER_UP, (2) all routers reconnecting together
# (collector restart / RR failover), (3) reconvergence/flap churn.
#
# THE MULTIPLIER THAT ACTUALLY BITES - full table x sessions:
# A full v4+v6 table is ~960k v4 + ~220k v6 ~= 1.18M paths (~350-500 MB in
# OpenBMP's parsed Postgres form, several GB with history/timestamps). In an
# RR topology the SAME full table is reflected to every client, so BMP
# ingests it ONCE PER MONITORED SESSION. Observed lab: GoBGP (AS 65100) pulls
# full v4/v6 and injects into an iBGP mesh; RRs (CML/PROX-CORE-01/02) reflect
# to 7 clients; BMP-activating the RR-CLIENTS group means OpenBMP absorbs the
# full table x (RRs x clients) sessions at once. That is what saturates a
# single host - not "18 x 100k". It is a handful of speakers each carrying a
# FULL table, monitored across many reflected sessions.
# Root cause when the whole host melts: Postgres write latency + WRITE
# AMPLIFICATION (every reflected copy re-persisted) backpressuring
# psql-app -> Kafka -> the BMP TCP sessions.
#
# Mitigations: NVMe with real IOPS; BMP-monitor pre-policy on the RRs only
# (not every client session) to avoid ingesting N reflected copies of the
# same table; stagger connects (initial-refresh delay/spread); raise
# KAFKA/PSQL_APP mem; or split ingest with --scope remote collectors.
2026-07-20 15:23:37 -07:00
# ---------------------------------------------------------------------------
#
# HOST TYPE affects default HOST_IP source, default auth/scope, reset guarding,
# and post-deploy notes (WSL prints portproxy; prod prints router guidance).
# ---------------------------------------------------------------------------
set -euo pipefail
# --- args -------------------------------------------------------------------
REPO_DIR="${REPO_DIR:-$(cd "$(dirname "$0")" && pwd)}"
ARG_HOSTTYPE="" # wsl | prod (empty = auto-detect)
ARG_HOST_IP=""
ARG_ROUTER_IP=""
ARG_ROUTER_PORT=""
ARG_AUTH="" # local | authelia
ARG_SCOPE="" # full-stack | remote | central-store (aliases accepted)
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ARG_CENTRAL_KAFKA="" # host:port for --scope remote
2026-07-20 15:23:37 -07:00
ARG_RESET=0
ARG_YES=0
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ARG_INSTALL_PREREQS=0
COMMAND="deploy" # deploy (default) | teardown
ARG_WIPE_DATA=0 # teardown: also wipe $OBMP_DATA_ROOT
ARG_PURGE_IMAGES=0 # teardown: also remove stack images
ARG_PURGE_BACKUPS=0 # teardown: do NOT preserve $OBMP_DATA_ROOT/backups
# First positional arg may be a subcommand.
case "${1:-}" in
teardown) COMMAND="teardown"; shift ;;
esac
2026-07-20 15:23:37 -07:00
while [ $# -gt 0 ]; do
case "$1" in
--wsl) ARG_HOSTTYPE="wsl" ;;
--prod) ARG_HOSTTYPE="prod" ;;
--host-ip) ARG_HOST_IP="${2:?--host-ip needs a value}"; shift ;;
--host-ip=*) ARG_HOST_IP="${1#*=}" ;;
--router-ip) ARG_ROUTER_IP="${2:?--router-ip needs a value}"; shift ;;
--router-ip=*) ARG_ROUTER_IP="${1#*=}" ;;
--router-port) ARG_ROUTER_PORT="${2:?--router-port needs a value}"; shift ;;
--router-port=*) ARG_ROUTER_PORT="${1#*=}" ;;
--auth) ARG_AUTH="${2:?--auth needs a value}"; shift ;;
--auth=*) ARG_AUTH="${1#*=}" ;;
--scope) ARG_SCOPE="${2:?--scope needs a value}"; shift ;;
--scope=*) ARG_SCOPE="${1#*=}" ;;
--central-kafka) ARG_CENTRAL_KAFKA="${2:?--central-kafka needs host:port}"; shift ;;
--central-kafka=*) ARG_CENTRAL_KAFKA="${1#*=}" ;;
--reset) ARG_RESET=1 ;;
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
--install-prereqs) ARG_INSTALL_PREREQS=1 ;;
--wipe-data) ARG_WIPE_DATA=1 ;;
--purge-images) ARG_PURGE_IMAGES=1 ;;
--purge-backups) ARG_PURGE_BACKUPS=1 ;;
2026-07-20 15:23:37 -07:00
--yes|-y) ARG_YES=1 ;;
-h|--help) grep '^#' "$0" | sed 's/^#//'; exit 0 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
shift
done
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# Shared output/prompt/.env helpers (log ok warn die hr banner step ask
# confirm get_env set_env is_ipv4) -- single definition for all deploy tooling.
. "$REPO_DIR/scripts/deploy-lib.sh"
2026-07-20 15:23:37 -07:00
# Return the host-published TCP ports this deployment type will bind, so we can
# check them for collisions BEFORE bring-up. Ports come from docker-compose.yml
# ("HOST:CONTAINER" left side). Kept explicit here (not parsed) so the check is
# predictable; keep in sync with compose if published ports change.
# full-stack / central-store: full UI+store surface. remote: just BMP+kafka.
ports_for_type() {
case "$1" in
remote)
# BMP in (router-facing) + kafka out
echo "$ROUTER_PORT 9092" ;;
central-store)
# no local collector -> no BMP listen; store+UI+bus
echo "9092 5432 3000 4300" ;;
full-stack|*)
# BMP in + kafka + postgres + grafana + whois
echo "$ROUTER_PORT 9092 5432 3000 4300" ;;
esac
}
# What is a port MAPPED to internally, for the message (router-facing -> 5000).
port_note() {
case "$1" in
"$ROUTER_PORT") echo "BMP (maps to collector :$COLLECTOR_PORT)" ;;
9092) echo "Kafka" ;;
5432) echo "Postgres" ;;
3000) echo "Grafana" ;;
4300) echo "whois" ;;
*) echo "stack service" ;;
esac
}
# Check each port for an existing listener on the host. Uses ss, falls back to
# nc/netstat. Warns per-collision (with what's likely holding it) and returns
# 1 if any collision found. Never hard-fails on its own - caller decides.
preflight_ports() {
local type="$1" p conflict=0 tool=""
if command -v ss >/dev/null 2>&1; then tool="ss"
elif command -v netstat >/dev/null 2>&1; then tool="netstat"
elif [ -r /proc/net/tcp ] || [ -r /proc/net/tcp6 ]; then tool="proc"
else
warn "No ss/netstat//proc net available - skipping port-collision check."
return 0
fi
# Build the set of listening ports once (as decimal), for the proc path.
local listening=""
if [ "$tool" = "proc" ]; then
# /proc/net/tcp{,6}: col 2 = local_addr:PORThex, col 4 = state (0A=LISTEN).
# Convert the hex port to decimal WITHOUT gawk's strtonum (mawk lacks it).
listening="$(cat /proc/net/tcp /proc/net/tcp6 2>/dev/null \
| awk '$4=="0A"{n=split($2,a,":"); print a[n]}' \
| while read -r hx; do printf '%d\n' "0x$hx"; done | sort -u)"
fi
port_busy() {
local port="$1"
case "$tool" in
ss) ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${port}\$" ;;
netstat) netstat -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${port}\$" ;;
proc) grep -qx "$port" <<<"$listening" ;;
esac
}
for p in $(ports_for_type "$type"); do
if port_busy "$p"; then
conflict=1
local who=""
if command -v ss >/dev/null 2>&1; then
who="$(ss -ltnp 2>/dev/null | grep -E "[:.]${p}\b" | grep -oE 'users:\(\("[^"]+"' | head -1 | sed 's/users:((//; s/"//g')"
fi
warn "Port ${p} ($(port_note "$p")) is ALREADY IN USE${who:+ by: $who}"
fi
done
return $conflict
}
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
cd "$REPO_DIR" || die "REPO_DIR '$REPO_DIR' not found"
[ -f docker-compose.yml ] || die "no docker-compose.yml in $REPO_DIR - set REPO_DIR"
# --- host prerequisites (fresh-WSL friendly) --------------------------------
# A pristine WSL2 Ubuntu has neither docker-ce nor systemd enabled; check the
# whole chain and either remediate (--install-prereqs / interactive offer) or
# print the exact fix instead of dying with a bare message mid-run.
install_docker_ce() {
log "Installing docker-ce from the official Docker apt repo ..."
sudo apt-get update -qq
sudo apt-get install -y -qq ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
ok "docker-ce installed"
2026-07-20 15:23:37 -07:00
}
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ensure_systemd_wsl() {
# Native docker in WSL2 needs systemd (the docker service). Pristine
# distros ship with it off; flipping it requires a WSL restart, which we
# cannot do from inside -- set it up and stop with clear instructions.
[ -d /run/systemd/system ] && return 0
grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null || return 0
warn "systemd is not running -- native docker cannot start without it."
if confirm "Enable systemd in /etc/wsl.conf now (needs a WSL restart afterwards)?"; then
if ! grep -q '^\[boot\]' /etc/wsl.conf 2>/dev/null; then
printf '\n[boot]\nsystemd=true\n' | sudo tee -a /etc/wsl.conf > /dev/null
elif ! grep -q '^systemd=true' /etc/wsl.conf; then
sudo sed -i '/^\[boot\]/a systemd=true' /etc/wsl.conf
fi
ok "systemd enabled in /etc/wsl.conf"
die "now run 'wsl --shutdown' from Windows, reopen this distro, and re-run ./deploy.sh"
fi
die "cannot continue without systemd on WSL (docker service will not start)"
2026-07-20 15:23:37 -07:00
}
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
preflight_host() {
if ! command -v docker >/dev/null; then
warn "docker is not installed on this host."
if [ "$ARG_INSTALL_PREREQS" -eq 1 ] || confirm "Install docker-ce now (official Docker apt repo)?"; then
ensure_systemd_wsl
install_docker_ce
sudo systemctl enable --now docker 2>/dev/null || true
else
die "install docker-ce first (https://docs.docker.com/engine/install/ubuntu/) or re-run with --install-prereqs"
fi
fi
docker compose version >/dev/null 2>&1 \
|| die "docker compose v2 missing - install the docker-compose-plugin package"
if ! docker info >/dev/null 2>&1; then
# client exists but daemon unreachable: systemd off (WSL) or service down
ensure_systemd_wsl
warn "docker daemon is not running - trying to start it ..."
sudo systemctl enable --now docker 2>/dev/null || true
sleep 2
docker info >/dev/null 2>&1 \
|| die "docker daemon still unreachable - check: systemctl status docker"
ok "docker daemon started"
fi
}
preflight_host
2026-07-20 15:23:37 -07:00
# --- Docker Desktop vs native-daemon guard ----------------------------------
# On WSL, Docker Desktop runs the daemon in its OWN separate distro
# (docker-desktop VM). Host bind mounts (this stack binds ${OBMP_DATA_ROOT})
# then resolve against THAT VM's filesystem, not this distro's - so a dir that
# exists in `ls` fails to mount with a misleading "no such file or directory".
# Detect it and warn loudly, because the failure is silent and wastes a deploy.
detect_docker_desktop() {
# Fast signals first (no daemon round-trip): the Desktop proxy process and
# the desktop-linux context.
if ps -eo args 2>/dev/null | grep -q '[d]ocker-desktop.*proxy'; then return 0; fi
if docker context inspect 2>/dev/null | grep -qi 'desktop-linux\|dockerDesktop'; then return 0; fi
# Authoritative: the daemon reports itself.
docker info 2>/dev/null | grep -qiE 'Operating System:.*Docker Desktop|Name:.*docker-desktop'
}
if detect_docker_desktop; then
warn "Docker Desktop detected as the active daemon."
warn "This stack uses HOST BIND MOUNTS under \$OBMP_DATA_ROOT. Under Docker"
warn "Desktop's WSL integration the daemon runs in a SEPARATE distro, so"
warn "those binds resolve against the wrong filesystem and fail to mount"
warn "with a misleading 'no such file or directory' - even though the dir"
warn "exists in your shell. Use NATIVE docker-in-WSL instead:"
warn " 1) Docker Desktop -> Settings -> Resources -> WSL Integration ->"
warn " turn OFF integration for this distro"
warn " 2) install docker-ce in this distro and: sudo systemctl enable --now docker"
warn " 3) confirm: docker info | grep -i 'operating system' (should NOT say Docker Desktop)"
if [ "$ARG_YES" -eq 1 ]; then
die "refusing to deploy onto Docker Desktop with host bind mounts under --yes"
fi
if ! confirm "Continue anyway (bind mounts will likely fail)?"; then
die "aborted - switch to native docker-in-WSL and re-run"
fi
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# --- teardown subcommand -----------------------------------------------------
# Leave-it-down teardown: removes every container across ALL compose profiles,
# the named volumes, and the rendered gitignored configs. Data-tree wipe is
# opt-in (--wipe-data or interactive), preserving backups/ unless
# --purge-backups. Images are kept unless --purge-images. Prints the
# Windows-side cleanup it cannot reach from inside WSL.
ALL_PROFILES=(--profile test --profile auth --profile evpn-test --profile scale-out)
cmd_teardown() {
banner "OpenBMP stack teardown"
local dr; dr="$( [ -f .env ] && get_env OBMP_DATA_ROOT || true )"; dr="${dr:-/var/openbmp}"
echo
log "This will remove:"
echo " - all obmp containers (every profile: core, test, auth, evpn-test, scale-out)"
echo " - docker networks + named volumes (obmp_data-volume, obmp_ts-volume)"
echo " - rendered configs (gobgp/gobgpd.conf, gobgp-evpn/gobgpd.conf)"
[ "$ARG_PURGE_IMAGES" -eq 1 ] && echo " - stack images (--purge-images)"
if [ "$ARG_WIPE_DATA" -eq 1 ]; then
echo " - DATA TREE $dr (--wipe-data)$( [ "$ARG_PURGE_BACKUPS" -eq 1 ] && echo ' INCLUDING backups/' || echo ', preserving backups/')"
else
echo " - (data tree $dr is KEPT; add --wipe-data for a full wipe)"
fi
echo " - .env is always kept"
echo
confirm "Tear the stack down?" || die "aborted - nothing was removed"
log "Stopping and removing containers (all profiles) ..."
if [ "$ARG_PURGE_IMAGES" -eq 1 ]; then
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans --rmi all 2>&1 | tail -3 || true
else
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans 2>&1 | tail -3 || true
fi
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
ok "containers, networks, volumes removed"
rm -f gobgp/gobgpd.conf gobgp-evpn/gobgpd.conf 2>/dev/null || true
ok "rendered configs removed"
if [ "$ARG_WIPE_DATA" -eq 1 ] || { [ "$ARG_YES" -eq 0 ] && confirm "ALSO wipe the data tree $dr (Postgres history, Kafka spool)?"; }; then
if [ -d "$dr" ]; then
if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then :; else
warn "Native host data wipe - this destroys all BMP history under $dr."
[ "$ARG_YES" -eq 1 ] && [ "$ARG_WIPE_DATA" -eq 0 ] && die "refusing implicit data wipe under --yes; pass --wipe-data explicitly"
if [ "$ARG_YES" -eq 0 ]; then
read -r -p "Type the data root path to confirm deletion: " __c
[ "$__c" = "$dr" ] || die "confirmation did not match - data tree untouched"
fi
fi
if [ "$ARG_PURGE_BACKUPS" -eq 1 ]; then
sudo rm -rf "${dr:?}"/* 2>/dev/null || true
ok "data tree wiped (including backups)"
else
sudo find "$dr" -mindepth 1 -maxdepth 1 ! -name backups -exec rm -rf {} + 2>/dev/null || true
ok "data tree wiped (backups/ preserved)"
fi
fi
fi
echo
hr
log "Linux-side teardown complete. Windows-side leftovers (if this was a"
log "WSL deployment) need an ELEVATED PowerShell:"
echo
echo " powershell -ExecutionPolicy Bypass -File scripts\\wsl-portproxy.ps1 -Remove"
echo
log "Verify clean: docker ps -a | grep obmp (expect nothing)"
hr
exit 0
}
[ "$COMMAND" = "teardown" ] && cmd_teardown
banner "OpenBMP stack deploy"
# --- .env bootstrap ----------------------------------------------------------
2026-07-20 15:23:37 -07:00
if [ ! -f .env ]; then
[ -f .env.example ] || die "no .env or .env.example present"
cp .env.example .env
log "Created .env from .env.example"
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# Prime sudo early if filesystem setup will need it, so the password prompt
# happens HERE (visible, at the start) instead of blocking mid-provision.
#
# `sudo -v` (and even `sudo -n -v`) demands a credential *timestamp*, which
# NOPASSWD entries never produce -- so both fail on cloud-init / CI even
# when the user has NOPASSWD:ALL. Probe with `sudo -n true` instead: it
# runs a real command through NOPASSWD when available and returns cleanly.
# If that fails AND there is no TTY, refuse loudly rather than hanging or
# dying with a confusing "authentication failed".
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
_dr_probe="$(get_env OBMP_DATA_ROOT)"; _dr_probe="${_dr_probe:-/var/openbmp}"
if [ ! -w "$(dirname "$_dr_probe")" ] || { [ -d "$_dr_probe" ] && [ ! -w "$_dr_probe" ]; }; then
if [ "$(id -u)" -ne 0 ]; then
if sudo -n true 2>/dev/null; then
log "Filesystem setup under $_dr_probe needs sudo - passwordless sudo OK."
elif [ -t 0 ]; then
log "Filesystem setup under $_dr_probe needs sudo - authenticating now."
sudo -v || die "sudo authentication failed"
else
die "sudo required for $_dr_probe but no TTY and no NOPASSWD entry for $(id -un). Grant NOPASSWD (see docs) or run interactively."
fi
2026-07-20 15:23:37 -07:00
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
fi
2026-07-20 15:23:37 -07:00
# --- 1. host type: auto-detect, then override -------------------------------
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 1 6 "Host type"
2026-07-20 15:23:37 -07:00
detected="prod"
if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then detected="wsl"; fi
if [ -n "$ARG_HOSTTYPE" ]; then
HOSTTYPE="$ARG_HOSTTYPE"
log "Host type: $HOSTTYPE (from flag; auto-detect said '$detected')"
else
log "Auto-detected host type: $detected"
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
echo " wsl = this distro runs under WSL2 (routers reach the stack via the Windows portproxy)"
echo " prod = native Linux host (routers reach the stack directly)"
2026-07-20 15:23:37 -07:00
ask HOSTTYPE "Host type (wsl/prod)" "$detected"
fi
case "$HOSTTYPE" in wsl|prod) ;; *) die "host type must be 'wsl' or 'prod' (got '$HOSTTYPE')";; esac
# --- 2. deployment TYPE (described menu, resolved before auth/endpoints) -----
# Three types from docs/DEPLOYMENT-TYPES.md. Old scope names still accepted as
2026-07-20 15:23:37 -07:00
# aliases via --scope so existing invocations don't break.
# full-stack = collocated ingest + store (the old 'feeders'/'full')
# remote = collector/forwarder only (the old 'standalone')
# central-store = store only, no local collector (new)
print_type_menu() {
cat <<'EOF'
Deployment types (what runs here / where data lands):
1) full-stack Everything on one host: collector + Kafka + Postgres +
Grafana + feeders. BMP terminates and data is stored here.
Use when one node monitors the whole fabric.
RESOURCES: prod-realistic 16 vCPU / 48-64 GB / NVMe >=250 GB;
lab-only 4 vCPU / 16 GB host / SSD.
2) remote Collector + local Kafka only, FORWARDS to a central store's
Kafka. No local Postgres/Grafana. Run near the routers to
spread ingest / distribute BMP session load.
RESOURCES: light - 2-4 vCPU / 4-8 GB / 20-40 GB fast disk
(Kafka spool only). Needs --central-kafka HOST:PORT.
3) central-store Store half: Kafka + Postgres + Grafana + feeders, NO local
collector. Ingest arrives from remote nodes over Kafka.
Run one of these behind N remote collectors.
RESOURCES: same as full-stack store tier - 16 vCPU /
48-64 GB / NVMe >=250 GB (carries all remotes' data).
Note: resource figures are engineering estimates to calibrate against your
own watermarking, not measured specs. Authelia auth is a SEPARATE toggle.
EOF
}
# normalize any alias to a canonical type
normalize_type() {
case "$1" in
full-stack|full_stack|fullstack|feeders|full|core) echo "full-stack" ;;
remote|remote-collector|standalone|collector|forwarder) echo "remote" ;;
central-store|central|store) echo "central-store" ;;
*) echo "" ;;
esac
}
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 2 6 "Deployment type"
2026-07-20 15:23:37 -07:00
if [ -n "$ARG_SCOPE" ]; then
DTYPE="$(normalize_type "$ARG_SCOPE")"
[ -n "$DTYPE" ] || die "unknown --scope '$ARG_SCOPE' (use full-stack|remote|central-store)"
else
default_type="full-stack"
if [ "$ARG_YES" -eq 0 ]; then print_type_menu; fi
_t="$default_type"
ask _t "Deployment type (full-stack/remote/central-store)" "$default_type"
DTYPE="$(normalize_type "$_t")"
[ -n "$DTYPE" ] || die "unknown type '$_t' (use full-stack|remote|central-store)"
fi
log "Deployment type: $DTYPE"
# --- 3. HOST_IP: smart default, editable, validated -------------------------
# HOST_IP is the address the INTERNAL stack advertises/binds (Kafka listener,
# gobgp->collector). On a remote collector it is also this node's own address.
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 3 6 "Addresses (internal bind + what routers target)"
echo " Two different addresses are about to be asked for:"
echo " HOST_IP = where the stack binds INTERNALLY (Kafka, gobgp). On WSL"
echo " this is the WSL VM address."
echo " Router-facing = what routers put in 'bmp server host ...'. On WSL this"
echo " is the WINDOWS LAN IP (portproxy forwards it inward)."
2026-07-20 15:23:37 -07:00
primary_ip="$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1 || true)"
wsl_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
current_ip="$(get_env HOST_IP)"
if [ "$HOSTTYPE" = "wsl" ]; then
default_ip="${ARG_HOST_IP:-${wsl_ip:-$current_ip}}"
else
default_ip="${ARG_HOST_IP:-${primary_ip:-$current_ip}}"
fi
if [ -n "$ARG_HOST_IP" ]; then
HOST_IP="$ARG_HOST_IP"
else
if [ -n "$current_ip" ] && [ "$current_ip" != "$default_ip" ]; then
warn "Existing HOST_IP in .env is '$current_ip' - may be stale (carried from another host)."
fi
ask HOST_IP "HOST_IP for the internal stack (Kafka listener / collector bind)" "$default_ip"
fi
[ -n "$HOST_IP" ] || die "HOST_IP cannot be empty"
local_addrs="$(ip -o addr show 2>/dev/null | awk '{print $4}' | cut -d/ -f1 || true)"
if [ -z "$local_addrs" ]; then
warn "Could not enumerate local addresses ('ip' missing?) - skipping HOST_IP validation."
elif ! grep -qx "$HOST_IP" <<<"$local_addrs"; then
if [ "$HOSTTYPE" = "wsl" ]; then
warn "HOST_IP $HOST_IP is not a local addr (expected on WSL; NAT/portproxy applies)."
else
warn "HOST_IP $HOST_IP is NOT a local address on this host. Local addresses:"
ip -o addr show | awk '{print " "$2" "$4}' | grep -v '127.0.0.1' >&2 || true
warn "This is the classic ported-.env bug. Only proceed if a VIP/NAT forwards to this host."
fi
fi
# --- 4. router-facing IP + port (what routers actually target for BMP) ------
# This is deliberately SEPARATE from HOST_IP:
# - On WSL, routers cannot reach the WSL NAT addr; they hit the Windows LAN
# IP, forwarded by netsh portproxy into the WSL collector.
# - On prod, routers usually reach HOST_IP directly, but a VIP/NAT/jump can
# make the router-facing address differ from the bind address.
# It feeds the printed portproxy commands and post-deploy guidance. The
# collector's own listen port stays 5000 inside the container regardless.
COLLECTOR_PORT=5000 # in-container BMP listen port (fixed by the image)
# Best-effort: from inside WSL, ask the Windows host for its LAN IP via
# powershell.exe interop. Returns empty if not WSL, powershell.exe is absent,
# or nothing plausible is found. Filters out the WSL vEthernet, loopback,
# link-local/APIPA (169.254), and picks the interface that owns the default
# route (the real LAN NIC) when possible.
detect_windows_lan_ip() {
command -v powershell.exe >/dev/null 2>&1 || return 0
# Preferred IPv4 = the source address of the default route on Windows.
local ps_out
ps_out="$(powershell.exe -NoProfile -Command \
"(Get-NetIPConfiguration | Where-Object { \$_.IPv4DefaultGateway -ne \$null -and \$_.NetAdapter.Status -eq 'Up' } | Select-Object -First 1 -ExpandProperty IPv4Address).IPAddress" \
2>/dev/null | tr -d '\r' | tr -d '[:space:]')"
# Fallback: first non-WSL, non-APIPA IPv4 on any up adapter.
if [ -z "$ps_out" ]; then
ps_out="$(powershell.exe -NoProfile -Command \
"Get-NetIPAddress -AddressFamily IPv4 | Where-Object { \$_.InterfaceAlias -notlike '*WSL*' -and \$_.IPAddress -notlike '169.254.*' -and \$_.IPAddress -ne '127.0.0.1' } | Select-Object -First 1 -ExpandProperty IPAddress" \
2>/dev/null | tr -d '\r' | tr -d '[:space:]')"
fi
# sanity: looks like a dotted quad
case "$ps_out" in
[0-9]*.[0-9]*.[0-9]*.[0-9]*) printf '%s' "$ps_out" ;;
*) return 0 ;;
esac
}
if [ "$HOSTTYPE" = "wsl" ]; then
win_lan_ip=""
if [ -z "$ARG_ROUTER_IP" ]; then
win_lan_ip="$(detect_windows_lan_ip || true)"
if [ -n "$win_lan_ip" ]; then
log "Detected Windows host LAN IP: $win_lan_ip (routers reach the stack here)"
else
warn "Could not auto-detect the Windows LAN IP - falling back to a placeholder."
warn "Find it on Windows with: Get-NetIPConfiguration (use the 'Up' adapter's IPv4Address)"
fi
fi
default_router_ip="${ARG_ROUTER_IP:-${win_lan_ip:-<WINDOWS_LAN_IP>}}"
else
default_router_ip="${ARG_ROUTER_IP:-$HOST_IP}"
fi
# The router-facing/external BMP port defaults to 1790 (the IANA-registered BMP
# port) rather than the collector's internal 5000 - avoids the crowded 5000 and
# is self-documenting. The collector still listens on 5000 INSIDE the container;
# compose publishes ${ROUTER_FACING_PORT:-1790}:5000 everywhere, so on WSL the
# portproxy forwards listenport=ROUTER_PORT to the SAME port on the WSL side.
2026-07-20 15:23:37 -07:00
BMP_STD_PORT=1790
default_router_port="${ARG_ROUTER_PORT:-$BMP_STD_PORT}"
if [ -n "$ARG_ROUTER_IP" ]; then ROUTER_IP="$ARG_ROUTER_IP"; else
ask ROUTER_IP "Router-facing IP (what 'bmp server' on the routers targets)" "$default_router_ip"
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# A failed Windows-LAN-IP autodetect leaves a <WINDOWS_LAN_IP> placeholder
# as the default; don't let a non-address sail into .env silently.
while ! is_ipv4 "$ROUTER_IP"; do
[ "$ARG_YES" -eq 1 ] && die "router-facing IP '$ROUTER_IP' is not an IPv4 address (autodetect failed?) - pass --router-ip"
warn "'$ROUTER_IP' is not an IPv4 address."
ask ROUTER_IP "Router-facing IP (dotted quad)" ""
done
2026-07-20 15:23:37 -07:00
fi
if [ -n "$ARG_ROUTER_PORT" ]; then ROUTER_PORT="$ARG_ROUTER_PORT"; else
ask ROUTER_PORT "Router-facing BMP port" "$default_router_port"
fi
[ -n "$ROUTER_IP" ] || die "router-facing IP cannot be empty"
[ -n "$ROUTER_PORT" ] || die "router-facing port cannot be empty"
# --- 5. remote: central Kafka endpoint --------------------------------------
CENTRAL_KAFKA=""
if [ "$DTYPE" = "remote" ]; then
default_ck="${ARG_CENTRAL_KAFKA:-$(get_env KAFKA_FQDN)}"
if [ -n "$ARG_CENTRAL_KAFKA" ]; then CENTRAL_KAFKA="$ARG_CENTRAL_KAFKA"; else
ask CENTRAL_KAFKA "Central store Kafka endpoint (HOST:PORT)" "$default_ck"
fi
[ -n "$CENTRAL_KAFKA" ] || die "remote type requires a central Kafka endpoint (--central-kafka HOST:PORT)"
case "$CENTRAL_KAFKA" in *:*) ;; *) die "central Kafka must be HOST:PORT (got '$CENTRAL_KAFKA')";; esac
fi
# --- 6. Authelia toggle (independent axis; N/A for remote - no local UI) -----
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 4 6 "Grafana authentication"
2026-07-20 15:23:37 -07:00
AUTH_MODE="local"
OBMP_DOMAIN="$(get_env OBMP_DOMAIN)"
OBMP_COOKIE_DOMAIN="$(get_env OBMP_COOKIE_DOMAIN)"
if [ "$DTYPE" = "remote" ]; then
AUTH_MODE="local" # remote collector runs no Grafana/portal
else
current_auth="$(get_env OBMP_AUTH_MODE)"
if [ -n "$ARG_AUTH" ]; then
AUTH_MODE="$ARG_AUTH"
else
default_auth="local"
[ "$HOSTTYPE" = "prod" ] && default_auth="${current_auth:-local}"
if [ "$ARG_YES" -eq 0 ]; then
printf '\n Authelia = SSO/reverse-proxy in front of Grafana (needs a real domain).\n local = Grafana served directly on :3000 (fine for lab/test).\n'
fi
ask AUTH_MODE "Front Grafana with Authelia? (local/authelia)" "$default_auth"
fi
case "$AUTH_MODE" in local|authelia) ;; *) die "auth mode must be 'local' or 'authelia' (got '$AUTH_MODE')";; esac
if [ "$AUTH_MODE" = "authelia" ]; then
if [ -z "$OBMP_DOMAIN" ] || [[ "$OBMP_DOMAIN" == changeme* ]] || [[ "$OBMP_DOMAIN" == nowhere.example.com ]]; then
ask OBMP_DOMAIN "Public domain fronting Grafana/Authelia" "${OBMP_DOMAIN}"
[ -n "$OBMP_DOMAIN" ] && [[ "$OBMP_DOMAIN" != nowhere.example.com ]] \
|| die "authelia mode needs a real OBMP_DOMAIN"
fi
ask OBMP_COOKIE_DOMAIN "Authelia cookie domain (parent of above)" "${OBMP_COOKIE_DOMAIN:-$OBMP_DOMAIN}"
fi
fi
OBMP_DATA_ROOT="$(get_env OBMP_DATA_ROOT)"; OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}"
# --- 7. PLAN ----------------------------------------------------------------
type_desc() {
case "$1" in
full-stack) echo "collector + Kafka + Postgres + Grafana + feeders (ingest + store on one host)" ;;
remote) echo "collector + local Kafka only -> forwards to central Kafka (no local store)" ;;
central-store) echo "Kafka + Postgres + Grafana + feeders, NO local collector (ingest via remotes)" ;;
esac
}
type_resources() {
case "$1" in
full-stack) echo "prod ~16 vCPU / 48-64 GB / NVMe >=250 GB (lab 4 vCPU / 16 GB / SSD)" ;;
remote) echo "~2-4 vCPU / 4-8 GB / 20-40 GB fast disk (Kafka spool only)" ;;
central-store) echo "~16 vCPU / 48-64 GB / NVMe >=250 GB (carries all remotes' data)" ;;
esac
}
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 5 6 "Plan review"
2026-07-20 15:23:37 -07:00
echo; hr; log "Deployment plan"; hr
# Record what is being deployed - a greenfield deploy is only reproducible if
# the checkout is pinned (branch + commit go in the plan and the terminal log).
git_desc="$(git -C "$REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
[ -n "$git_desc" ] && git_desc="$git_desc @ $(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null)$(git -C "$REPO_DIR" diff --quiet 2>/dev/null || echo ' (dirty)')"
2026-07-20 15:23:37 -07:00
cat <<EOF
Repo dir : $REPO_DIR
Checkout : ${git_desc:-not a git checkout}
2026-07-20 15:23:37 -07:00
Host type : $HOSTTYPE (auto-detected: $detected)
Deployment type : $DTYPE
$(type_desc "$DTYPE")
Resources (est) : $(type_resources "$DTYPE")
Data root : $OBMP_DATA_ROOT
HOST_IP : $HOST_IP (internal stack bind/advertise)
Router-facing : ${ROUTER_IP}:${ROUTER_PORT} (what 'bmp server' targets)
EOF
[ "$DTYPE" = "remote" ] && echo " Central Kafka : $CENTRAL_KAFKA"
[ "$DTYPE" != "remote" ] && printf ' Grafana auth : %s%s\n' "$AUTH_MODE" "$([ "$AUTH_MODE" = authelia ] && printf ' (domain: %s)' "$OBMP_DOMAIN")"
echo " Reset data : $([ "$ARG_RESET" -eq 1 ] && echo 'YES - destroys data tree' || echo 'no')"
echo
echo " Will run setup.sh (provision data tree, render configs, pull/build)"
echo " Then: clear stale volume handles -> staged bring-up -> verify"
hr
# --- port-collision preflight (before touching anything) --------------------
if preflight_ports "$DTYPE"; then
: # no collisions
else
echo
warn "One or more host ports the stack needs are already in use (see above)."
warn "If you continue, those containers will FAIL to bind and come up unhealthy."
warn "Options: stop the conflicting service, or re-run choosing different"
warn "published ports (e.g. --router-port for BMP; Grafana/Postgres/Kafka"
warn "ports are set in docker-compose.yml)."
if [ "$ARG_YES" -eq 1 ]; then
die "refusing to proceed under --yes with a port collision; resolve it or change ports"
fi
if ! confirm "Proceed ANYWAY despite the port collision?"; then
die "aborted due to port collision - nothing was modified"
fi
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# NOTE: default answer is No -- hitting Enter here ABORTS (safe default).
if ! confirm "Proceed with this plan? (y = deploy, Enter/n = abort)"; then
die "aborted before making changes - nothing was modified (answer 'y' to deploy)"
2026-07-20 15:23:37 -07:00
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
step 6 6 "Provision + staged bring-up"
2026-07-20 15:23:37 -07:00
# --- 8. write .env ----------------------------------------------------------
set_env HOST_IP "$HOST_IP"
set_env OBMP_AUTH_MODE "$AUTH_MODE"
if [ "$AUTH_MODE" = "authelia" ]; then
set_env OBMP_DOMAIN "$OBMP_DOMAIN"
set_env OBMP_COOKIE_DOMAIN "$OBMP_COOKIE_DOMAIN"
fi
# Persist router-facing target for reference/other tooling.
set_env ROUTER_FACING_IP "$ROUTER_IP"
set_env ROUTER_FACING_PORT "$ROUTER_PORT"
# Remote: point the collector at the central bus.
if [ "$DTYPE" = "remote" ]; then
set_env KAFKA_FQDN "$CENTRAL_KAFKA"
fi
log ".env reconciled for this host"
# --- 9. optional reset ------------------------------------------------------
if [ "$ARG_RESET" -eq 1 ]; then
if [ "$HOSTTYPE" = "prod" ]; then
warn "PROD reset will DELETE ALL DATA under $OBMP_DATA_ROOT (incl. Postgres BMP history)."
[ "$ARG_YES" -eq 1 ] && die "refusing unattended --reset on prod; run interactively to confirm"
read -r -p "Type the data root path to confirm deletion: " __c
[ "$__c" = "$OBMP_DATA_ROOT" ] || die "confirmation did not match - aborted"
else
warn "Resetting data tree under $OBMP_DATA_ROOT (WSL test host)"
fi
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans 2>/dev/null || true
2026-07-20 15:23:37 -07:00
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
# Wipe the tree but keep backups/ -- pg-backup.sh dumps live there and a
# reset should not silently destroy them (use 'teardown --purge-backups'
# for that).
sudo find "${OBMP_DATA_ROOT:?}" -mindepth 1 -maxdepth 1 ! -name backups -exec rm -rf {} + 2>/dev/null || true
log "Reset complete (backups/ preserved if present)"
2026-07-20 15:23:37 -07:00
fi
# --- 10. run setup.sh -------------------------------------------------------
# Clear any stale state BEFORE setup.sh, so a "failed to populate" volume
# handle from a prior aborted run can't survive into this deployment. Bind
# volumes (type:none,o:bind) cache a mount-failure state that replays even
# after the source dir exists - removing the handle forces a fresh bind.
docker compose down 2>/dev/null || true
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
[ -x ./setup.sh ] || chmod +x ./setup.sh 2>/dev/null || true
[ -f ./setup.sh ] || die "setup.sh not found in $REPO_DIR - cannot provision"
log "Running setup.sh ..."
./setup.sh || die "setup.sh failed - fix the reported issue and re-run"
# Verify setup.sh actually produced the bind-mount source dirs compose needs.
# If these are missing, every psql 'up' fails with 'no such file or directory'.
_dr="$(get_env OBMP_DATA_ROOT)"; _dr="${_dr:-/var/openbmp}"
for _d in postgres/data postgres/ts; do
if [ ! -d "$_dr/$_d" ]; then
warn "Expected bind dir $_dr/$_d is missing after setup.sh - creating it."
sudo mkdir -p "$_dr/$_d" || die "could not create $_dr/$_d"
fi
done
# Clear the volume handles again right before bring-up (setup.sh's pull/build
# or any concurrent action could have recreated them).
docker compose down 2>/dev/null || true
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
# --- 11. staged bring-up ----------------------------------------------------
wait_kafka() {
log " infra (zookeeper, kafka) ..."
docker compose up -d zookeeper kafka
for _ in $(seq 1 30); do
[ "$(docker inspect obmp-kafka --format '{{.State.Status}}' 2>/dev/null || echo x)" = running ] && break
sleep 2
done
[ "$(docker inspect obmp-kafka --format '{{.State.Status}}' 2>/dev/null)" = running ] \
|| die "kafka did not stabilise - check: docker logs obmp-kafka"
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "kafka up"
2026-07-20 15:23:37 -07:00
}
wait_postgres() {
log " waiting for postgres ..."
for _ in $(seq 1 60); do
docker exec obmp-psql pg_isready -U openbmp -d openbmp >/dev/null 2>&1 && break
sleep 3
done
}
if [ "$DTYPE" = "remote" ]; then
# Remote collector: ingest path only, forwards to central Kafka.
log "Stage 1/2 - infra"
wait_kafka
log "Stage 2/2 - collector (forwarding to $CENTRAL_KAFKA) ..."
docker compose up -d collector
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "collector up"
2026-07-20 15:23:37 -07:00
elif [ "$DTYPE" = "central-store" ]; then
# Store only: Postgres/psql-app/Grafana/feeders, NO local collector.
log "Stage 1/3 - infra"
wait_kafka
log "Stage 2/3 - store (psql, psql-app, grafana, whois) ..."
docker compose up -d psql
wait_postgres
docker compose up -d psql-app grafana whois
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "store up"
2026-07-20 15:23:37 -07:00
log "Stage 3/3 - feeders/consumers (--profile test) ..."
docker compose --profile test up -d
# ensure no local collector is running on a central-store node
docker compose stop collector 2>/dev/null || true
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "feeders up (no local collector)"
2026-07-20 15:23:37 -07:00
else
# full-stack: collector + store + feeders on one host.
profiles=(--profile test)
[ "$AUTH_MODE" = "authelia" ] && profiles+=(--profile auth)
log "Stage 1/3 - infra"
wait_kafka
log "Stage 2/3 - core (psql, collector, psql-app, grafana, whois) ..."
docker compose up -d psql
wait_postgres
docker compose up -d collector psql-app grafana whois
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "core up"
2026-07-20 15:23:37 -07:00
log "Stage 3/3 - feeders${AUTH_MODE:+ + auth if authelia} (${profiles[*]}) ..."
docker compose "${profiles[@]}" up -d
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
ok "feeders up"
2026-07-20 15:23:37 -07:00
fi
# --- 12. verify -------------------------------------------------------------
echo; log "Current state:"
docker compose ps
echo; log "Collector -> Kafka (want NO 'Failed to resolve' / 'brokers down'):"
docker logs obmp-collector --tail 8 2>&1 | sed 's/^/ /' || true
# --- 13. post-deploy notes --------------------------------------------------
echo
printf '\033[1;32m[deploy] done\033[0m\n'
deploy.sh: teardown subcommand, fresh-host preflight, guided interactive flow Implements the one-shot goal from the greenfield audit: tear down fully and/or deploy cleanly on a pristine WSL instance with nothing but the interactive script. - './deploy.sh teardown': leave-it-down removal across ALL compose profiles (core/test/auth/evpn-test/scale-out -- the old reset missed the last two), networks, named volumes, rendered gobgpd.confs. Data wipe is opt-in (--wipe-data, guarded on native hosts) and preserves backups/ unless --purge-backups; --purge-images reclaims images. Prints the Windows-side cleanup (wsl-portproxy.ps1 -Remove, new switch). - fresh-host preflight: verifies the docker DAEMON (not just the client), offers to install docker-ce from the official repo (--install-prereqs for unattended), and handles the classic pristine-WSL trap: systemd off -> writes /etc/wsl.conf [boot] systemd=true and stops with exact restart instructions instead of dying cryptically. - sudo authenticates ONCE up front (no more password prompt buried mid-provision); --reset now covers all profiles and preserves backups/ - guided flow: banner, Step N/6 headers, context lines explaining HOST_IP-vs-router-facing before the address prompts, router-facing IP validated as a dotted quad (a failed Windows-LAN autodetect can no longer write a <WINDOWS_LAN_IP> placeholder into .env), stage completions print green checkmarks, loud abort note on the plan confirm, post-deploy Verify + teardown hints. - shared helpers moved to scripts/deploy-lib.sh; setup.sh sources it too (duplicate get_env/set_env/ask/confirm definitions deleted), keeping setup.sh as a standalone provisioning primitive. Verified live on this host: teardown left zero obmp containers and removed rendered configs; unattended redeploy brought the stack back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:12:49 -07:00
hr
log "Verify (any time):"
echo " docker compose ps # every service Up / healthy"
echo " docker exec -i obmp-psql psql -U openbmp -d openbmp -c 'SELECT name, state FROM routers;'"
echo " (DB schema is created automatically by psql-app on its first run)"
log "Tear down later: ./deploy.sh teardown (add --wipe-data for a full wipe)"
hr
2026-07-20 15:23:37 -07:00
if [ "$DTYPE" = "remote" ]; then
cat <<EOF
Remote collector active. BMP ingest -> $CENTRAL_KAFKA (central store).
This node runs NO local Postgres/Grafana; view data on the central node.
Point routers' 'bmp server' at ${ROUTER_IP}:${ROUTER_PORT}.
EOF
elif [ "$DTYPE" = "central-store" ]; then
if [ "$AUTH_MODE" = "authelia" ]; then
echo " Grafana: https://${OBMP_DOMAIN}/grafana/"
else
echo " Grafana: http://${HOST_IP}:3000/grafana/"
fi
echo " Central store active. Ingest arrives from remote collectors over Kafka."
echo " No local collector here - routers point at the REMOTE nodes, not this host."
else
if [ "$AUTH_MODE" = "authelia" ]; then
echo " Grafana: https://${OBMP_DOMAIN}/grafana/"
else
echo " Grafana: http://${HOST_IP}:3000/grafana/"
fi
echo " BMP collector: routers target ${ROUTER_IP}:${ROUTER_PORT}"
fi
if [ "$HOSTTYPE" = "wsl" ] && [ "$DTYPE" != "central-store" ]; then
cat <<EOF
WSL note: HOST_IP ${HOST_IP} is the WSL VM address - reachable from Windows
and the compose network, but NOT from physical routers, and it changes on
'wsl --shutdown'. Routers must target the router-facing address
(${ROUTER_IP}:${ROUTER_PORT}) via a portproxy on the Windows host. In an
ELEVATED PowerShell, run the repo's helper (auto-detects the current WSL IP,
idempotent):
powershell -ExecutionPolicy Bypass -File scripts/wsl-portproxy.ps1 -RouterPort ${ROUTER_PORT}
or add the rules by hand:
2026-07-20 15:23:37 -07:00
netsh interface portproxy add v4tov4 listenport=${ROUTER_PORT} listenaddress=0.0.0.0 connectport=${ROUTER_PORT} connectaddress=${HOST_IP}
2026-07-20 15:23:37 -07:00
netsh interface portproxy add v4tov4 listenport=9092 listenaddress=0.0.0.0 connectport=9092 connectaddress=${HOST_IP}
New-NetFirewallRule -DisplayName "OpenBMP ${ROUTER_PORT}" -Direction Inbound -LocalPort ${ROUTER_PORT} -Protocol TCP -Action Allow
Re-run after any WSL restart (the WSL IP changes). See docs/router-bmp-config.md.
2026-07-20 15:23:37 -07:00
EOF
elif [ "$DTYPE" != "central-store" ]; then
cat <<EOF
Native host. Ensure the firewall permits inbound TCP ${ROUTER_PORT} from the
router mgmt network. Router config: see docs/router-bmp-config.md (use
2026-07-20 15:23:37 -07:00
${ROUTER_IP}:${ROUTER_PORT} as the bmp server host/port).
EOF
fi