Files
rustfs-s3-toolkit/docker/registrar.sh
hotwa 03b8b52315
All checks were successful
Build and Push to ACR / docker (push) Successful in 3m49s
fix(registrar): 标签按行拼接避免逗号拆分;默认启用 TCP TLS;新增 TLS_MODE
- 修复 ConsulCatalog 标签因逗号分割导致的解析错误:
  「error parsing server URL ... : invalid port ':9009,'」
  现在改为按“行”累加标签,再转换为 JSON 数组,避免值中逗号(如 middlewares)被错误拆分
- TCP 路由默认启用 TLS(terminating),补齐 tls=true / tls.certresolver
  修复「HostSNI(...) but no TLS on router」
- 新增 TLS_MODE=terminating|passthrough|plaintext 三种模式
- 统一使用 loadbalancer.server.port(替代 server.url),规避尾逗号风险
- 提供严格的 to_json_array 实现,避免尾逗号 JSON 问题
- HTTP 路由补充 priority=10000;保留可选中间件(需与动态配置名称一致)

Test plan:
- 清理旧服务并重注册:
  curl -s http://100.64.0.1:8500/v1/agent/services \
    | jq 'to_entries[] | select(.value.Service=="mcp") | .key' \
    | xargs -I{} curl -s -X PUT http://100.64.0.1:8500/v1/agent/service/deregister/{}
  然后重启注册器容器
- 验证 Traefik 路由:
  curl -s http://localhost:8083/api/tcp/routers \
    | jq '.[] | select(.rule|test("ci-agent\\.jmsu\\.top")) | {name,entryPoints,tls}'
- 验证证书握手:
  openssl s_client -connect ci-agent.jmsu.top:4443 -servername ci-agent.jmsu.top -brief </dev/null

Refs:
- invalid port ":9009,"
- HostSNI(...) but no TLS on router
2025-10-03 14:52:04 +08:00

122 lines
4.9 KiB
Bash
Executable File
Raw Permalink 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