Files
EchoChat/scripts/deploy-public.sh

280 lines
10 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.

#!/usr/bin/env bash
# ============================================================
# EchoChat 公网部署脚本Phase 2e-2 Task 14
# ------------------------------------------------------------
# 职责:
# 1. 校验 deploy/.env 内关键参数ANNOUNCED_IP / 密码替换等)
# 2. 给出防火墙/安全组端口放通 checklist本地用 nc 探测)
# 3. 启动 docker compose --profile public含 media-server + coturn
#
# 不做的事:
# - 不替你改 iptables / ufw不同云/OS 命令不一,避免误操作)
# - 不替你改 hosts / DNS域名解析请预先完成
#
# 用法:
# cp deploy/.env.public.example deploy/.env
# vim deploy/.env # 按 checklist 替换所有 _REPLACE_WITH_*_
# ./scripts/deploy-public.sh
# ============================================================
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
DEPLOY_DIR="$ROOT_DIR/deploy"
ENV_FILE="$DEPLOY_DIR/.env"
COLOR_GREEN='\033[0;32m'
COLOR_YELLOW='\033[0;33m'
COLOR_RED='\033[0;31m'
COLOR_CYAN='\033[0;36m'
COLOR_RESET='\033[0m'
log_info() { printf "${COLOR_CYAN}[INFO]${COLOR_RESET} %s\n" "$*"; }
log_ok() { printf "${COLOR_GREEN}[OK]${COLOR_RESET} %s\n" "$*"; }
log_warn() { printf "${COLOR_YELLOW}[WARN]${COLOR_RESET} %s\n" "$*"; }
log_err() { printf "${COLOR_RED}[ERR]${COLOR_RESET} %s\n" "$*"; }
# 计数器
ERR_COUNT=0
WARN_COUNT=0
err() { log_err "$*"; ERR_COUNT=$((ERR_COUNT + 1)); }
warn() { log_warn "$*"; WARN_COUNT=$((WARN_COUNT + 1)); }
# ------------------------------------------------------------
# Step 1env 文件校验
# ------------------------------------------------------------
step_validate_env() {
log_info "=== Step 1/4: 校验 deploy/.env ==="
if [[ ! -f "$ENV_FILE" ]]; then
err "deploy/.env 不存在"
log_info "请先执行cp deploy/.env.public.example deploy/.env 并填写关键参数"
return 1
fi
# shellcheck disable=SC1090
set -a
source "$ENV_FILE"
set +a
# DEPLOY_MODE 必须是 public防止误把 local 的 env 复制过来)
if [[ "${DEPLOY_MODE:-}" != "public" ]]; then
warn "deploy/.env 中 DEPLOY_MODE=\"${DEPLOY_MODE:-<unset>}\",不是 public确认你要走公网部署"
fi
# MEDIASOUP_ANNOUNCED_IP 必填
if [[ -z "${MEDIASOUP_ANNOUNCED_IP:-}" ]]; then
err "MEDIASOUP_ANNOUNCED_IP 未设置,远端浏览器将无法建连 mediasoup"
log_info "请填写服务器的公网 IPMEDIASOUP_ANNOUNCED_IP=203.0.113.42"
else
log_ok "MEDIASOUP_ANNOUNCED_IP=${MEDIASOUP_ANNOUNCED_IP}"
fi
# 检查占位符没被替换
local placeholders=(
POSTGRES_PASSWORD
REDIS_PASSWORD
MINIO_ROOT_PASSWORD
JWT_SECRET
MEDIA_INTERNAL_TOKEN
TURN_USERNAME
TURN_PASSWORD
)
for var in "${placeholders[@]}"; do
local val="${!var:-}"
if [[ -z "$val" ]]; then
# REDIS_PASSWORD 允许为空(仅内网访问时)
if [[ "$var" == "REDIS_PASSWORD" ]]; then
warn "$var 为空(仅当 Redis 不对公网暴露时可接受)"
else
err "$var 为空"
fi
elif [[ "$val" == _REPLACE_WITH_* ]]; then
err "$var 仍是占位符(${val}),请替换为真实值"
fi
done
# JWT_SECRET 长度
if [[ -n "${JWT_SECRET:-}" && ${#JWT_SECRET} -lt 32 ]]; then
warn "JWT_SECRET 长度仅 ${#JWT_SECRET} 字符,推荐 >= 64 字符openssl rand -hex 32"
fi
# Task 16 Nit代码审查 2026-04-23REDIS_PASSWORD 与 redis.conf requirepass 联动校验
# 背景REDIS_PASSWORD 只影响 go-service / media-server 的连接侧,
# 若 deploy/docker/redis/redis.conf 未设置 requirepassRedis 本身会接受无密码连接(相当于裸奔)
local redis_conf="$DEPLOY_DIR/docker/redis/redis.conf"
if [[ -f "$redis_conf" ]]; then
local has_requirepass
has_requirepass=$(grep -E '^\s*requirepass\s+\S+' "$redis_conf" || true)
if [[ -n "${REDIS_PASSWORD:-}" ]]; then
if [[ -z "$has_requirepass" ]]; then
err "REDIS_PASSWORD 已设置,但 $redis_conf 未启用 requirepass —— Redis 仍允许空密码登录,公网暴露时务必同步开启"
log_info "建议:在 redis.conf 追加 requirepass \$REDIS_PASSWORD或在 docker-compose command 中传入 --requirepass"
else
log_ok "Redis requirepass 已在 redis.conf 启用,与 REDIS_PASSWORD 联动"
fi
else
if [[ -n "$has_requirepass" ]]; then
warn "redis.conf 启用了 requirepass但 deploy/.env 的 REDIS_PASSWORD 为空 —— go-service/media-server 连接会失败"
fi
fi
else
warn "未找到 $redis_conf,跳过 Redis 密码联动校验"
fi
# TURN 开关一致性
if [[ "${TURN_ENABLED:-false}" != "true" ]]; then
warn "TURN_ENABLED=${TURN_ENABLED:-false},公网部署下建议开启 TURN 作为对称 NAT fallback"
fi
if (( ERR_COUNT > 0 )); then
log_err "env 校验失败:$ERR_COUNT 个错误,$WARN_COUNT 个警告"
return 1
fi
log_ok "env 校验通过($WARN_COUNT 个警告)"
}
# ------------------------------------------------------------
# Step 2防火墙/端口占用检查(本机视角)
# ------------------------------------------------------------
# 说明:云安全组需要在云控制台放通,本脚本只能检测本机 iptables + 当前占用
step_check_ports() {
log_info "=== Step 2/4: 本机端口占用检查 ==="
local ports=(
"8085/tcp|Go backend API"
"3300/tcp|media-server (internal, 不建议暴露公网)"
"40000/udp|mediasoup RTC (端口范围起点)"
"40199/udp|mediasoup RTC (端口范围终点)"
)
if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
ports+=(
"${TURN_LISTEN_PORT:-3478}/udp|coturn STUN/TURN"
"${TURN_LISTEN_PORT:-3478}/tcp|coturn STUN/TURN"
"${TURN_TLS_PORT:-5349}/tcp|coturn TLS"
)
fi
for entry in "${ports[@]}"; do
local port="${entry%%|*}"
local desc="${entry##*|}"
local port_num="${port%/*}"
local proto="${port##*/}"
if [[ "$proto" == "tcp" ]]; then
if lsof -iTCP:"$port_num" -sTCP:LISTEN -n -P >/dev/null 2>&1; then
warn "TCP:$port_num 已被占用(${desc}compose 启动可能失败"
else
log_ok "TCP:$port_num 空闲(${desc}"
fi
else
# UDP 只做提示,不中断
log_ok "UDP:$port_num 待 docker compose 绑定(${desc}"
fi
done
printf "\n${COLOR_YELLOW}⚠️ 云安全组/iptables 放通清单${COLOR_RESET}(本脚本无法自动执行,请手动确认):\n"
printf " TCP:8085 Go backend API\n"
printf " UDP:40000-40199 mediasoup RTC 流量\n"
printf " TCP:40000-40199 mediasoup ICE-TCP fallback\n"
if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
printf " UDP:${TURN_LISTEN_PORT:-3478} coturn STUN/TURN\n"
printf " TCP:${TURN_LISTEN_PORT:-3478} coturn STUN/TURN\n"
printf " TCP:${TURN_TLS_PORT:-5349} coturn TLS\n"
printf " UDP:${TURN_MIN_PORT:-49160}-${TURN_MAX_PORT:-49200} coturn 中继流量\n"
fi
echo ""
}
# ------------------------------------------------------------
# Step 3Docker 环境自检
# ------------------------------------------------------------
step_check_docker() {
log_info "=== Step 3/4: Docker 环境自检 ==="
if ! command -v docker >/dev/null 2>&1; then
err "未检测到 docker 命令"
return 1
fi
log_ok "docker: $(docker --version)"
if ! docker info >/dev/null 2>&1; then
err "docker daemon 无法连接sudo systemctl start docker?"
return 1
fi
log_ok "docker daemon 可访问"
if ! docker-compose version >/dev/null 2>&1; then
err "未检测到 docker-compose需 Compose V2 plugin"
return 1
fi
log_ok "docker-compose: $(docker-compose version | head -n1)"
}
# ------------------------------------------------------------
# Step 4启动 public profile
# ------------------------------------------------------------
step_launch() {
log_info "=== Step 4/4: 启动 docker-compose (public profile) ==="
if (( ERR_COUNT > 0 )); then
log_err "前面校验有错误,拒绝启动(先修正后再跑脚本)"
return 1
fi
cd "$DEPLOY_DIR"
if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
log_info "TURN_ENABLED=true使用 --profile public 启动(含 coturn"
docker-compose -f docker-compose.dev.yml --profile public up -d --build
else
log_info "TURN_ENABLED=false跳过 coturn仅默认服务"
docker-compose -f docker-compose.dev.yml up -d --build
fi
log_ok "启动指令已发出,等待 healthcheck最多 180 秒)..."
local waited=0
while (( waited < 180 )); do
local health
health="$(docker inspect -f '{{.State.Health.Status}}' echochat-media-server 2>/dev/null || echo 'starting')"
if [[ "$health" == "healthy" ]]; then
log_ok "media-server healthy$waited 秒)"
break
fi
if [[ "$health" == "unhealthy" ]]; then
log_err "media-server unhealthy查看日志docker logs echochat-media-server"
return 1
fi
sleep 3
waited=$((waited + 3))
done
printf "\n${COLOR_GREEN}=============== 公网部署完成 ===============${COLOR_RESET}\n"
printf " Go 后端 API: http://${MEDIASOUP_ANNOUNCED_IP}:8085\n"
printf " media-server: http://127.0.0.1:3300 (仅内网,勿对外暴露)\n"
if [[ "${TURN_ENABLED:-false}" == "true" ]]; then
printf " coturn TURN: ${MEDIASOUP_ANNOUNCED_IP}:${TURN_LISTEN_PORT:-3478}\n"
fi
printf "\n 查看状态: docker compose -f deploy/docker-compose.dev.yml ps\n"
printf " 查看日志: docker compose -f deploy/docker-compose.dev.yml logs -f [service]\n"
printf " 停止全栈: ./scripts/stop.sh full\n"
printf "${COLOR_GREEN}============================================${COLOR_RESET}\n"
}
main() {
step_validate_env || exit 1
step_check_ports # Step 2不会 return 1只收集 warn
step_check_docker || exit 1 # Step 3
# 二次确认(至少暴露 1 个明显错误时已退出,这里只对警告放行)
if [[ "${1:-}" != "--yes" ]] && (( WARN_COUNT > 0 )); then
printf "\n${COLOR_YELLOW}上述 %d 个警告仅提示,不阻塞启动。继续部署?(y/N): ${COLOR_RESET}" "$WARN_COUNT"
read -r confirm
case "$confirm" in
y|Y|yes|YES) ;;
*) log_info "已取消部署"; exit 0 ;;
esac
fi
step_launch
}
main "$@"