Files
edge-node/registrar.sh
lingyuzeng 8d2689a15a feat(registrar): add TLS_MODE for Traefik TCP; fix tag newline; default ACME resolver
- Add TLS_MODE env to control TCP TLS behavior:
  - terminating (default): Traefik terminates TLS (tls=true + tls.certresolver)
  - passthrough: end-to-end TLS passthrough (tls.passthrough=true)
  - plaintext: plain TCP for internal/Tailscale (HostSNI(`*`) + priority=1)

- Introduce TRAEFIK_CERT_RESOLVER with default "alidns" for ACME issuance.
  (HTTP branch keeps optional tls.certresolver line commented for easy enablement.)

- Fix tag concatenation: use a real newline for NL so that to_json_array()
  parses tags line-by-line into a proper JSON array (prevents single-line tag blob).

- Improve TCP router/service tags and comments to make behavior explicit
  per mode, and keep HTTP branch consistent (middlewares + priority).

BREAKING CHANGE:
- TCP default changes from implicit plaintext to TLS-terminating via Traefik.
  If you relied on plaintext TCP previously, set `TLS_MODE=plaintext` (or
  use `TLS_MODE=passthrough` if your backend terminates TLS itself).
  Ensure Traefik has a working ACME resolver named in $TRAEFIK_CERT_RESOLVER
  (default "alidns") or override accordingly.
2025-10-03 17:30:18 +08:00

122 lines
4.9 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
set -eu
: "${SERVICE_NAME:?need SERVICE_NAME}"
: "${SERVICE_ADDR:?need SERVICE_ADDR}"
: "${SERVICE_PORT:?need SERVICE_PORT}"
: "${ROUTE_HOST:?need ROUTE_HOST}"
CONSUL="${CONSUL_HTTP_ADDR:?need CONSUL_HTTP_ADDR}"
SERVICE_PROTOCOL="${SERVICE_PROTOCOL:-http}" # http | tcp
CHECK_TYPE="${CHECK_TYPE:-tcp}" # http | tcp
CHECK_PATH="${CHECK_PATH:-/}"
CHECK_INTERVAL="${CHECK_INTERVAL:-10s}"
CHECK_TIMEOUT="${CHECK_TIMEOUT:-2s}"
DEREG_AFTER="${DEREG_AFTER:-1m}"
TRAEFIK_HTTP_ENTRYPOINT="${TRAEFIK_HTTP_ENTRYPOINT:-websecure}"
TRAEFIK_TCP_ENTRYPOINT="${TRAEFIK_TCP_ENTRYPOINT:-tcp}"
TRAEFIK_CERT_RESOLVER="${TRAEFIK_CERT_RESOLVER:-alidns}"
TLS_MODE="${TLS_MODE:-terminating}" # terminating | passthrough | plaintext
# TRAEFIK_CERT_RESOLVER="${TRAEFIK_CERT_RESOLVER:-cf}"
echo "[registrar] consul: $CONSUL, service: $SERVICE_NAME@$SERVICE_ADDR:$SERVICE_PORT"
# 等云端 Consul Server 可用
for i in $(seq 1 90); do
if wget -qO- "$CONSUL/v1/status/leader" >/dev/null 2>&1; then
break
fi
sleep 1
done
ID="${SERVICE_NAME}-${SERVICE_ADDR}-${SERVICE_PORT}"
# 组装 Traefik tags按“行”累加避免值中逗号被拆
NL='
'
TAGS="traefik.enable=true"
if [ "$SERVICE_PROTOCOL" = "http" ]; then
TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.rule=Host(\`${ROUTE_HOST}\`)"
TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_HTTP_ENTRYPOINT}"
TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.tls=true"
TAGS="$TAGS${NL}traefik.http.services.${SERVICE_NAME}.loadbalancer.server.scheme=http"
TAGS="$TAGS${NL}traefik.http.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}"
# 抢占路由:给当前 Host 的 router 设置更高优先级
TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.priority=10000"
# 可选中间件(注意:值里有逗号也安全)
TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.middlewares=gzip-all@file,sec-headers@file"
# 如需 ACME 证书解析器可再加一行(取消注释)
# TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
elif [ "$SERVICE_PROTOCOL" = "tcp" ]; then
case "$TLS_MODE" in
# ATraefik 终止 TLS推荐公网
terminating)
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`${ROUTE_HOST}\`)"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls=true"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls.certresolver=${TRAEFIK_CERT_RESOLVER}"
TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}"
;;
# A-备用:后端自己终止 TLS需要给 woodpecker-server 配 cert/key
passthrough)
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`${ROUTE_HOST}\`)"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls.passthrough=true"
TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}"
;;
# B明文 TCP仅内网/Tailscale用 * 兜底)
plaintext)
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`*\`)"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}"
TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.priority=1"
TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}"
;;
*)
echo "unsupported TLS_MODE=$TLS_MODE" >&2; exit 2;;
esac
else
echo "unsupported SERVICE_PROTOCOL=$SERVICE_PROTOCOL" >&2; exit 2
fi
# 转 JSON 数组(按“行”解析)
to_json_array() {
# 逐行 -> trim -> "..." -> [ ... ]
awk 'BEGIN{RS="\n"} NF {gsub(/^[ \t]+|[ \t]+$/,""); printf "\"%s\",\n",$0}' |
sed '1s/^/[/' | sed '$s/,\s*$/]/'
}
TAGS_JSON="$(printf "%s" "$TAGS" | to_json_array)"
# 健康检查 JSON
if [ "$CHECK_TYPE" = "http" ]; then
CHECK_JSON=$(cat <<EOF
{"Name":"http","HTTP":"http://${SERVICE_ADDR}:${SERVICE_PORT}${CHECK_PATH}","Interval":"${CHECK_INTERVAL}","Timeout":"${CHECK_TIMEOUT}","DeregisterCriticalServiceAfter":"${DEREG_AFTER}"}
EOF
)
else
CHECK_JSON=$(cat <<EOF
{"Name":"tcp","TCP":"${SERVICE_ADDR}:${SERVICE_PORT}","Interval":"${CHECK_INTERVAL}","Timeout":"${CHECK_TIMEOUT}","DeregisterCriticalServiceAfter":"${DEREG_AFTER}"}
EOF
)
fi
# 写 service 定义并注册到云端 Consul
cat > /tmp/svc.json <<EOF
{"service":{"id":"${ID}","name":"${SERVICE_NAME}","address":"${SERVICE_ADDR}","port":${SERVICE_PORT},"tags":${TAGS_JSON},"checks":[${CHECK_JSON}]}}
EOF
echo "[registrar] register ${ID} -> ${CONSUL}"
consul services register -http-addr="$CONSUL" /tmp/svc.json
term() {
echo "[registrar] deregister ${ID}"
consul services deregister -http-addr="$CONSUL" /tmp/svc.json || true
exit 0
}
trap term TERM INT
tail -f /dev/null