From 753bdc0848beadede7f3b172a35a00db8c5a8da3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=8C?= Date: Wed, 18 Mar 2026 20:57:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20setup-relay.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup-relay.sh | 105 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/setup-relay.sh b/setup-relay.sh index da80f02..c344520 100644 --- a/setup-relay.sh +++ b/setup-relay.sh @@ -56,7 +56,38 @@ ask_secret() { done } -generate_secret() { +parse_existing_env() { + # parse_existing_env + # Sets EX_* variables from an existing relay.env file. + local envfile="$1" + [[ -f "$envfile" ]] || return 1 + + _get() { grep -m1 "^${1}=" "$envfile" 2>/dev/null | cut -d= -f2- || true; } + + EX_LOG_LEVEL="$(_get NB_LOG_LEVEL)" + EX_AUTH_SECRET="$(_get NB_AUTH_SECRET)" + EX_LE_EMAIL="$(_get NB_LETSENCRYPT_EMAIL)" + EX_CERT_FILE="$(_get NB_TLS_CERT_FILE)" + EX_KEY_FILE="$(_get NB_TLS_KEY_FILE)" + EX_ENABLE_STUN="$(_get NB_ENABLE_STUN)" + EX_STUN_PORTS="$(_get NB_STUN_PORTS)" + + # Parse domain and port from NB_EXPOSED_ADDRESS=rels://domain:port + local exposed; exposed="$(_get NB_EXPOSED_ADDRESS)" + EX_DOMAIN="${exposed#rels://}"; EX_DOMAIN="${EX_DOMAIN%%:*}" + EX_LISTEN_PORT="${exposed##*:}" + + # Detect TLS mode + local le_domains; le_domains="$(_get NB_LETSENCRYPT_DOMAINS)" + if [[ -n "$le_domains" ]]; then + EX_TLS_MODE="1" + elif [[ -n "$EX_CERT_FILE" ]]; then + EX_TLS_MODE="2" # could be self-signed, but treat as existing cert + else + EX_TLS_MODE="1" # fallback + fi +} + if command -v openssl &>/dev/null; then openssl rand -base64 32 else @@ -104,7 +135,17 @@ 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 +# Try to pre-read an existing secret from /opt/netbird-relay/relay.env +_PREREAD_SECRET="" +if [[ -f "/opt/netbird-relay/relay.env" ]]; then + _PREREAD_SECRET="$(grep -m1 '^NB_AUTH_SECRET=' /opt/netbird-relay/relay.env 2>/dev/null | cut -d= -f2- || true)" +fi + +if [[ -n "$_PREREAD_SECRET" ]]; then + info "Found existing secret in /opt/netbird-relay/relay.env" + ask AUTH_SECRET "Keep existing secret or enter a new one" "$_PREREAD_SECRET" + success "Using secret." +elif 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." @@ -136,23 +177,36 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do header "Step 3.$i · Relay Server #${i}" + # ── Output directory (ask first so we can read existing config) ─────────── + ask RELAY_DIR " Output directory for this relay's files" "/opt/netbird-relay" + + # ── Load existing config as defaults if relay.env is present ───────────── + EX_DOMAIN=""; EX_LISTEN_PORT="443"; EX_LOG_LEVEL="info" + EX_AUTH_SECRET=""; EX_LE_EMAIL=""; EX_CERT_FILE=""; EX_KEY_FILE="" + EX_ENABLE_STUN="true"; EX_STUN_PORTS="3478"; EX_TLS_MODE="1" + _EXISTING_ENV="${RELAY_DIR}/relay.env" + if parse_existing_env "$_EXISTING_ENV"; then + info " Found existing config in ${_EXISTING_ENV} — using as defaults." + fi + # ── Domain ──────────────────────────────────────────────────────────────── while true; do - ask DOMAIN " Domain name (e.g. relay-us.example.com)" "" + ask DOMAIN " Domain name (e.g. relay-us.example.com)" "${EX_DOMAIN}" 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" + ask LISTEN_PORT " HTTPS listen port" "${EX_LISTEN_PORT:-443}" + ask LOG_LEVEL " Log level (debug/info/warn/error)" "${EX_LOG_LEVEL:-info}" # ── STUN ────────────────────────────────────────────────────────────────── echo "" - if ask_yn " Enable embedded STUN server?" "y"; then + _STUN_DEFAULT="y"; [[ "${EX_ENABLE_STUN}" == "false" ]] && _STUN_DEFAULT="n" + if ask_yn " Enable embedded STUN server?" "$_STUN_DEFAULT"; then ENABLE_STUN=true - ask STUN_PORTS " STUN port(s) — comma-separated for multiple (e.g. 3478,3479)" "3478" + ask STUN_PORTS " STUN port(s) — comma-separated for multiple (e.g. 3478,3479)" "${EX_STUN_PORTS:-3478}" else ENABLE_STUN=false STUN_PORTS="" @@ -164,22 +218,28 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do echo " 1) Let's Encrypt (automatic — server needs port 80/tcp open)" echo " 2) Existing certificates (wildcard / own CA)" echo " 3) Self-signed certificate (generated by this script)" - ask TLS_MODE " Choose [1/2/3]" "1" + ask TLS_MODE " Choose [1/2/3]" "${EX_TLS_MODE:-1}" if [[ "$TLS_MODE" == "1" ]]; then - ask LE_EMAIL " Let's Encrypt email" "" + ask LE_EMAIL " Let's Encrypt email" "${EX_LE_EMAIL}" LE_DATA_DIR="/data/letsencrypt" - CERT_HOST_PATH=""; CERT_FILE=""; KEY_FILE=""; SELFSIGN_DAYS="" + CERT_HOST_PATH=""; CERT_FILE=""; KEY_FILE="" elif [[ "$TLS_MODE" == "2" ]]; then - 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=""; SELFSIGN_DAYS="" + # Derive existing host path from cert file path stored in env (best-effort) + _EX_CERT_HOST="" + [[ -n "$EX_CERT_FILE" ]] && _EX_CERT_HOST="$( + grep -m1 'volumes:' -A5 "${RELAY_DIR}/docker-compose.yml" 2>/dev/null \ + | grep -m1 ':/certs:ro\|:/certs ' \ + | awk -F: '{print $1}' | sed 's/^ *- *//' || true + )" + ask CERT_HOST_PATH " Host path to certs directory" "${_EX_CERT_HOST:-/opt/1panel/www/sites/${DOMAIN}/ssl}" + ask CERT_FILE " Cert file path inside container" "${EX_CERT_FILE:-/certs/fullchain.pem}" + ask KEY_FILE " Key file path inside container" "${EX_KEY_FILE:-/certs/privkey.pem}" + LE_EMAIL=""; LE_DATA_DIR="" else echo "" echo " ── Self-signed certificate details ──" - # Subject fields ask SS_CN " Common Name (CN)" "${DOMAIN}" ask SS_O " Organization (O)" "" ask SS_OU " Organizational Unit (OU)" "" @@ -187,7 +247,6 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do ask SS_ST " State / Province (ST)" "" ask SS_L " Locality / City (L)" "" - # SANs — domain always included, allow adding more SS_SANS="DNS:${DOMAIN}" echo "" echo " Subject Alternative Names (SANs):" @@ -200,10 +259,8 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do SS_SANS+=",${_san}" done - # Validity ask SS_DAYS " Certificate validity (days)" "3650" - # Key type echo "" echo " Key type:" echo " 1) RSA" @@ -229,7 +286,6 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do esac fi - # Build subject string (only include non-empty fields) SS_SUBJ="" [[ -n "$SS_C" ]] && SS_SUBJ+="/C=${SS_C}" [[ -n "$SS_ST" ]] && SS_SUBJ+="/ST=${SS_ST}" @@ -238,20 +294,11 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do [[ -n "$SS_OU" ]] && SS_SUBJ+="/OU=${SS_OU}" SS_SUBJ+="/CN=${SS_CN}" - CERT_HOST_PATH="" # filled in after RELAY_DIR is known + CERT_HOST_PATH="" CERT_FILE="/certs/fullchain.pem" KEY_FILE="/certs/privkey.pem" LE_EMAIL=""; LE_DATA_DIR="" fi - - # ── Output directory ────────────────────────────────────────────────────── - _DEFAULT_RELAY_DIR="/opt/netbird-relay" - # If the default dir already has relay files, surface it as the default so - # the user can confirm with Enter rather than retyping the path. - if [[ -f "${_DEFAULT_RELAY_DIR}/relay.env" || -f "${_DEFAULT_RELAY_DIR}/docker-compose.yml" ]]; then - warn " ${_DEFAULT_RELAY_DIR} already contains relay files (will be overwritten if confirmed)." - fi - ask RELAY_DIR " Output directory for this relay's files" "${_DEFAULT_RELAY_DIR}" # For self-signed mode, certs live inside the relay dir [[ "$TLS_MODE" == "3" ]] && CERT_HOST_PATH="${RELAY_DIR}/certs" RELAY_DIRS+=("$RELAY_DIR")