#!/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 <