226 lines
10 KiB
Bash
Executable File
226 lines
10 KiB
Bash
Executable File
#!/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" 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)
|