Files
deploy-test/common.sh
2026-04-21 12:24:04 +07:00

393 lines
16 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.

#!/usr/bin/env bash
# =============================================================================
# common.sh — 公共函数库,供各子脚本 source 引入
# 不可直接执行
# =============================================================================
# 防止重复加载
[[ -n "${_COMMON_LOADED:-}" ]] && return 0
_COMMON_LOADED=1
# ── 根目录workspace46/)──────────────────────────────────────────────────────
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# ── 运行时目录(测试服务器环境) ───────────────────────────────────────────────
LOG_DIR="$ROOT_DIR/.deploy-test/logs" # 后端服务日志
PID_DIR="$ROOT_DIR/.deploy-test/pids" # PID 文件(含日志收集进程)
BUILD_DIR="$ROOT_DIR/.deploy-test/bin" # 编译产物
DATA_DIR="$ROOT_DIR/.deploy-test/docker-data" # Docker 数据卷
DOCKER_LOG_DIR="$ROOT_DIR/.deploy-test/docker-logs" # Docker 容器日志
SCRIPT_LOG_DIR="$ROOT_DIR/.deploy-test/script-logs" # 脚本执行日志
ENV_FILE="$ROOT_DIR/.env.deploy-test"
# ── 颜色 ───────────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
step() { echo -e "\n${BOLD}${BLUE}$*${NC}"; }
header() {
echo ""
echo -e "${BOLD}${BLUE}══════════════════════════════════════════${NC}"
echo -e "${BOLD}${BLUE} $*${NC}"
echo -e "${BOLD}${BLUE}══════════════════════════════════════════${NC}"
}
# ── 初始化运行时目录 ────────────────────────────────────────────────────────────
init_dirs() {
mkdir -p "$LOG_DIR" "$PID_DIR" "$BUILD_DIR" "$DATA_DIR" \
"$DOCKER_LOG_DIR" "$SCRIPT_LOG_DIR"
}
# ──────────────────────────────────────────────────────────────────────────────
# 脚本执行日志
# 调用位置:每个脚本 init_dirs 之后
# 效果所有输出stdout+stderr同时写入 .local-dev/script-logs/<name>-<ts>.log
# ──────────────────────────────────────────────────────────────────────────────
init_script_log() {
local script_name
script_name="$(basename "${BASH_SOURCE[1]:-$0}" .sh)"
local ts; ts="$(date +%Y%m%d-%H%M%S)"
export _CURRENT_SCRIPT_LOG="$SCRIPT_LOG_DIR/${script_name}-${ts}.log"
mkdir -p "$SCRIPT_LOG_DIR"
# 写入文件头(纯文本,不含颜色码)
{
echo "========================================"
echo "Script : $script_name"
echo "Started: $(date '+%Y-%m-%d %H:%M:%S')"
echo "========================================"
} > "$_CURRENT_SCRIPT_LOG"
# exec将所有后续输出同时流向终端和日志文件
# 用 sed 去除 ANSI 颜色码,保证日志文件可读
exec > >(tee >(sed $'s/\033\\[[0-9;]*m//g' >> "$_CURRENT_SCRIPT_LOG")) 2>&1
info "脚本日志 → $_CURRENT_SCRIPT_LOG"
}
# ──────────────────────────────────────────────────────────────────────────────
# Docker 容器日志收集
# ──────────────────────────────────────────────────────────────────────────────
# 启动后台日志收集进程docker logs -f → 本地文件(按日期滚动)
start_docker_logger() {
local cname="$1"
local svc="${cname#dev-}" # dev-redis → redis
local log_dir="$DOCKER_LOG_DIR/$svc"
local logfile="$log_dir/${svc}-$(date +%Y%m%d).log"
local pid_file="$PID_DIR/docker-log-${cname}.pid"
mkdir -p "$log_dir"
# 停止已有的收集进程
if [[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
kill "$(cat "$pid_file")" 2>/dev/null || true
sleep 0.3
fi
# 写分隔符,区分每次启动会话
{
echo ""
echo "──── 容器启动 $(date '+%Y-%m-%d %H:%M:%S') ────"
} >> "$logfile"
# 后台跟踪容器日志docker logs 本身已含历史,--tail 0 只取新增)
# 首次启动时先 dump 当前快照,再 follow 新增
docker logs "$cname" >> "$logfile" 2>&1 || true
docker logs -f --tail 0 "$cname" >> "$logfile" 2>&1 &
echo $! > "$pid_file"
info " 容器日志 → $logfile"
}
# 停止容器日志收集进程
stop_docker_logger() {
local cname="$1"
local pid_file="$PID_DIR/docker-log-${cname}.pid"
if [[ -f "$pid_file" ]]; then
local pid; pid=$(cat "$pid_file")
kill "$pid" 2>/dev/null || true
rm -f "$pid_file"
fi
}
# ── 加载 .env.local ─────────────────────────────────────────────────────────────
load_env() {
if [[ ! -f "$ENV_FILE" ]]; then
error ".env.local 不存在,请先执行: ./deploy-test/01-init-env.sh"
exit 1
fi
set -a
# shellcheck source=/dev/null
source "$ENV_FILE"
set +a
}
# ── 检查必要工具 ────────────────────────────────────────────────────────────────
require_tools() {
local missing=()
for tool in "$@"; do
command -v "$tool" &>/dev/null || missing+=("$tool")
done
if [[ ${#missing[@]} -gt 0 ]]; then
error "缺少必要工具: ${missing[*]}"
for t in "${missing[@]}"; do
case "$t" in
go) echo " → 安装 Go: https://go.dev/dl/" ;;
docker) echo " → 安装 Docker Desktop: https://www.docker.com/products/docker-desktop/" ;;
esac
done
exit 1
fi
}
# ── Docker daemon 检查 ──────────────────────────────────────────────────────────
require_docker_running() {
require_tools docker
if ! docker info &>/dev/null; then
error "Docker daemon 未运行,请启动 Docker Desktop"
exit 1
fi
}
# ── 启动单个后端服务nohup 后台) ───────────────────────────────────────────────
start_svc() {
local name="$1" bin="$2" args="${3:-}" workdir="${4:-$ROOT_DIR}"
local pidfile="$PID_DIR/$name.pid" logfile="$LOG_DIR/$name.log"
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
warn "$name 已在运行 (PID=$(cat "$pidfile")),跳过"
return 0
fi
[[ ! -f "$bin" ]] && { error "$name 二进制不存在 ($bin),请先执行 04-build.sh"; return 1; }
info "启动 $name ..."
(
cd "$workdir"
# shellcheck disable=SC2086
nohup "$bin" $args > "$logfile" 2>&1 &
echo $! > "$pidfile"
)
sleep 1
if kill -0 "$(cat "$pidfile")" 2>/dev/null; then
success " $name 已启动 (PID=$(cat "$pidfile")) → $logfile"
else
error " $name 启动失败,查看日志:"
tail -20 "$logfile" 2>/dev/null || true
return 1
fi
}
# ── 停止单个后端服务 ────────────────────────────────────────────────────────────
stop_svc() {
local name="$1"
local pidfile="$PID_DIR/$name.pid"
if [[ -f "$pidfile" ]]; then
local pid; pid=$(cat "$pidfile")
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" && success "$name 已停止 (PID=$pid)"
else
warn "$name 进程 $pid 不存在(可能已退出)"
fi
rm -f "$pidfile"
else
warn "$name 没有 PID 记录(未运行)"
fi
}
# ── Docker 容器状态打印 ─────────────────────────────────────────────────────────
print_container_status() {
local label="$1" cname="$2" port="$3"
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${cname}$"; then
printf " ${GREEN}${NC} %-12s container=%-14s :%-5s\n" "$label" "$cname" "$port"
elif docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${cname}$"; then
printf " ${YELLOW}${NC} %-12s stopped=%-14s :%-5s\n" "$label" "$cname" "$port"
else
printf " ${RED}${NC} %-12s (未创建) :%-5s\n" "$label" "$port"
fi
}
# ── 后端服务状态打印 ────────────────────────────────────────────────────────────
print_svc_status() {
local name="$1" desc="$2"
local pidfile="$PID_DIR/$name.pid"
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
printf " ${GREEN}${NC} %-18s PID=%-7s %s\n" "$name" "$(cat "$pidfile")" "$desc"
else
printf " ${RED}${NC} %-18s %-11s %s\n" "$name" "未运行" "$desc"
fi
}
# ── 所有后端服务名列表 ──────────────────────────────────────────────────────────
ALL_SVCS=(openim-server chat-rpc admin-rpc chat-api admin-api meetingmsg livecloud livestream build-server)
# ── PC 反代入口环境归一化 ───────────────────────────────────────────────────────
normalize_pc_proxy_env() {
PC_PROXY_DOMAIN="${PC_PROXY_DOMAIN:-pc-jack.imharry.work}"
local default_origin="https://${PC_PROXY_DOMAIN}"
local origin="${PC_BACKEND_ORIGIN:-}"
local legacy_cms_origin="https://cms-jack.imharry.work"
local legacy_cms_origin_http="http://cms-jack.imharry.work"
origin="${origin%/}"
if [[ -z "$origin" ]]; then
PC_BACKEND_ORIGIN="$default_origin"
elif [[ "$origin" == "$legacy_cms_origin" ]] || [[ "$origin" == "$legacy_cms_origin_http" ]]; then
warn " 检测到旧 PC_BACKEND_ORIGIN=${origin},已切换为 ${default_origin}"
PC_BACKEND_ORIGIN="$default_origin"
elif [[ -n "${DEPLOY_TEST_IP:-}" ]] && [[ "$origin" =~ ^https?://${DEPLOY_TEST_IP}(:[0-9]+)?$ ]]; then
warn " 检测到旧 PC_BACKEND_ORIGIN=${origin},已切换为 ${default_origin}"
PC_BACKEND_ORIGIN="$default_origin"
elif [[ "$origin" == "http://${PC_PROXY_DOMAIN}" ]]; then
warn " 检测到 HTTP PC_BACKEND_ORIGIN=${origin},已切换为 ${default_origin}"
PC_BACKEND_ORIGIN="$default_origin"
else
PC_BACKEND_ORIGIN="$origin"
fi
export PC_PROXY_DOMAIN PC_BACKEND_ORIGIN
}
# ── PC 前端 Vite 环境(不写 pc 目录,由 07-start-frontend 在子 shell 内 export────────
# 依赖 .env.deploy-test / .env.deploy-local 中的 PC_BACKEND_ORIGIN及可选 PC_VITE_*
pc_export_vite_backend_env() {
normalize_pc_proxy_env
local o="${PC_BACKEND_ORIGIN:-}"
o="${o%/}"
[[ -z "$o" ]] && return 0
local host
host=$(printf '%s' "$o" | sed -E 's#^https?://([^/]+).*#\1#')
[[ -z "$host" ]] && return 0
export VITE_BASE_DOMAIN="${PC_VITE_BASE_DOMAIN:-$host}"
export VITE_API_URL="${PC_VITE_API_URL:-$o/api/im}"
export VITE_CHAT_URL="${PC_VITE_CHAT_URL:-$o/api/chat}"
export VITE_USER_URL="${PC_VITE_USER_URL:-$o/api/user}"
if [[ "$o" == http://* ]]; then
export VITE_WS_URL="${PC_VITE_WS_URL:-ws://${host}/msg_gateway}"
else
export VITE_WS_URL="${PC_VITE_WS_URL:-wss://${host}/msg_gateway}"
fi
}
pc_print_vite_backend_env() {
if [[ -z "${PC_BACKEND_ORIGIN:-}" ]]; then
warn " pc 未设置 PC_BACKEND_ORIGIN将使用 pc/.env 或代码默认地址WebSocket 可能连错环境"
return 0
fi
info " pc 后端 PC_BACKEND_ORIGIN=${PC_BACKEND_ORIGIN}(注入 VITE_*,不写 pc 目录)"
info " VITE_API_URL=${VITE_API_URL:-<empty>}"
info " VITE_WS_URL=${VITE_WS_URL:-<empty>}"
info " VITE_CHAT_URL=${VITE_CHAT_URL:-<empty>}"
info " VITE_USER_URL=${VITE_USER_URL:-<empty>}"
info " VITE_ADMIN_URL=${VITE_ADMIN_URL:-${PC_VITE_ADMIN_URL:-<empty>}}"
if [[ "$PC_BACKEND_ORIGIN" == http://* ]] && [[ "$PC_BACKEND_ORIGIN" != http://127.0.0.1* ]] && [[ "$PC_BACKEND_ORIGIN" != http://localhost* ]]; then
warn " PC_BACKEND_ORIGIN 当前是 HTTP: ${PC_BACKEND_ORIGIN};推荐使用 https://${VITE_BASE_DOMAIN}"
fi
}
pc_check_nginx_gateway() {
local o="${PC_BACKEND_ORIGIN:-}"
o="${o%/}"
[[ -z "$o" ]] && return 0
if ! command -v curl &>/dev/null; then
warn " 未安装 curl跳过 Nginx 网关检查;请手动访问 ${o}/nginx-health"
return 0
fi
local curl_tls=()
[[ "$o" == https://* ]] && curl_tls=(-k)
if curl "${curl_tls[@]}" -fsS --max-time 3 "${o}/nginx-health" >/dev/null 2>&1; then
success " Nginx 网关可达: ${o}/nginx-health"
else
warn " Nginx 网关不可达: ${o}/nginx-health"
warn " PC WebSocket 默认连 ${VITE_WS_URL:-wss://<host>/msg_gateway};请确认已执行 sudo ./deploy-test/00-init-tools.sh nginx本机 Nginx 放行 TCP 80外层 HTTPS 入口放行 TCP 443"
fi
pc_probe_msg_gateway "$o"
}
pc_probe_msg_gateway() {
local o="${1:-${PC_BACKEND_ORIGIN:-}}"
o="${o%/}"
[[ -z "$o" ]] && return 0
local gateway_pid
gateway_pid=$(lsof -ti :10001 2>/dev/null | head -1 || true)
if [[ -n "$gateway_pid" ]]; then
success " MsgGateway 本机端口监听正常: 127.0.0.1:10001 (PID=${gateway_pid})"
else
warn " MsgGateway 本机端口未监听: 127.0.0.1:10001请确认 openim-server 已启动)"
fi
info " MsgGateway Nginx 入口: ${o}/msg_gateway不做无 token HTTP 探测,避免在 openim-server.log 中产生 token is empty 告警)"
}
pc_check_wasm_assets() {
local origin="${1:-}"
origin="${origin%/}"
[[ -z "$origin" ]] && return 0
if ! command -v curl &>/dev/null; then
warn " 未安装 curl跳过 PC WASM 资源检查"
return 0
fi
local curl_tls=()
[[ "$origin" == https://* ]] && curl_tls=(-k)
local asset url ct
for asset in \
openIM.wasm \
sql-wasm.wasm \
wasm_exec.js \
node_modules/@openim/wasm-client-sdk/lib/worker.js \
node_modules/@openim/wasm-client-sdk/lib/worker-legacy.js; do
url="${origin}/${asset}"
ct=$(curl "${curl_tls[@]}" -fsSI --max-time 5 "$url" 2>/dev/null | awk 'BEGIN{IGNORECASE=1} /^content-type:/ {sub(/\r$/, ""); print $0; exit}' || true)
if [[ -n "$ct" ]] || curl "${curl_tls[@]}" -fsS --max-time 5 -r 0-0 "$url" >/dev/null 2>&1; then
success " PC SDK 资源可达: ${url}${ct:+ (${ct#*: })}"
else
warn " PC SDK 资源不可达: ${url}SDK login 可能卡住且不会发起 /msg_gateway WebSocket"
fi
done
info " 浏览器侧 SDK 检查: typeof Go / typeof window.initSDK / typeof window.login / typeof window.commonEventFunc"
}
pc_ensure_wasm_assets() {
local public_dir="$ROOT_DIR/pc/public"
local assets_dir="$ROOT_DIR/pc/node_modules/@openim/wasm-client-sdk/assets"
local asset src dst size
for asset in openIM.wasm sql-wasm.wasm wasm_exec.js; do
src="$assets_dir/$asset"
dst="$public_dir/$asset"
if [[ -s "$dst" ]]; then
continue
fi
if [[ ! -s "$src" ]]; then
warn " PC SDK 资源缺失且无法自动补齐: $dst(未找到 $src"
continue
fi
mkdir -p "$public_dir"
cp "$src" "$dst"
chmod 0644 "$dst" 2>/dev/null || true
size=$(wc -c < "$dst" 2>/dev/null | tr -d ' ' || true)
success " 已补齐 PC SDK 资源: $dst${size:+ (${size} bytes)}"
done
}