添加 set-relay.sh

This commit is contained in:
i
2026-03-18 20:35:51 +08:00
commit 34013fda59
+363
View File
@@ -0,0 +1,363 @@
#!/usr/bin/env bash
# =============================================================================
# NetBird External Relay Server Setup Script
# Interactively generates relay.env and docker-compose.yml for one or more
# relay servers. Run this script ON each relay server, or use --dry-run to
# preview the generated files locally.
# =============================================================================
set -euo pipefail
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "${CYAN}[INFO]${RESET} $*"; }
success() { echo -e "${GREEN}[OK]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
error() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
header() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}"; }
# ── Helpers ───────────────────────────────────────────────────────────────────
ask() {
# ask <var_name> <prompt> [default]
local var="$1" prompt="$2" default="${3:-}"
local display_default=""
[[ -n "$default" ]] && display_default=" [${default}]"
while true; do
read -rp "$(echo -e "${BOLD}${prompt}${display_default}: ${RESET}")" value
value="${value:-$default}"
if [[ -n "$value" ]]; then
printf -v "$var" '%s' "$value"
return
fi
warn "This field is required."
done
}
ask_yn() {
# ask_yn <prompt> <default: y|n> → returns 0=yes 1=no
local prompt="$1" default="${2:-y}"
local opts="[Y/n]"; [[ "$default" == "n" ]] && opts="[y/N]"
read -rp "$(echo -e "${BOLD}${prompt} ${opts}: ${RESET}")" reply
reply="${reply:-$default}"
[[ "${reply,,}" == "y" ]]
}
ask_secret() {
local var="$1" prompt="$2"
while true; do
read -rsp "$(echo -e "${BOLD}${prompt}: ${RESET}")" value; echo
if [[ -n "$value" ]]; then
printf -v "$var" '%s' "$value"
return
fi
warn "Secret cannot be empty."
done
}
generate_secret() {
if command -v openssl &>/dev/null; then
openssl rand -base64 32
else
head -c 32 /dev/urandom | base64
fi
}
validate_domain() {
# Very basic domain sanity check
[[ "$1" =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$ ]]
}
# ── Arg parsing ───────────────────────────────────────────────────────────────
DRY_RUN=false
OUTPUT_DIR="."
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--output=*) OUTPUT_DIR="${arg#--output=}" ;;
-h|--help)
echo "Usage: $0 [--dry-run] [--output=<dir>]"
echo " --dry-run Print generated files to stdout instead of writing them"
echo " --output=DIR Write files to DIR instead of current directory"
exit 0 ;;
esac
done
mkdir -p "$OUTPUT_DIR"
# ── Banner ────────────────────────────────────────────────────────────────────
echo -e "${BOLD}${CYAN}"
echo "╔══════════════════════════════════════════════════════════╗"
echo "║ NetBird External Relay Server Setup Wizard ║"
echo "╚══════════════════════════════════════════════════════════╝${RESET}"
echo ""
echo "This script generates relay.env and docker-compose.yml"
echo "for one or more NetBird relay servers."
$DRY_RUN && warn "DRY-RUN mode: files will be printed, not written."
echo ""
# ═════════════════════════════════════════════════════════════════════════════
# STEP 1 – Auth secret
# ═════════════════════════════════════════════════════════════════════════════
header "Step 1 · Authentication Secret"
echo "All relay servers AND your main NetBird server must share the same secret."
echo ""
if ask_yn "Generate a new random secret automatically?" "y"; then
AUTH_SECRET="$(generate_secret)"
success "Generated secret: ${BOLD}${AUTH_SECRET}${RESET}"
warn "Save this — you'll need it for every relay and your main server config."
else
ask_secret AUTH_SECRET "Paste your existing shared secret"
success "Using supplied secret."
fi
# ═════════════════════════════════════════════════════════════════════════════
# STEP 2 – Number of relay servers
# ═════════════════════════════════════════════════════════════════════════════
header "Step 2 · How many relay servers?"
ask RELAY_COUNT "Number of relay servers to configure" "1"
if ! [[ "$RELAY_COUNT" =~ ^[1-9][0-9]*$ ]]; then
error "Please enter a positive integer."; exit 1
fi
# ═════════════════════════════════════════════════════════════════════════════
# STEP 3 – Per-relay configuration
# ═════════════════════════════════════════════════════════════════════════════
declare -a RELAY_DOMAINS=()
declare -a RELAY_DIRS=()
declare -a RELAY_LISTEN_PORTS=()
declare -a RELAY_STUN_PORTS_LIST=()
declare -a RELAY_TLS_MODES=()
declare -a RELAY_ENABLE_STUN=()
for (( i=1; i<=RELAY_COUNT; i++ )); do
header "Step 3.$i · Relay Server #${i}"
# ── Domain ────────────────────────────────────────────────────────────────
while true; do
ask DOMAIN " Domain name (e.g. relay-us.example.com)" ""
if validate_domain "$DOMAIN"; then break
else warn " That doesn't look like a valid domain. Try again."; fi
done
RELAY_DOMAINS+=("$DOMAIN")
# ── Listen & exposed ports ────────────────────────────────────────────────
ask LISTEN_PORT " HTTPS listen port" "443"
ask LOG_LEVEL " Log level (debug/info/warn/error)" "info"
# ── STUN ──────────────────────────────────────────────────────────────────
echo ""
if ask_yn " Enable embedded STUN server?" "y"; then
ENABLE_STUN=true
ask STUN_PORTS " STUN port(s) — comma-separated for multiple (e.g. 3478,3479)" "3478"
else
ENABLE_STUN=false
STUN_PORTS=""
fi
# ── TLS mode ──────────────────────────────────────────────────────────────
echo ""
echo " TLS mode:"
echo " 1) Let's Encrypt (automatic — server needs port 80/tcp open)"
echo " 2) Existing certificates (wildcard / own CA)"
ask TLS_MODE " Choose [1/2]" "1"
if [[ "$TLS_MODE" == "1" ]]; then
ask LE_EMAIL " Let's Encrypt email" ""
LE_DATA_DIR="/data/letsencrypt"
CERT_HOST_PATH=""; CERT_FILE=""; KEY_FILE=""
else
ask CERT_HOST_PATH " Host path to certs directory" "/opt/1panel/www/sites/${DOMAIN}/ssl"
ask CERT_FILE " Cert file path inside container" "/certs/fullchain.pem"
ask KEY_FILE " Key file path inside container" "/certs/privkey.pem"
LE_EMAIL=""; LE_DATA_DIR=""
fi
# ── Output directory ──────────────────────────────────────────────────────
ask RELAY_DIR " Output directory for this relay's files" "/opt/netbird-relay"
RELAY_DIRS+=("$RELAY_DIR")
RELAY_LISTEN_PORTS+=("$LISTEN_PORT")
RELAY_STUN_PORTS_LIST+=("$STUN_PORTS")
RELAY_TLS_MODES+=("$TLS_MODE")
RELAY_ENABLE_STUN+=("$ENABLE_STUN")
# ── Build relay.env ───────────────────────────────────────────────────────
ENV_FILE="${RELAY_DIR}/relay.env"
COMPOSE_FILE="${RELAY_DIR}/docker-compose.yml"
ENV_CONTENT="# NetBird Relay — ${DOMAIN}
# Generated by setup-relay.sh on $(date -u '+%Y-%m-%dT%H:%M:%SZ')
# ---------------------------------------------------------------
NB_LOG_LEVEL=${LOG_LEVEL}
NB_LISTEN_ADDRESS=:${LISTEN_PORT}
NB_EXPOSED_ADDRESS=rels://${DOMAIN}:${LISTEN_PORT}
NB_AUTH_SECRET=${AUTH_SECRET}
"
if [[ "$TLS_MODE" == "1" ]]; then
ENV_CONTENT+="
# TLS — Let's Encrypt (automatic certificate provisioning)
NB_LETSENCRYPT_DOMAINS=${DOMAIN}
NB_LETSENCRYPT_EMAIL=${LE_EMAIL}
NB_LETSENCRYPT_DATA_DIR=${LE_DATA_DIR}
"
else
ENV_CONTENT+="
# TLS — Existing certificates
NB_TLS_CERT_FILE=${CERT_FILE}
NB_TLS_KEY_FILE=${KEY_FILE}
"
fi
if $ENABLE_STUN; then
ENV_CONTENT+="
# Embedded STUN
NB_ENABLE_STUN=true
NB_STUN_PORTS=${STUN_PORTS}
"
else
ENV_CONTENT+="
# Embedded STUN disabled
NB_ENABLE_STUN=false
"
fi
# ── Build STUN port mappings ───────────────────────────────────────────────
STUN_PORT_LINES=""
if $ENABLE_STUN && [[ -n "$STUN_PORTS" ]]; then
IFS=',' read -ra SPORT_ARRAY <<< "$STUN_PORTS"
for SP in "${SPORT_ARRAY[@]}"; do
SP="${SP// /}"
STUN_PORT_LINES+=" - '${SP}:${SP}/udp'"$'\n'
done
fi
# ── Build volume section ───────────────────────────────────────────────────
if [[ "$TLS_MODE" == "1" ]]; then
VOLUME_LINES=" - relay_data:/data"
else
VOLUME_LINES=" - ${CERT_HOST_PATH}:$(dirname "${CERT_FILE}"):ro
- relay_data:/data"
fi
# ── Build docker-compose.yml ──────────────────────────────────────────────
if [[ "$TLS_MODE" == "1" ]]; then
LE_PORT_LINE=" - '80:80'"$'\n'
else
LE_PORT_LINE=""
fi
COMPOSE_CONTENT="# NetBird Relay — ${DOMAIN}
# Generated by setup-relay.sh on $(date -u '+%Y-%m-%dT%H:%M:%SZ')
# ---------------------------------------------------------------
services:
relay:
image: netbirdio/relay:latest
container_name: netbird-relay
restart: unless-stopped
ports:
- '${LISTEN_PORT}:${LISTEN_PORT}'
${LE_PORT_LINE}${STUN_PORT_LINES} env_file:
- relay.env
volumes:
${VOLUME_LINES}
logging:
driver: \"json-file\"
options:
max-size: \"500m\"
max-file: \"2\"
volumes:
relay_data:
"
# ── Write or print ────────────────────────────────────────────────────────
if $DRY_RUN; then
echo ""
echo -e "${BOLD}▶ ${ENV_FILE}${RESET}"
echo "────────────────────────────────────────"
echo "$ENV_CONTENT"
echo ""
echo -e "${BOLD}▶ ${COMPOSE_FILE}${RESET}"
echo "────────────────────────────────────────"
echo "$COMPOSE_CONTENT"
else
mkdir -p "$RELAY_DIR"
printf '%s' "$ENV_CONTENT" > "$ENV_FILE"
printf '%s' "$COMPOSE_CONTENT" > "$COMPOSE_FILE"
chmod 600 "$ENV_FILE" # protect the secret
success " Written: ${ENV_FILE}"
success " Written: ${COMPOSE_FILE}"
fi
done # end per-relay loop
# ═════════════════════════════════════════════════════════════════════════════
# STEP 4 – Main server snippet
# ═════════════════════════════════════════════════════════════════════════════
header "Step 4 · Main Server Configuration Snippet"
echo "Add the following relay entries to your main NetBird server's"
echo "management.json (or equivalent config), replacing any existing ones:"
echo ""
echo -e "${BOLD}Relay URLs:${RESET}"
for i in "${!RELAY_DOMAINS[@]}"; do
echo " rels://${RELAY_DOMAINS[$i]}:${RELAY_LISTEN_PORTS[$i]}"
done
echo ""
echo -e "${BOLD}STUN URLs:${RESET}"
for i in "${!RELAY_DOMAINS[@]}"; do
if [[ "${RELAY_ENABLE_STUN[$i]}" == "true" ]] && [[ -n "${RELAY_STUN_PORTS_LIST[$i]}" ]]; then
IFS=',' read -ra _SP <<< "${RELAY_STUN_PORTS_LIST[$i]}"
for _P in "${_SP[@]}"; do
_P="${_P// /}"
echo " stun:${RELAY_DOMAINS[$i]}:${_P}"
done
fi
done
echo ""
echo -e "${BOLD}Shared secret:${RESET} ${AUTH_SECRET}"
echo ""
# ═════════════════════════════════════════════════════════════════════════════
# STEP 5 – Next steps
# ═════════════════════════════════════════════════════════════════════════════
header "Step 5 · Next Steps"
for i in "${!RELAY_DOMAINS[@]}"; do
D="${RELAY_DOMAINS[$i]}"
DIR="${RELAY_DIRS[$i]}"
LP="${RELAY_LISTEN_PORTS[$i]}"
TM="${RELAY_TLS_MODES[$i]}"
ES="${RELAY_ENABLE_STUN[$i]}"
SP="${RELAY_STUN_PORTS_LIST[$i]}"
echo -e "${BOLD}Relay: ${D}${RESET}"
echo " 1. Copy ${DIR}/ to the relay server"
echo " 2. On the relay server:"
echo " cd ${DIR}"
echo " docker compose up -d"
echo " docker compose logs -f"
if [[ "$TM" == "1" ]]; then
echo " 3. Trigger TLS cert (Let's Encrypt):"
echo " curl -v https://${D}/"
echo " Expect: 404 page not found + valid LE cert"
fi
echo ""
echo -e " ${BOLD}Firewall ports to open:${RESET}"
[[ "$TM" == "1" ]] && echo " 80/tcp — Let's Encrypt HTTP challenge"
echo " ${LP}/tcp — Relay (HTTPS)"
if [[ "$ES" == "true" ]] && [[ -n "$SP" ]]; then
IFS=',' read -ra _SP <<< "$SP"
for _P in "${_SP[@]}"; do
_P="${_P// /}"
echo " ${_P}/udp — STUN"
done
fi
echo ""
done
success "Setup complete! 🎉"