#!/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/-.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" 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" 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)