Add IP and pasted certificate support
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
每台 Relay 服务器需满足:
|
每台 Relay 服务器需满足:
|
||||||
|
|
||||||
- Linux,≥ 1 CPU / 1 GB RAM
|
- Linux,≥ 1 CPU / 1 GB RAM
|
||||||
- 公网 IP,且有域名解析指向该 IP
|
- 公网 IP,且有域名解析指向该 IP;脚本里的 Relay 地址也可以直接填写 IP
|
||||||
- 已安装 Docker(含 `docker compose`)
|
- 已安装 Docker(含 `docker compose`)
|
||||||
- 以下端口按实际配置放行(脚本会在最后动态列出):
|
- 以下端口按实际配置放行(脚本会在最后动态列出):
|
||||||
- `80/tcp` — Let's Encrypt HTTP challenge(仅自动证书模式需要)
|
- `80/tcp` — Let's Encrypt HTTP challenge(仅自动证书模式需要)
|
||||||
@@ -71,7 +71,8 @@ Step 3.N · Relay Server #N(每台重复)
|
|||||||
1) Let's Encrypt — 自动签发,需开放 80/tcp
|
1) Let's Encrypt — 自动签发,需开放 80/tcp
|
||||||
2) 已有证书 — 输入宿主机目录及容器内路径(均可自定义)
|
2) 已有证书 — 输入宿主机目录及容器内路径(均可自定义)
|
||||||
3) 自签证书 — 脚本调用 openssl 生成,支持完整 Subject 字段、
|
3) 自签证书 — 脚本调用 openssl 生成,支持完整 Subject 字段、
|
||||||
额外 SAN(DNS/IP)、有效期、RSA/ECC 及密钥长度选择
|
额外 SAN(DNS/IP)、有效期、RSA/ECC 及密钥长度选择
|
||||||
|
4) 粘贴证书 — 直接粘贴 PEM 证书和私钥内容,由脚本写入 certs 目录
|
||||||
|
|
||||||
Step 4 · Main Server config.yaml Snippet
|
Step 4 · Main Server config.yaml Snippet
|
||||||
→ 打印带占位符的 config.yaml 片段(不输出真实域名和密钥)
|
→ 打印带占位符的 config.yaml 片段(不输出真实域名和密钥)
|
||||||
@@ -89,7 +90,7 @@ Step 6 · Firewall Configuration(可选,可跳过)
|
|||||||
|
|
||||||
脚本支持在已有部署上重新运行以修改配置。Step 3 中先询问输出目录,若该目录下存在 `relay.env`,脚本会自动解析并将以下字段作为默认值填入后续输入框,直接回车即保留原值,输入新值则覆盖:
|
脚本支持在已有部署上重新运行以修改配置。Step 3 中先询问输出目录,若该目录下存在 `relay.env`,脚本会自动解析并将以下字段作为默认值填入后续输入框,直接回车即保留原值,输入新值则覆盖:
|
||||||
|
|
||||||
- 域名、监听端口、日志级别
|
- 域名或 IP、监听端口、日志级别
|
||||||
- STUN 开关及端口
|
- STUN 开关及端口
|
||||||
- TLS 模式、LE 邮箱、证书路径
|
- TLS 模式、LE 邮箱、证书路径
|
||||||
|
|
||||||
@@ -170,6 +171,10 @@ curl -v https://relay.example.com/
|
|||||||
|
|
||||||
证书输出到 `<输出目录>/certs/`,私钥权限自动设为 `600`。若系统未安装 `openssl`,脚本会打印等效命令供手动执行。
|
证书输出到 `<输出目录>/certs/`,私钥权限自动设为 `600`。若系统未安装 `openssl`,脚本会打印等效命令供手动执行。
|
||||||
|
|
||||||
|
**粘贴证书**
|
||||||
|
|
||||||
|
选择该模式后,脚本会提示分别粘贴证书 PEM 和私钥 PEM,保存到 `<输出目录>/certs/fullchain.pem` 与 `<输出目录>/certs/privkey.pem`,并自动挂载到容器中。
|
||||||
|
|
||||||
## 主服务器 config.yaml 配置
|
## 主服务器 config.yaml 配置
|
||||||
|
|
||||||
Step 4 输出带占位符的配置片段,需在主服务器上手动填入真实域名和密钥:
|
Step 4 输出带占位符的配置片段,需在主服务器上手动填入真实域名和密钥:
|
||||||
|
|||||||
+97
-12
@@ -101,6 +101,38 @@ validate_domain() {
|
|||||||
[[ "$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,}$ ]]
|
[[ "$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,}$ ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_ipv4() {
|
||||||
|
local IFS=.
|
||||||
|
local octet
|
||||||
|
local -a octets
|
||||||
|
|
||||||
|
read -ra octets <<< "$1"
|
||||||
|
[[ "${#octets[@]}" -eq 4 ]] || return 1
|
||||||
|
|
||||||
|
for octet in "${octets[@]}"; do
|
||||||
|
[[ "$octet" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
(( octet >= 0 && octet <= 255 )) || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_domain_or_ip() {
|
||||||
|
validate_domain "$1" || validate_ipv4 "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_multiline_block() {
|
||||||
|
# read_multiline_block <var_name> <prompt> [terminator]
|
||||||
|
local var="$1" prompt="$2" terminator="${3:-END}"
|
||||||
|
local line block=""
|
||||||
|
|
||||||
|
echo -e "${BOLD}${prompt}${RESET}"
|
||||||
|
echo " Paste the content below. Finish with a line containing only: ${terminator}"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ "$line" == "$terminator" ]] && break
|
||||||
|
block+="$line"$'\n'
|
||||||
|
done
|
||||||
|
printf -v "$var" '%s' "$block"
|
||||||
|
}
|
||||||
|
|
||||||
# ── Arg parsing ───────────────────────────────────────────────────────────────
|
# ── Arg parsing ───────────────────────────────────────────────────────────────
|
||||||
DRY_RUN=false
|
DRY_RUN=false
|
||||||
OUTPUT_DIR="."
|
OUTPUT_DIR="."
|
||||||
@@ -120,9 +152,9 @@ mkdir -p "$OUTPUT_DIR"
|
|||||||
|
|
||||||
# ── Banner ────────────────────────────────────────────────────────────────────
|
# ── Banner ────────────────────────────────────────────────────────────────────
|
||||||
echo -e "${BOLD}${CYAN}"
|
echo -e "${BOLD}${CYAN}"
|
||||||
echo "╔══════════════════════════════════════════════════════════╗"
|
echo "============================================================"
|
||||||
echo "║ NetBird External Relay Server Setup Wizard ║"
|
echo " NetBird External Relay Server Setup Wizard"
|
||||||
echo "╚══════════════════════════════════════════════════════════╝${RESET}"
|
echo "============================================================${RESET}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This script generates relay.env and docker-compose.yml"
|
echo "This script generates relay.env and docker-compose.yml"
|
||||||
echo "for one or more NetBird relay servers."
|
echo "for one or more NetBird relay servers."
|
||||||
@@ -210,9 +242,9 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do
|
|||||||
|
|
||||||
# ── Domain ────────────────────────────────────────────────────────────────
|
# ── Domain ────────────────────────────────────────────────────────────────
|
||||||
while true; do
|
while true; do
|
||||||
ask DOMAIN " Domain name (e.g. relay-us.example.com)" "${EX_DOMAIN}"
|
ask DOMAIN " Domain name or IP (e.g. relay-us.example.com / 203.0.113.10)" "${EX_DOMAIN}"
|
||||||
if validate_domain "$DOMAIN"; then break
|
if validate_domain_or_ip "$DOMAIN"; then break
|
||||||
else warn " That doesn't look like a valid domain. Try again."; fi
|
else warn " That doesn't look like a valid domain or IPv4 address. Try again."; fi
|
||||||
done
|
done
|
||||||
RELAY_DOMAINS+=("$DOMAIN")
|
RELAY_DOMAINS+=("$DOMAIN")
|
||||||
|
|
||||||
@@ -237,7 +269,14 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do
|
|||||||
echo " 1) Let's Encrypt (automatic — server needs port 80/tcp open)"
|
echo " 1) Let's Encrypt (automatic — server needs port 80/tcp open)"
|
||||||
echo " 2) Existing certificates (wildcard / own CA)"
|
echo " 2) Existing certificates (wildcard / own CA)"
|
||||||
echo " 3) Self-signed certificate (generated by this script)"
|
echo " 3) Self-signed certificate (generated by this script)"
|
||||||
ask TLS_MODE " Choose [1/2/3]" "${EX_TLS_MODE:-1}"
|
echo " 4) Paste certificate and private key"
|
||||||
|
ask TLS_MODE " Choose [1/2/3/4]" "${EX_TLS_MODE:-1}"
|
||||||
|
|
||||||
|
if [[ "$TLS_MODE" == "1" ]] && validate_ipv4 "$DOMAIN"; then
|
||||||
|
warn " Let's Encrypt does not issue certificates for raw IP addresses."
|
||||||
|
warn " Choose mode 2, 3, or 4 for IP-based deployments."
|
||||||
|
ask TLS_MODE " Re-choose TLS mode [2/3/4]" "3"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$TLS_MODE" == "1" ]]; then
|
if [[ "$TLS_MODE" == "1" ]]; then
|
||||||
ask LE_EMAIL " Let's Encrypt email" "${EX_LE_EMAIL}"
|
ask LE_EMAIL " Let's Encrypt email" "${EX_LE_EMAIL}"
|
||||||
@@ -255,6 +294,15 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do
|
|||||||
ask CERT_FILE " Cert file path inside container" "${EX_CERT_FILE:-/certs/fullchain.pem}"
|
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}"
|
ask KEY_FILE " Key file path inside container" "${EX_KEY_FILE:-/certs/privkey.pem}"
|
||||||
LE_EMAIL=""; LE_DATA_DIR=""
|
LE_EMAIL=""; LE_DATA_DIR=""
|
||||||
|
elif [[ "$TLS_MODE" == "4" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo " ── Pasted certificate details ──"
|
||||||
|
CERT_HOST_PATH="${RELAY_DIR}/certs"
|
||||||
|
CERT_FILE="/certs/fullchain.pem"
|
||||||
|
KEY_FILE="/certs/privkey.pem"
|
||||||
|
LE_EMAIL=""; LE_DATA_DIR=""
|
||||||
|
read_multiline_block PASTED_CERT " Paste the certificate PEM" "END_CERT"
|
||||||
|
read_multiline_block PASTED_KEY " Paste the private key PEM" "END_KEY"
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo " ── Self-signed certificate details ──"
|
echo " ── Self-signed certificate details ──"
|
||||||
@@ -266,10 +314,18 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do
|
|||||||
ask SS_ST " State / Province (ST)" ""
|
ask SS_ST " State / Province (ST)" ""
|
||||||
ask SS_L " Locality / City (L)" ""
|
ask SS_L " Locality / City (L)" ""
|
||||||
|
|
||||||
SS_SANS="DNS:${DOMAIN}"
|
if validate_ipv4 "$DOMAIN"; then
|
||||||
|
SS_SANS="IP:${DOMAIN}"
|
||||||
|
else
|
||||||
|
SS_SANS="DNS:${DOMAIN}"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo " Subject Alternative Names (SANs):"
|
echo " Subject Alternative Names (SANs):"
|
||||||
echo " DNS:${DOMAIN} is included automatically."
|
if validate_ipv4 "$DOMAIN"; then
|
||||||
|
echo " IP:${DOMAIN} is included automatically."
|
||||||
|
else
|
||||||
|
echo " DNS:${DOMAIN} is included automatically."
|
||||||
|
fi
|
||||||
echo " Add extra SANs one by one (IP:x.x.x.x or DNS:other.example.com)."
|
echo " Add extra SANs one by one (IP:x.x.x.x or DNS:other.example.com)."
|
||||||
echo " Press Enter on an empty line when done."
|
echo " Press Enter on an empty line when done."
|
||||||
while true; do
|
while true; do
|
||||||
@@ -318,14 +374,22 @@ for (( i=1; i<=RELAY_COUNT; i++ )); do
|
|||||||
KEY_FILE="/certs/privkey.pem"
|
KEY_FILE="/certs/privkey.pem"
|
||||||
LE_EMAIL=""; LE_DATA_DIR=""
|
LE_EMAIL=""; LE_DATA_DIR=""
|
||||||
fi
|
fi
|
||||||
# For self-signed mode, certs live inside the relay dir
|
# For generated or pasted certs, certs live inside the relay dir
|
||||||
[[ "$TLS_MODE" == "3" ]] && CERT_HOST_PATH="${RELAY_DIR}/certs"
|
[[ "$TLS_MODE" == "3" || "$TLS_MODE" == "4" ]] && CERT_HOST_PATH="${RELAY_DIR}/certs"
|
||||||
RELAY_DIRS+=("$RELAY_DIR")
|
RELAY_DIRS+=("$RELAY_DIR")
|
||||||
RELAY_LISTEN_PORTS+=("$LISTEN_PORT")
|
RELAY_LISTEN_PORTS+=("$LISTEN_PORT")
|
||||||
RELAY_STUN_PORTS_LIST+=("$STUN_PORTS")
|
RELAY_STUN_PORTS_LIST+=("$STUN_PORTS")
|
||||||
RELAY_TLS_MODES+=("$TLS_MODE")
|
RELAY_TLS_MODES+=("$TLS_MODE")
|
||||||
RELAY_ENABLE_STUN+=("$ENABLE_STUN")
|
RELAY_ENABLE_STUN+=("$ENABLE_STUN")
|
||||||
|
|
||||||
|
if [[ "$TLS_MODE" == "3" ]]; then
|
||||||
|
TLS_LABEL="Self-signed certificate"
|
||||||
|
elif [[ "$TLS_MODE" == "4" ]]; then
|
||||||
|
TLS_LABEL="Pasted certificate"
|
||||||
|
else
|
||||||
|
TLS_LABEL="Existing certificates"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Build relay.env ───────────────────────────────────────────────────────
|
# ── Build relay.env ───────────────────────────────────────────────────────
|
||||||
ENV_FILE="${RELAY_DIR}/relay.env"
|
ENV_FILE="${RELAY_DIR}/relay.env"
|
||||||
COMPOSE_FILE="${RELAY_DIR}/docker-compose.yml"
|
COMPOSE_FILE="${RELAY_DIR}/docker-compose.yml"
|
||||||
@@ -349,7 +413,7 @@ NB_LETSENCRYPT_DATA_DIR=${LE_DATA_DIR}
|
|||||||
"
|
"
|
||||||
else
|
else
|
||||||
ENV_CONTENT+="
|
ENV_CONTENT+="
|
||||||
# TLS — $([ "$TLS_MODE" == "3" ] && echo "Self-signed certificate" || echo "Existing certificates")
|
# TLS — ${TLS_LABEL}
|
||||||
NB_TLS_CERT_FILE=${CERT_FILE}
|
NB_TLS_CERT_FILE=${CERT_FILE}
|
||||||
NB_TLS_KEY_FILE=${KEY_FILE}
|
NB_TLS_KEY_FILE=${KEY_FILE}
|
||||||
"
|
"
|
||||||
@@ -442,6 +506,18 @@ volumes:
|
|||||||
echo " -subj \"${SS_SUBJ}\" \\"
|
echo " -subj \"${SS_SUBJ}\" \\"
|
||||||
echo " -addext \"subjectAltName=${SS_SANS}\""
|
echo " -addext \"subjectAltName=${SS_SANS}\""
|
||||||
fi
|
fi
|
||||||
|
if [[ "$TLS_MODE" == "4" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}▶ Pasted certificate (would write):${RESET}"
|
||||||
|
echo "────────────────────────────────────────"
|
||||||
|
echo "mkdir -p ${CERT_HOST_PATH}"
|
||||||
|
echo "cat > ${CERT_HOST_PATH}/fullchain.pem <<'EOF_CERT'"
|
||||||
|
echo "$PASTED_CERT"
|
||||||
|
echo "EOF_CERT"
|
||||||
|
echo "cat > ${CERT_HOST_PATH}/privkey.pem <<'EOF_KEY'"
|
||||||
|
echo "$PASTED_KEY"
|
||||||
|
echo "EOF_KEY"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
mkdir -p "$RELAY_DIR"
|
mkdir -p "$RELAY_DIR"
|
||||||
printf '%s' "$ENV_CONTENT" > "$ENV_FILE"
|
printf '%s' "$ENV_CONTENT" > "$ENV_FILE"
|
||||||
@@ -492,6 +568,15 @@ volumes:
|
|||||||
info " SANs : ${SS_SANS}"
|
info " SANs : ${SS_SANS}"
|
||||||
info " Valid for : ${SS_DAYS} days"
|
info " Valid for : ${SS_DAYS} days"
|
||||||
fi
|
fi
|
||||||
|
if [[ "$TLS_MODE" == "4" ]]; then
|
||||||
|
info " Writing pasted certificate ..."
|
||||||
|
mkdir -p "${CERT_HOST_PATH}"
|
||||||
|
printf '%s' "${PASTED_CERT}" > "${CERT_HOST_PATH}/fullchain.pem"
|
||||||
|
printf '%s' "${PASTED_KEY}" > "${CERT_HOST_PATH}/privkey.pem"
|
||||||
|
chmod 600 "${CERT_HOST_PATH}/privkey.pem"
|
||||||
|
success " Certificate : ${CERT_HOST_PATH}/fullchain.pem"
|
||||||
|
success " Private key : ${CERT_HOST_PATH}/privkey.pem"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user