obmp-docker/setup.sh
sam 4bcf368af0 setup.sh: add OBMP_AUTH_MODE for local vs authelia bootstrap
The bootstrap previously hard-required OBMP_DOMAIN and OBMP_COOKIE_DOMAIN
even when a user just wanted a local lab deployment with Grafana's built-in
login -- those vars only feed Authelia's session-cookie domain and the
public URL it lives behind. On a fresh host with no FQDN this made
./setup.sh impossible to pass without inventing dummy values.

New OBMP_AUTH_MODE=local|authelia in .env (default local) gates the FQDN
validation, Authelia secret generation, Authelia config rendering, and the
auth-profile image pull/build. setup.sh also writes GF_SERVER_ROOT_URL into
.env -- http://HOST_IP:3000/grafana/ for local, https://OBMP_DOMAIN/grafana/
for authelia -- and docker-compose.yml now reads ${GF_SERVER_ROOT_URL}
instead of hardcoding the apodacalab.com fallback.

Back-compat: an existing .env with no OBMP_AUTH_MODE but a real OBMP_DOMAIN
or an existing AUTHELIA_SESSION_SECRET is inferred as 'authelia' and the
mode is persisted -- a re-run on a live Authelia host won't silently flip
it to local and break the next docker compose up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 13:35:22 -07:00

215 lines
8.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# OpenBMP stack bootstrap — idempotent. Safe to run against an existing
# deployment: every step is guarded and never overwrites live config.
#
# cp .env.example .env # first run only
# $EDITOR .env # at minimum fill in HOST_IP
# ./setup.sh # local-auth mode by default
# docker compose up -d # collector core
# docker compose --profile test up -d # + lab feeders (ExaBGP, gobgp, ...)
#
# For Authelia mode, set OBMP_AUTH_MODE=authelia plus OBMP_DOMAIN and
# OBMP_COOKIE_DOMAIN in .env before running setup.sh, then bring up with
# docker compose --profile test --profile auth up -d
#
set -euo pipefail
cd "$(dirname "$0")"
AUTHELIA_IMAGE="authelia/authelia:4.38"
# --- .env -------------------------------------------------------------------
if [ ! -f .env ]; then
cp .env.example .env
echo "Created .env from .env.example."
echo "Edit it (set HOST_IP at minimum; OBMP_DOMAIN/OBMP_COOKIE_DOMAIN only"
echo "if OBMP_AUTH_MODE=authelia), then re-run ./setup.sh"
exit 1
fi
# Read a single KEY=value from .env without sourcing it — .env contains keys
# with hyphens (PROX-CML_*) that a shell `source` would choke on.
get_env() { grep -E "^$1=" .env | head -1 | cut -d= -f2- || true; }
# Set KEY=value in .env: replace the line if present, else append.
set_env() {
local key="$1" val="$2"
if grep -qE "^${key}=" .env; then
# `|` delimiter — values are hex, no `|`.
sed -i "s|^${key}=.*|${key}=${val}|" .env
else
printf '%s=%s\n' "$key" "$val" >> .env
fi
}
OBMP_DATA_ROOT="$(get_env OBMP_DATA_ROOT)"
OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}"
OBMP_DOMAIN="$(get_env OBMP_DOMAIN)"
OBMP_COOKIE_DOMAIN="$(get_env OBMP_COOKIE_DOMAIN)"
HOST_IP="$(get_env HOST_IP)"
OBMP_AUTH_MODE="$(get_env OBMP_AUTH_MODE)"
if [ -z "$OBMP_AUTH_MODE" ]; then
# Back-compat: existing deployments predate OBMP_AUTH_MODE. If Authelia was
# ever initialized (session secret present) or a real OBMP_DOMAIN is set,
# this is an Authelia host — default to that mode rather than flipping it
# to local and breaking the next `docker compose up`. Fresh .env from
# .env.example explicitly sets OBMP_AUTH_MODE=local, so this branch only
# triggers for pre-existing installs.
if [ -n "$(get_env AUTHELIA_SESSION_SECRET)" ] || \
{ [ -n "$OBMP_DOMAIN" ] && [[ "$OBMP_DOMAIN" != changeme* ]]; }; then
OBMP_AUTH_MODE="authelia"
echo "OBMP_AUTH_MODE not set; inferred 'authelia' from existing .env"
else
OBMP_AUTH_MODE="local"
echo "OBMP_AUTH_MODE not set; defaulting to 'local'"
fi
set_env OBMP_AUTH_MODE "$OBMP_AUTH_MODE"
fi
# --- validate ---------------------------------------------------------------
# HOST_IP is always required. OBMP_DOMAIN / OBMP_COOKIE_DOMAIN are only
# required when OBMP_AUTH_MODE=authelia (Authelia needs a real cookie domain
# and the public URL it lives behind).
fail=0
required=(HOST_IP)
case "$OBMP_AUTH_MODE" in
local) ;;
authelia) required+=(OBMP_DOMAIN OBMP_COOKIE_DOMAIN) ;;
*)
echo "ERROR: OBMP_AUTH_MODE='$OBMP_AUTH_MODE' (must be 'local' or 'authelia')" >&2
exit 1
;;
esac
for var in "${required[@]}"; do
val="$(get_env "$var")"
if [ -z "$val" ] || [[ "$val" == changeme* ]]; then
echo "ERROR: $var is unset or still 'changeme' in .env" >&2
fail=1
fi
done
[ "$fail" -eq 0 ] || { echo "Fix .env and re-run." >&2; exit 1; }
echo "Auth mode: $OBMP_AUTH_MODE"
# --- privilege helper -------------------------------------------------------
# $OBMP_DATA_ROOT is often root-owned (e.g. /var/openbmp). Use sudo only if the
# current user cannot write the parent directory.
parent="$(dirname "$OBMP_DATA_ROOT")"
SUDO=""
if [ ! -w "$parent" ] || { [ -d "$OBMP_DATA_ROOT" ] && [ ! -w "$OBMP_DATA_ROOT" ]; }; then
SUDO="sudo"
echo "Note: using sudo for filesystem setup under $OBMP_DATA_ROOT"
fi
# --- data-root directory tree -----------------------------------------------
echo "Creating data tree under $OBMP_DATA_ROOT ..."
for d in config grafana grafana/provisioning kafka-data zk-data zk-log \
postgres/data postgres/ts influxdb authelia; do
$SUDO mkdir -p "$OBMP_DATA_ROOT/$d"
done
# Container processes run as assorted UIDs; lab-permissive perms.
$SUDO chmod -R 777 "$OBMP_DATA_ROOT" 2>/dev/null || true
# --- Grafana provisioning ---------------------------------------------------
echo "Syncing Grafana provisioning ..."
$SUDO cp -r obmp-grafana/provisioning/. "$OBMP_DATA_ROOT/grafana/provisioning/"
# --- Grafana root URL -------------------------------------------------------
# In local mode Grafana is hit directly on :3000; in authelia mode the public
# URL is https://OBMP_DOMAIN/grafana/. setup.sh writes the right value into
# .env so docker-compose just references ${GF_SERVER_ROOT_URL}.
case "$OBMP_AUTH_MODE" in
local) desired_root_url="http://${HOST_IP}:3000/grafana/" ;;
authelia) desired_root_url="https://${OBMP_DOMAIN}/grafana/" ;;
esac
current_root_url="$(get_env GF_SERVER_ROOT_URL)"
if [ "$current_root_url" != "$desired_root_url" ]; then
set_env GF_SERVER_ROOT_URL "$desired_root_url"
echo "Set GF_SERVER_ROOT_URL=$desired_root_url"
fi
# --- Authelia setup (authelia mode only) ------------------------------------
if [ "$OBMP_AUTH_MODE" = "authelia" ]; then
# Generate secrets only if blank/absent; never overwrite an existing value.
for key in AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY; do
cur="$(get_env "$key")"
if [ -z "$cur" ]; then
set_env "$key" "$(openssl rand -hex 32)"
echo "Generated $key"
fi
done
AUTHELIA_SESSION_SECRET="$(get_env AUTHELIA_SESSION_SECRET)"
AUTHELIA_JWT_SECRET="$(get_env AUTHELIA_JWT_SECRET)"
AUTHELIA_STORAGE_ENCRYPTION_KEY="$(get_env AUTHELIA_STORAGE_ENCRYPTION_KEY)"
# Config files: fresh-deploy only — never clobber a live config.
export AUTHELIA_SESSION_SECRET AUTHELIA_JWT_SECRET AUTHELIA_STORAGE_ENCRYPTION_KEY \
OBMP_DOMAIN OBMP_COOKIE_DOMAIN
SUBST='${AUTHELIA_SESSION_SECRET} ${AUTHELIA_JWT_SECRET} ${AUTHELIA_STORAGE_ENCRYPTION_KEY} ${OBMP_DOMAIN} ${OBMP_COOKIE_DOMAIN}'
if [ ! -f "$OBMP_DATA_ROOT/authelia/configuration.yml" ]; then
envsubst "$SUBST" < authelia/configuration.yml.template \
| $SUDO tee "$OBMP_DATA_ROOT/authelia/configuration.yml" > /dev/null
echo "Rendered authelia/configuration.yml"
else
echo "authelia/configuration.yml exists — left untouched"
fi
if [ ! -f "$OBMP_DATA_ROOT/authelia/users_database.yml" ]; then
$SUDO cp authelia/users_database.yml.template \
"$OBMP_DATA_ROOT/authelia/users_database.yml"
echo "Rendered authelia/users_database.yml (demo user: openbmp)"
else
echo "authelia/users_database.yml exists — left untouched"
fi
else
echo "Skipping Authelia setup (OBMP_AUTH_MODE=$OBMP_AUTH_MODE)"
fi
# --- gobgpd.conf rendering (host-side -- gobgp image is distroless) ---------
# Render gobgp{,-evpn}/gobgpd.conf from gobgpd.conf.tmpl, substituting
# __HOST_IP__ with $HOST_IP. The rendered .conf is gitignored.
for d in gobgp gobgp-evpn; do
if [ -f "$d/gobgpd.conf.tmpl" ]; then
sed -e "s/__HOST_IP__/$HOST_IP/g" "$d/gobgpd.conf.tmpl" > "$d/gobgpd.conf"
echo "Rendered $d/gobgpd.conf (HOST_IP=$HOST_IP)"
fi
done
# --- images -----------------------------------------------------------------
# Always pull / build the `test` profile (ExaBGP, traffic-gen, gobgp -- the
# lab feeder bits). Pull `auth` images only when that mode is selected so a
# local-mode bootstrap on a fresh host doesn't drag in Authelia + portal.
echo "Pulling and building images ..."
if [ "$OBMP_AUTH_MODE" = "authelia" ]; then
docker compose --profile test --profile auth pull --quiet
docker compose --profile test --profile auth build
else
docker compose --profile test pull --quiet
docker compose --profile test build
fi
# --- done -------------------------------------------------------------------
cat <<EOF
Setup complete.
EOF
if [ "$OBMP_AUTH_MODE" = "authelia" ]; then
cat <<EOF
docker compose up -d # BMP collector core
docker compose --profile test --profile auth up -d # full stack (lab + auth)
Grafana: https://${OBMP_DOMAIN}/grafana/ (behind Authelia)
EOF
else
cat <<EOF
docker compose up -d # BMP collector core
docker compose --profile test up -d # collector + lab feeders (ExaBGP, gobgp, traffic-gen)
Grafana: http://${HOST_IP}:3000/grafana/ (login: admin / openbmp)
To switch on Authelia later: set OBMP_AUTH_MODE=authelia + OBMP_DOMAIN +
OBMP_COOKIE_DOMAIN in .env, re-run ./setup.sh, then add --profile auth.
EOF
fi