Compare commits
50 Commits
62aa15f171
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fadb1ba795 | ||
|
|
a34f6ad515 | ||
|
|
6eb89ad2a9 | ||
|
|
67ab5f5ba5 | ||
|
|
cdbcad650e | ||
|
|
26894b9838 | ||
|
|
d630b757d7 | ||
|
|
cd362753f8 | ||
|
|
117949d932 | ||
|
|
4606ddacbd | ||
|
|
13dfe7fe79 | ||
|
|
4bd0efc8f5 | ||
|
|
ea6019311f | ||
|
|
462614e82e | ||
|
|
c09b117c65 | ||
|
|
a9f1547c69 | ||
|
|
0171f17439 | ||
|
|
68c9613cef | ||
|
|
83d425b182 | ||
|
|
fa6b610ddf | ||
|
|
8d3b8bdc23 | ||
|
|
f409121771 | ||
|
|
77c464e757 | ||
|
|
868a22e940 | ||
|
|
d26596addf | ||
|
|
f41930113d | ||
|
|
20f8009572 | ||
|
|
c714c255bd | ||
|
|
3fd1393ffd | ||
|
|
506bc93d36 | ||
|
|
c493ccda6e | ||
|
|
4ce37703b3 | ||
|
|
3332ec541a | ||
|
|
de947acc0f | ||
|
|
5412d592f3 | ||
|
|
194a1a839e | ||
|
|
4ee373fe6d | ||
|
|
2366da8932 | ||
|
|
04cd3e2b4b | ||
|
|
fdb9679577 | ||
|
|
4128760a07 | ||
|
|
57edbc4b79 | ||
|
|
bb4c3273ca | ||
|
|
11561c9775 | ||
|
|
3719d1fcaf | ||
|
|
1e39e41c9d | ||
|
|
a5c6161b71 | ||
|
|
58d379ec98 | ||
|
|
6c30dddbbe | ||
|
|
2e0af83599 |
119
00-init-tools.sh
119
00-init-tools.sh
@@ -7,8 +7,9 @@
|
||||
# 2. 配置 GOPROXY(自动测速选最快节点)
|
||||
# 3. 安装 Node.js / npm(前端依赖)
|
||||
# 4. 安装 Docker(基础设施容器)
|
||||
# 5. 安装 Nginx 并写入 PC/OpenIM 反代(:80 → 10001/10002/10008,见 nginx/openim-pc-proxy.conf)
|
||||
# 6. 写入 /etc/profile.d/deploy-env.sh(永久生效)
|
||||
# 5. 安装 etcdctl(查看 Etcd 服务注册)
|
||||
# 6. 安装 Nginx 并写入 PC/CMS/Build-CMS/Build-Down/OpenIM 反代(本机 HTTP :80;外部 HTTPS 由 LB/CDN 终止)
|
||||
# 7. 写入 /etc/profile.d/deploy-env.sh(永久生效)
|
||||
#
|
||||
# 用法:
|
||||
# ./deploy-test/00-init-tools.sh # 安装全部(含 Nginx 反代)
|
||||
@@ -16,6 +17,7 @@
|
||||
# ./deploy-test/00-init-tools.sh node # 只安装 Node.js
|
||||
# ./deploy-test/00-init-tools.sh docker # 只安装 Docker
|
||||
# ./deploy-test/00-init-tools.sh goproxy # 只配置 GOPROXY
|
||||
# sudo ./deploy-test/00-init-tools.sh etcdctl # 只安装 etcdctl(需 root)
|
||||
# sudo ./deploy-test/00-init-tools.sh nginx # 只安装 Nginx 反代(需 root)
|
||||
#
|
||||
# 前置条件: root 或 sudo 权限,Ubuntu/Debian 系统
|
||||
@@ -31,6 +33,7 @@ init_script_log
|
||||
GO_VERSION="${GO_VERSION:-1.22.5}"
|
||||
GO_ARCH="${GO_ARCH:-amd64}" # amd64 / arm64
|
||||
NODE_VERSION="${NODE_VERSION:-20}" # Node.js LTS 大版本
|
||||
ETCDCTL_VERSION="${ETCDCTL_VERSION:-3.5.17}"
|
||||
PROFILE_FILE="/etc/profile.d/deploy-env.sh"
|
||||
|
||||
TARGET="${1:-all}"
|
||||
@@ -244,10 +247,72 @@ _install_docker() {
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 5. Nginx — PC / OpenIM 统一入口(HTTP :80,反代本机 10001/10002/10008)
|
||||
# 5. etcdctl
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
_install_etcdctl() {
|
||||
step "安装 etcdctl ${ETCDCTL_VERSION}"
|
||||
|
||||
if _has etcdctl; then
|
||||
success " etcdctl 已安装: $(etcdctl version | head -1)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
warn " etcdctl 安装需 root,请执行: sudo $0 etcdctl"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if _has apt-get; then
|
||||
apt-get update -y
|
||||
if apt-get install -y etcd-client; then
|
||||
success " etcdctl 安装完成: $(etcdctl version | head -1)"
|
||||
return 0
|
||||
fi
|
||||
warn " apt 安装 etcd-client 失败,尝试下载官方二进制"
|
||||
elif _has dnf; then
|
||||
if dnf install -y etcd; then
|
||||
success " etcdctl 安装完成: $(etcdctl version | head -1)"
|
||||
return 0
|
||||
fi
|
||||
warn " dnf 安装 etcd 失败,尝试下载官方二进制"
|
||||
elif _has yum; then
|
||||
if yum install -y etcd; then
|
||||
success " etcdctl 安装完成: $(etcdctl version | head -1)"
|
||||
return 0
|
||||
fi
|
||||
warn " yum 安装 etcd 失败,尝试下载官方二进制"
|
||||
fi
|
||||
|
||||
local arch
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) arch="amd64" ;;
|
||||
aarch64|arm64) arch="arm64" ;;
|
||||
*)
|
||||
error " 不支持的架构: $(uname -m),请手动安装 etcdctl"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
local tarball="etcd-v${ETCDCTL_VERSION}-linux-${arch}.tar.gz"
|
||||
local url="https://github.com/etcd-io/etcd/releases/download/v${ETCDCTL_VERSION}/${tarball}"
|
||||
local tmp="/tmp/${tarball}"
|
||||
local out_dir="/tmp/etcd-v${ETCDCTL_VERSION}-linux-${arch}"
|
||||
|
||||
info " 下载 ${url}"
|
||||
curl -fL --progress-bar -o "$tmp" "$url"
|
||||
rm -rf "$out_dir"
|
||||
tar -C /tmp -xzf "$tmp"
|
||||
install -m 0755 "${out_dir}/etcdctl" /usr/local/bin/etcdctl
|
||||
rm -rf "$tmp" "$out_dir"
|
||||
|
||||
success " etcdctl 安装完成: $(etcdctl version | head -1)"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# 6. Nginx — PC / CMS / Build-CMS / Build-Down / OpenIM 统一入口(本机 HTTP :80)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
_install_pc_nginx_proxy() {
|
||||
step "安装 Nginx 并配置 OpenIM/PC 反代"
|
||||
step "安装 Nginx 并配置 OpenIM/PC/CMS/Build-CMS/Build-Down 反代"
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
error " Nginx 安装需 root,请执行: sudo $0 nginx"
|
||||
@@ -258,6 +323,13 @@ _install_pc_nginx_proxy() {
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
local conf_src="${script_dir}/nginx/openim-pc-proxy.conf"
|
||||
local conf_name="openim-pc-proxy.conf"
|
||||
local proxy_domain="${PC_PROXY_DOMAIN:-pc-jack.imharry.work}"
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
proxy_domain="${PC_PROXY_DOMAIN:-$proxy_domain}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$conf_src" ]]; then
|
||||
error " 找不到配置: $conf_src"
|
||||
@@ -302,13 +374,37 @@ _install_pc_nginx_proxy() {
|
||||
systemctl enable nginx 2>/dev/null || true
|
||||
systemctl restart nginx
|
||||
|
||||
local health_failed=0
|
||||
local host
|
||||
for host in \
|
||||
"$proxy_domain" \
|
||||
"cms-jack.imharry.work" \
|
||||
"build-jack.imharry.work" \
|
||||
"down-jack.imharry.work"
|
||||
do
|
||||
if curl -fsS --max-time 3 -H "Host: ${host}" http://127.0.0.1/nginx-health >/dev/null 2>&1; then
|
||||
success " Host 路由已生效: ${host}"
|
||||
else
|
||||
error " Host 路由检查失败: ${host}"
|
||||
health_failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
success " Nginx 反代已启用(配置: $conf_src)"
|
||||
info " 请放行 TCP 80;curl -sS http://127.0.0.1/nginx-health 应返回 ok"
|
||||
info " 纯 IP 访问 :80 为 API 网关;若 nginx -t 报 duplicate default_server,请从其它站点配置中去掉 default_server"
|
||||
info " .env.deploy-test 中 PC_BACKEND_ORIGIN=http://<公网IP>(与 DEPLOY_TEST_IP 一致)"
|
||||
info " 本机 Nginx 仅监听 TCP 80;curl -sS -H 'Host: ${proxy_domain}' http://127.0.0.1/nginx-health 应返回 ok"
|
||||
info " 外部 HTTPS 可由 LB/CDN/其它网关终止后转发到本机 :80"
|
||||
info " PC 入口: https://${proxy_domain}/"
|
||||
info " CMS 入口: http://cms-jack.imharry.work/"
|
||||
info " Build CMS 入口: http://build-jack.imharry.work/"
|
||||
info " Build Down 入口: http://down-jack.imharry.work/"
|
||||
|
||||
if [[ "$health_failed" -ne 0 ]]; then
|
||||
error " Nginx 已重启,但部分 Host 路由校验失败,请检查 server_name / 默认站点 / 其它占用 :80 的配置"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# all 时非 root 则跳过(不中断 Go/Node/Docker)
|
||||
# all 时非 root 则跳过(不中断 Go/Node/Docker/etcdctl)
|
||||
_run_nginx_if_root() {
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
_install_pc_nginx_proxy
|
||||
@@ -334,6 +430,9 @@ case "$TARGET" in
|
||||
docker)
|
||||
_install_docker
|
||||
;;
|
||||
etcdctl)
|
||||
_install_etcdctl
|
||||
;;
|
||||
nginx)
|
||||
_install_pc_nginx_proxy
|
||||
;;
|
||||
@@ -342,11 +441,12 @@ case "$TARGET" in
|
||||
_config_goproxy
|
||||
_install_node
|
||||
_install_docker
|
||||
_install_etcdctl
|
||||
_run_nginx_if_root
|
||||
;;
|
||||
*)
|
||||
error "未知目标: $TARGET"
|
||||
echo "用法: $0 [all|go|goproxy|node|docker|nginx]"
|
||||
echo "用法: $0 [all|go|goproxy|node|docker|etcdctl|nginx]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -364,6 +464,7 @@ _has npm && echo " npm: $(npm --version)" || echo
|
||||
_has pnpm && echo " pnpm: $(pnpm --version)" || echo " pnpm: 未安装"
|
||||
_has yarn && echo " yarn: $(yarn --version)" || echo " yarn: 未安装"
|
||||
_has docker && echo " Docker: $(docker --version | awk '{print $3}' | tr -d ',')" || echo " Docker: 未安装"
|
||||
_has etcdctl && echo " etcdctl: $(etcdctl version | head -1 | sed 's/^etcdctl version: //')" || echo " etcdctl: 未安装"
|
||||
_has nginx && echo " Nginx: $(nginx -v 2>&1 | sed 's/^nginx version: //')" || echo " Nginx: 未安装"
|
||||
echo ""
|
||||
echo -e "${BOLD}GOPROXY 配置:${NC}"
|
||||
|
||||
@@ -104,12 +104,13 @@ CF_CUSTOMER_CODE=huonxouoa55ent9z
|
||||
TENCENT_SDK_APP_ID=20033091
|
||||
TENCENT_SDK_SECRET_KEY=cceba44084aaa04f8c48a1858ffd5385875c3a5ec006d34278d9d3714b40e3b0
|
||||
|
||||
# ── PC 客户端(Vite dev)对接的后端公网地址(可选)───────────────────────────
|
||||
# 仅 IP:在服务器上先部署 Nginx 反代(sudo ./deploy-test/00-init-tools.sh nginx)
|
||||
# 填写与下方 DEPLOY_TEST_IP 一致的 http:// 根地址,无末尾斜杠
|
||||
# ./deploy-test/07-start-frontend.sh 启动 pc 时会 export VITE_*,覆盖 pc/.env,无需改 pc 目录
|
||||
# ── PC 静态站点对接的后端公网地址(可选)──────────────────────────────────────
|
||||
# 通过域名访问 Nginx 反代(sudo ./deploy-test/00-init-tools.sh nginx)。
|
||||
# 填写 HTTPS 根地址,无末尾斜杠;Nginx 会将 / 指向 /app/pc/dist,并代理 API/WS。
|
||||
# ./deploy-test/08-build-static-frontend.sh 构建 pc 时会 export VITE_*,覆盖 pc/.env,无需改 pc 目录
|
||||
# 若某路径与网关不一致,可单独覆盖:PC_VITE_API_URL / PC_VITE_WS_URL / PC_VITE_CHAT_URL / PC_VITE_USER_URL
|
||||
PC_BACKEND_ORIGIN=http://54.116.29.247
|
||||
PC_PROXY_DOMAIN=pc-jack.imharry.work
|
||||
PC_BACKEND_ORIGIN=https://pc-jack.imharry.work
|
||||
EOF
|
||||
|
||||
success ".env.deploy-test 已写入: $ENV_FILE"
|
||||
|
||||
@@ -74,7 +74,7 @@ _start_all() {
|
||||
step "第 1 组: openim-server(核心 IM 服务)"
|
||||
_start_one openim-server
|
||||
|
||||
info "等待 openim-server 将 RPC 注册到 Etcd... (8s)"
|
||||
info "等待 openim-server 初始化 standalone 内部 RPC... (8s)"
|
||||
sleep 8
|
||||
|
||||
step "第 2 组: chat RPC 服务"
|
||||
|
||||
@@ -74,7 +74,9 @@ case "$TARGET" in
|
||||
success "所有前端依赖安装完成!"
|
||||
echo ""
|
||||
echo -e "${BOLD}下一步:${NC}"
|
||||
echo -e " 启动前端开发服务器:"
|
||||
echo -e " 构建静态前端(pc/cms/build-cms/build-down):"
|
||||
echo -e " ${CYAN}./deploy-test/08-build-static-frontend.sh${NC}"
|
||||
echo -e " 启动开发态前端(meetingh5/h5):"
|
||||
echo -e " ${CYAN}./deploy-test/07-start-frontend.sh${NC}"
|
||||
;;
|
||||
pc|meetingh5|h5|cms|build-cms|build-down)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 07-start-frontend.sh — 启动前端开发服务器
|
||||
# 07-start-frontend.sh — 启动前端开发服务器(开发态)
|
||||
#
|
||||
# 用法:
|
||||
# ./07-start-frontend.sh # 启动全部前端项目
|
||||
# ./07-start-frontend.sh <project> # 只启动指定项目
|
||||
#
|
||||
# 项目与端口:
|
||||
# pc → 默认 yarn dev:web :5173(无 Electron);本机要 Electron 时设 PC_ELECTRON=1 使用 yarn dev
|
||||
# 后端地址:在 .env.deploy-test 设 PC_BACKEND_ORIGIN,启动时注入 VITE_*(不改 pc 目录)
|
||||
# meetingh5 → React + Vite :5188
|
||||
# meetingh5 → Vue + Vite :5188
|
||||
# h5 → Vue + Vite :3003
|
||||
# cms → UMI Max :8001
|
||||
# build-cms → UMI Max :8002
|
||||
# build-down → UMI v3 :8003
|
||||
#
|
||||
#
|
||||
# 注意:
|
||||
# pc / cms / build-cms / build-down 已改为静态构建 + Nginx 域名访问:
|
||||
# ./deploy-test/08-build-static-frontend.sh
|
||||
#
|
||||
# 日志文件: .local-dev/logs/fe-<project>.log
|
||||
# PID 文件: .local-dev/pids/fe-<project>.pid
|
||||
@@ -21,6 +21,7 @@
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
init_dirs
|
||||
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
||||
|
||||
# 供文末打印访问地址(set -u 下须已定义;与 01-init-env 中 DEPLOY_TEST_IP 一致)
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
@@ -30,6 +31,7 @@ if [[ -f "$ENV_FILE" ]]; then
|
||||
set +a
|
||||
fi
|
||||
DEPLOY_TEST_IP="${DEPLOY_TEST_IP:-127.0.0.1}"
|
||||
normalize_pc_proxy_env
|
||||
|
||||
init_script_log # ← 脚本执行日志
|
||||
|
||||
@@ -38,56 +40,81 @@ header "启动前端开发服务器"
|
||||
# ── 前端服务配置 ──────────────────────────────────────────────────────────────
|
||||
# name → (目录, 包管理器, 启动命令, 环境变量, 端口描述)
|
||||
declare -A FE_DIR=(
|
||||
[pc]="pc"
|
||||
[meetingh5]="meetingh5"
|
||||
[h5]="h5"
|
||||
[cms]="cms"
|
||||
[build-cms]="build-cms"
|
||||
[build-down]="build-down"
|
||||
)
|
||||
|
||||
declare -A FE_PM=(
|
||||
[pc]="yarn"
|
||||
[meetingh5]="npm"
|
||||
[h5]="npm"
|
||||
[cms]="pnpm"
|
||||
[build-cms]="pnpm"
|
||||
[build-down]="npm"
|
||||
)
|
||||
|
||||
declare -A FE_CMD=(
|
||||
[pc]="yarn dev:web"
|
||||
[meetingh5]="npm run dev"
|
||||
[h5]="npm run dev"
|
||||
[cms]="pnpm run dev"
|
||||
[build-cms]="pnpm run dev"
|
||||
[build-down]="npm run dev"
|
||||
)
|
||||
|
||||
# cms/build-cms/build-down 不配置端口时默认都是 8000,需手动指定
|
||||
declare -A FE_ENV=(
|
||||
[pc]=""
|
||||
[meetingh5]=""
|
||||
[h5]=""
|
||||
[cms]="PORT=8001"
|
||||
[build-cms]="PORT=8002"
|
||||
[build-down]="PORT=8003"
|
||||
)
|
||||
|
||||
declare -A FE_PORT=(
|
||||
[pc]=":5173 (yarn dev:web)"
|
||||
[meetingh5]=":5188"
|
||||
[h5]=":3003"
|
||||
[cms]=":8001"
|
||||
[build-cms]=":8002"
|
||||
[build-down]=":8003"
|
||||
)
|
||||
|
||||
# 本机需 Electron 桌面客户端时:PC_ELECTRON=1 → yarn dev(需 GUI / libatk 等)
|
||||
if [[ "${PC_ELECTRON:-}" == "1" ]] || [[ "${PC_ELECTRON:-}" == "true" ]]; then
|
||||
FE_CMD[pc]="yarn dev"
|
||||
FE_PORT[pc]=":5173 (yarn dev + Electron)"
|
||||
declare -A FE_PORT_NUM=(
|
||||
[meetingh5]="5188"
|
||||
[h5]="3003"
|
||||
)
|
||||
|
||||
_check_fe_port_free() {
|
||||
local name="$1"
|
||||
local port="${FE_PORT_NUM[$name]}"
|
||||
local pids
|
||||
|
||||
info "检查 ${BOLD}$name${NC} 端口 $port ..."
|
||||
pids="$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
|
||||
if [[ -n "$pids" ]]; then
|
||||
error "$name 启动失败: 端口 $port 已被占用"
|
||||
echo "占用进程:"
|
||||
lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null || true
|
||||
echo ""
|
||||
echo "请先停止占用进程,例如:"
|
||||
echo " ./deploy-test/stop-frontend.sh $name"
|
||||
echo " lsof -ti :$port | xargs -r kill"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_wait_fe_port_listen() {
|
||||
local name="$1"
|
||||
local port="${FE_PORT_NUM[$name]}"
|
||||
local i
|
||||
|
||||
for i in {1..10}; do
|
||||
if lsof -tiTCP:"$port" -sTCP:LISTEN &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
error "$name 启动后未监听预期端口 $port"
|
||||
return 1
|
||||
}
|
||||
|
||||
_kill_tree() {
|
||||
local pid="$1" signal="${2:-TERM}"
|
||||
local children child
|
||||
|
||||
children=$(pgrep -P "$pid" 2>/dev/null || true)
|
||||
for child in $children; do
|
||||
_kill_tree "$child" "$signal"
|
||||
done
|
||||
|
||||
kill "-$signal" "$pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ── 启动单个前端服务 ──────────────────────────────────────────────────────────
|
||||
_start_fe() {
|
||||
@@ -98,36 +125,35 @@ _start_fe() {
|
||||
local env_prefix="${FE_ENV[$name]}"
|
||||
local pidfile="$PID_DIR/fe-${name}.pid"
|
||||
local logfile="$LOG_DIR/fe-${name}.log"
|
||||
local port="${FE_PORT_NUM[$name]}"
|
||||
|
||||
_check_fe_port_free "$name" || return 1
|
||||
|
||||
# 已在运行
|
||||
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
||||
warn "$name 已在运行 (PID=$(cat "$pidfile")),跳过"
|
||||
warn "$name 已在运行 (端口=$port, PID=$(cat "$pidfile")),跳过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 目录检查
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
warn "$name 目录不存在 ($dir),跳过"
|
||||
warn "$name 目录不存在 (端口=$port, 目录=$dir),跳过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 依赖检查
|
||||
if [[ ! -d "$dir/node_modules" ]]; then
|
||||
warn "$name node_modules 不存在,请先执行: ./deploy-test/06-install-frontend.sh $name"
|
||||
warn "$name node_modules 不存在 (端口=$port),请先执行: ./deploy-test/06-install-frontend.sh $name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 包管理器检查
|
||||
if ! command -v "$pm" &>/dev/null; then
|
||||
error "$name 需要 $pm,未安装"
|
||||
error "$name 需要 $pm,未安装 (端口=$port)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "启动 ${BOLD}$name${NC} ..."
|
||||
if [[ "$name" == "pc" ]] && [[ -n "${PC_BACKEND_ORIGIN:-}" ]]; then
|
||||
info " pc 后端 PC_BACKEND_ORIGIN=${PC_BACKEND_ORIGIN}(注入 VITE_*,不写 pc 目录)"
|
||||
fi
|
||||
|
||||
info "启动 ${BOLD}$name${NC} (端口=$port) ..."
|
||||
# 写日志分隔符
|
||||
{
|
||||
echo ""
|
||||
@@ -137,9 +163,6 @@ _start_fe() {
|
||||
# 后台启动(带环境变量前缀)
|
||||
(
|
||||
cd "$dir"
|
||||
if [[ "$name" == "pc" ]]; then
|
||||
pc_export_vite_backend_env
|
||||
fi
|
||||
if [[ -n "$env_prefix" ]]; then
|
||||
# shellcheck disable=SC2086
|
||||
nohup env $env_prefix $cmd >> "$logfile" 2>&1 &
|
||||
@@ -152,9 +175,21 @@ _start_fe() {
|
||||
|
||||
sleep 2
|
||||
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
||||
success " ✓ $name (PID=$(cat "$pidfile")) ${FE_PORT[$name]} → $logfile"
|
||||
if ! _wait_fe_port_listen "$name"; then
|
||||
local started_pid
|
||||
started_pid="$(cat "$pidfile")"
|
||||
warn "$name 未监听预期端口 $port,清理本次启动进程 (PID=$started_pid)"
|
||||
_kill_tree "$started_pid" TERM
|
||||
sleep 1
|
||||
if kill -0 "$started_pid" 2>/dev/null; then
|
||||
_kill_tree "$started_pid" KILL
|
||||
fi
|
||||
rm -f "$pidfile"
|
||||
return 1
|
||||
fi
|
||||
success " ✓ $name (端口=$port, PID=$(cat "$pidfile")) ${FE_PORT[$name]} → $logfile"
|
||||
else
|
||||
error " ✗ $name 启动失败,查看日志:"
|
||||
error " ✗ $name 启动失败 (端口=$port),查看日志:"
|
||||
tail -20 "$logfile" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
@@ -162,7 +197,8 @@ _start_fe() {
|
||||
|
||||
# ── 入口 ─────────────────────────────────────────────────────────────────────
|
||||
TARGET="${1:-all}"
|
||||
FE_PROJECTS=(pc meetingh5 h5 cms build-cms build-down)
|
||||
FE_PROJECTS=(meetingh5 h5)
|
||||
STATIC_FRONTENDS=(pc cms build-cms build-down)
|
||||
|
||||
_all_valid() {
|
||||
for p in "${FE_PROJECTS[@]}"; do
|
||||
@@ -175,7 +211,9 @@ if [[ "$TARGET" == "all" ]]; then
|
||||
step "启动全部前端开发服务器"
|
||||
FAILED=()
|
||||
for proj in "${FE_PROJECTS[@]}"; do
|
||||
_start_fe "$proj" || FAILED+=("$proj")
|
||||
if ! bash "$SCRIPT_PATH" "$proj"; then
|
||||
FAILED+=("$proj")
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
@@ -191,10 +229,7 @@ if [[ "$TARGET" == "all" ]]; then
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}访问地址:${NC}"
|
||||
echo " PC: http://${DEPLOY_TEST_IP}:5173 (默认 yarn dev:web;Electron 用 PC_ELECTRON=1 或 pc 目录内 yarn dev)"
|
||||
echo " H5: http://${DEPLOY_TEST_IP}:3003"
|
||||
echo " CMS: http://${DEPLOY_TEST_IP}:8001"
|
||||
echo " Build CMS: http://${DEPLOY_TEST_IP}:8002"
|
||||
echo " Build Download: http://${DEPLOY_TEST_IP}:8003"
|
||||
echo ""
|
||||
echo -e "${BOLD}MeetingH5 访问地址(后端 URL 由 .env.local 默认设置,也可通过 URL 参数覆盖):${NC}"
|
||||
@@ -209,9 +244,18 @@ if [[ "$TARGET" == "all" ]]; then
|
||||
echo " 可能原因: node_modules 未安装,执行: ./deploy-test/06-install-frontend.sh"
|
||||
fi
|
||||
else
|
||||
for static_fe in "${STATIC_FRONTENDS[@]}"; do
|
||||
if [[ "$TARGET" == "$static_fe" ]]; then
|
||||
error "$TARGET 已改为静态构建,不再通过 07-start-frontend.sh 启动"
|
||||
echo "请执行: ./deploy-test/08-build-static-frontend.sh $TARGET"
|
||||
echo "然后执行: sudo ./deploy-test/00-init-tools.sh nginx"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if ! _all_valid "$TARGET"; then
|
||||
error "未知项目: $TARGET"
|
||||
echo "可用: ${FE_PROJECTS[*]}"
|
||||
echo "开发态前端可用: ${FE_PROJECTS[*]}"
|
||||
echo "静态前端请用: ./deploy-test/08-build-static-frontend.sh [pc|cms|build-cms|build-down]"
|
||||
exit 1
|
||||
fi
|
||||
step "启动前端项目: $TARGET"
|
||||
|
||||
133
08-build-static-frontend.sh
Executable file
133
08-build-static-frontend.sh
Executable file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 08-build-static-frontend.sh — 构建静态前端产物(dist)
|
||||
#
|
||||
# 用法:
|
||||
# ./08-build-static-frontend.sh # 构建全部静态前端
|
||||
# ./08-build-static-frontend.sh <project> # 构建指定项目
|
||||
#
|
||||
# 可用项目: pc, cms, build-cms, build-down
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
init_dirs
|
||||
load_env
|
||||
init_script_log
|
||||
|
||||
header "构建静态前端"
|
||||
|
||||
STATIC_FRONTENDS=(pc cms build-cms build-down)
|
||||
|
||||
declare -A FE_PM=(
|
||||
[pc]="yarn"
|
||||
[cms]="pnpm"
|
||||
[build-cms]="pnpm"
|
||||
[build-down]="npm"
|
||||
)
|
||||
|
||||
declare -A FE_BUILD_CMD=(
|
||||
[pc]="yarn build"
|
||||
[cms]="pnpm run build"
|
||||
[build-cms]="pnpm run build"
|
||||
[build-down]="npm run build"
|
||||
)
|
||||
|
||||
declare -A FE_DIST_FILE=(
|
||||
[pc]="$ROOT_DIR/pc/dist/index.html"
|
||||
[cms]="$ROOT_DIR/cms/dist/index.html"
|
||||
[build-cms]="$ROOT_DIR/build-cms/dist/index.html"
|
||||
[build-down]="$ROOT_DIR/build-down/dist/index.html"
|
||||
)
|
||||
|
||||
declare -A FE_URL=(
|
||||
[pc]="https://${PC_PROXY_DOMAIN:-pc-jack.imharry.work}/"
|
||||
[cms]="http://cms-jack.imharry.work/"
|
||||
[build-cms]="http://build-jack.imharry.work/"
|
||||
[build-down]="http://down-jack.imharry.work/"
|
||||
)
|
||||
|
||||
_build_one() {
|
||||
local name="$1"
|
||||
local dir="$ROOT_DIR/$name"
|
||||
local pm="${FE_PM[$name]}"
|
||||
local cmd="${FE_BUILD_CMD[$name]}"
|
||||
local dist_file="${FE_DIST_FILE[$name]}"
|
||||
local logfile="$LOG_DIR/build-${name}.log"
|
||||
|
||||
if [[ ! -d "$dir" ]]; then
|
||||
error "$name 目录不存在: $dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "$dir/node_modules" ]]; then
|
||||
error "$name node_modules 不存在,请先执行: ./deploy-test/06-install-frontend.sh $name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! command -v "$pm" >/dev/null 2>&1; then
|
||||
error "$name 需要 $pm,但当前未安装"
|
||||
return 1
|
||||
fi
|
||||
|
||||
info "构建 ${BOLD}$name${NC} ..."
|
||||
: > "$logfile"
|
||||
|
||||
if [[ "$name" == "pc" ]]; then
|
||||
pc_ensure_wasm_assets
|
||||
pc_export_vite_backend_env
|
||||
pc_print_vite_backend_env
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$dir"
|
||||
# shellcheck disable=SC2086
|
||||
$cmd 2>&1
|
||||
) | tee "$logfile"
|
||||
|
||||
if [[ ! -f "$dist_file" ]]; then
|
||||
error "$name 构建完成,但未找到产物: $dist_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
success "$name 构建完成: $dist_file"
|
||||
info "访问地址: ${FE_URL[$name]}"
|
||||
}
|
||||
|
||||
TARGET="${1:-all}"
|
||||
|
||||
if [[ "$TARGET" == "all" ]]; then
|
||||
FAILED=()
|
||||
for project in "${STATIC_FRONTENDS[@]}"; do
|
||||
if ! _build_one "$project"; then
|
||||
FAILED+=("$project")
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
error "以下项目构建失败: ${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
valid=false
|
||||
for project in "${STATIC_FRONTENDS[@]}"; do
|
||||
if [[ "$TARGET" == "$project" ]]; then
|
||||
valid=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $valid; then
|
||||
error "未知项目: $TARGET"
|
||||
echo "可用: ${STATIC_FRONTENDS[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_build_one "$TARGET"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
success "静态前端构建完成"
|
||||
echo "下一步:"
|
||||
echo " sudo ./deploy-test/00-init-tools.sh nginx"
|
||||
echo " ./deploy-test/09-verify-static-frontends.sh"
|
||||
104
09-verify-static-frontends.sh
Normal file
104
09-verify-static-frontends.sh
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# 09-verify-static-frontends.sh — 验证静态前端与 Nginx 域名入口
|
||||
#
|
||||
# 用法:
|
||||
# ./09-verify-static-frontends.sh
|
||||
#
|
||||
# 检查项:
|
||||
# - /app/pc/dist / /app/cms/dist / /app/build-cms/dist / /app/build-down/dist 是否存在
|
||||
# - Nginx Host 路由是否可达
|
||||
# - 站点首页是否能返回 HTML
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
init_dirs
|
||||
init_script_log
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
normalize_pc_proxy_env
|
||||
|
||||
require_tools curl
|
||||
|
||||
header "验证静态前端"
|
||||
|
||||
check_dist() {
|
||||
local label="$1" path="$2"
|
||||
if [[ -f "$path" ]]; then
|
||||
success "$label dist 存在: $path"
|
||||
else
|
||||
error "$label dist 不存在: $path"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_site() {
|
||||
local label="$1" host="$2" expected_path="$3" expected_title="${4:-}"
|
||||
local body_file
|
||||
local actual_title
|
||||
body_file="$(mktemp)"
|
||||
|
||||
if curl -fsS --max-time 5 -H "Host: ${host}" http://127.0.0.1/nginx-health >/dev/null 2>&1; then
|
||||
success "$label nginx-health 可达: http://${host}/nginx-health"
|
||||
else
|
||||
error "$label nginx-health 不可达: http://${host}/nginx-health"
|
||||
rm -f "$body_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if curl -fsS --max-time 10 -H "Host: ${host}" http://127.0.0.1/ > "$body_file"; then
|
||||
if grep -qi "<html" "$body_file"; then
|
||||
success "$label 首页可达: http://${host}/"
|
||||
actual_title=$(grep -oiE '<title>[^<]*</title>' "$body_file" | head -1 | sed -E 's#</?title>##g' || true)
|
||||
[[ -n "$actual_title" ]] && info " ${label} title: ${actual_title}"
|
||||
info " ${label} dist: ${expected_path}"
|
||||
if [[ -n "$expected_title" ]]; then
|
||||
if [[ "$actual_title" == "$expected_title" ]]; then
|
||||
success "$label title 符合预期"
|
||||
else
|
||||
error "$label title 不符合预期: 期望=${expected_title} 实际=${actual_title:-<empty>}"
|
||||
rm -f "$body_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
error "$label 首页返回成功但不是 HTML: http://${host}/"
|
||||
rm -f "$body_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
error "$label 首页不可达: http://${host}/"
|
||||
rm -f "$body_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$body_file"
|
||||
}
|
||||
|
||||
FAILED=()
|
||||
|
||||
check_dist "PC" "$ROOT_DIR/pc/dist/index.html" || FAILED+=("pc-dist")
|
||||
check_dist "CMS" "$ROOT_DIR/cms/dist/index.html" || FAILED+=("cms-dist")
|
||||
check_dist "Build CMS" "$ROOT_DIR/build-cms/dist/index.html" || FAILED+=("build-cms-dist")
|
||||
check_dist "Build Down" "$ROOT_DIR/build-down/dist/index.html" || FAILED+=("build-down-dist")
|
||||
|
||||
check_site "PC" "${PC_PROXY_DOMAIN}" "$ROOT_DIR/pc/dist/index.html" || FAILED+=("pc-nginx")
|
||||
check_site "CMS" "cms-jack.imharry.work" "$ROOT_DIR/cms/dist/index.html" "欢迎使用MChat" || FAILED+=("cms-nginx")
|
||||
check_site "Build CMS" "build-jack.imharry.work" "$ROOT_DIR/build-cms/dist/index.html" "打包管理系统" || FAILED+=("build-cms-nginx")
|
||||
check_site "Build Down" "down-jack.imharry.work" "$ROOT_DIR/build-down/dist/index.html" || FAILED+=("build-down-nginx")
|
||||
|
||||
echo ""
|
||||
pc_probe_msg_gateway "${PC_BACKEND_ORIGIN}"
|
||||
|
||||
echo ""
|
||||
if [[ ${#FAILED[@]} -eq 0 ]]; then
|
||||
success "静态前端验证通过"
|
||||
else
|
||||
error "以下检查失败: ${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
112
README.md
112
README.md
@@ -57,7 +57,8 @@ ssh -T git@github.com
|
||||
|
||||
```
|
||||
deploy-test/
|
||||
├── 00-init-tools.sh # 步骤0(可选):Linux 服务器安装 Go / Node / Docker、GOPROXY、GitHub HTTPS 重写
|
||||
├── dt.sh # 总控快捷入口:pull/build/start/stop/restart/status
|
||||
├── 00-init-tools.sh # 步骤0(可选):Linux 服务器安装 Go / Node / Docker / etcdctl、GOPROXY、GitHub HTTPS 重写
|
||||
├── common.sh # 公共函数库(路径、日志函数)
|
||||
├── 01-init-env.sh # 步骤1:写入 .env.deploy-test(已存在则覆盖,旧文件带时间戳备份)
|
||||
├── 02-patch-config.sh # 步骤2:将 .env.deploy-test 写入各服务 YAML
|
||||
@@ -91,21 +92,43 @@ deploy-test/
|
||||
|
||||
---
|
||||
|
||||
## 快捷入口:`dt.sh`
|
||||
|
||||
`dt.sh` 是 `deploy-test` 的总控脚本,放在 `deploy-test/` 目录内,但会扫描 **deploy-test 上级目录** 下的所有 Git 工程。
|
||||
|
||||
```bash
|
||||
./deploy-test/dt.sh pull # 拉取所有工程最新代码(fetch + pull --ff-only)
|
||||
./deploy-test/dt.sh build # 编译后端服务
|
||||
./deploy-test/dt.sh start # 启动 deploy-test 后端服务
|
||||
./deploy-test/dt.sh stop # 停止 deploy-test 后端服务
|
||||
./deploy-test/dt.sh restart # 重启 deploy-test 后端服务
|
||||
./deploy-test/dt.sh status # 查看服务状态
|
||||
./deploy-test/dt.sh fe-build-static pc # 构建 pc 静态前端
|
||||
./deploy-test/dt.sh fe-verify-static # 验证静态前端域名入口
|
||||
./deploy-test/dt.sh fe-stop pc # 停止 pc 前端
|
||||
./deploy-test/dt.sh up # pull + build + restart
|
||||
./deploy-test/dt.sh deploy # pull + build + restart + status
|
||||
```
|
||||
|
||||
`pull` 是安全更新:只执行 `git fetch --all --prune` 和 `git pull --ff-only`。如果某工程有本地未提交改动,脚本只会 `fetch` 并提示,不会 `reset` 或覆盖本地修改。
|
||||
|
||||
---
|
||||
|
||||
## 步骤 0:`00-init-tools.sh`(裸机 / 新服务器)
|
||||
|
||||
在**尚未安装 Go、Node、Docker** 的 Linux 测试服务器上,先执行本脚本再跑 `01-init-env.sh` 及后续步骤。`setup.sh` **不会**自动调用它,需手动执行。
|
||||
在**尚未安装 Go、Node、Docker、etcdctl** 的 Linux 测试服务器上,先执行本脚本再跑 `01-init-env.sh` 及后续步骤。`setup.sh` **不会**自动调用它,需手动执行。
|
||||
|
||||
**GitHub 访问**:请先完成上文 **「GitHub:优先配置 SSH 密钥」**;本脚本的 **HTTPS 重写** 是在**不能配 SSH** 时的备选,不是首选。
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **作用** | 安装 Go(默认 1.22.5,可用 `GO_VERSION` 覆盖)、配置 GOPROXY(测速选节点)、安装 Node.js LTS、全局安装 pnpm/yarn、安装 Docker;**备选**:将 GitHub SSH 克隆地址重写为 HTTPS(当环境无法使用 SSH 时,减轻 `pc` 等项目 `yarn install` 失败) |
|
||||
| **环境变量** | `GO_VERSION`(如 `1.22.5`)、`GO_ARCH`(`amd64` / `arm64`)、`NODE_VERSION`(Node 大版本,默认 `20`) |
|
||||
| **作用** | 安装 Go(默认 1.22.5,可用 `GO_VERSION` 覆盖)、配置 GOPROXY(测速选节点)、安装 Node.js LTS、全局安装 pnpm/yarn、安装 Docker、安装 `etcdctl`;**备选**:将 GitHub SSH 克隆地址重写为 HTTPS(当环境无法使用 SSH 时,减轻 `pc` 等项目 `yarn install` 失败) |
|
||||
| **环境变量** | `GO_VERSION`(如 `1.22.5`)、`GO_ARCH`(`amd64` / `arm64`)、`NODE_VERSION`(Node 大版本,默认 `20`)、`ETCDCTL_VERSION`(默认 `3.5.17`) |
|
||||
| **权限** | 需要 **root** 或 **sudo**(写入 `/usr/local/go`、`/etc/profile.d/deploy-env.sh` 等) |
|
||||
| **系统** | 面向 Ubuntu/Debian(`apt-get`);脚本内注释说明前置条件 |
|
||||
|
||||
```bash
|
||||
# 安装全部(Go + GOPROXY + Node + Docker)
|
||||
# 安装全部(Go + GOPROXY + Node + Docker + etcdctl)
|
||||
sudo ./deploy-test/00-init-tools.sh
|
||||
|
||||
# 或只执行其中一项
|
||||
@@ -113,6 +136,7 @@ sudo ./deploy-test/00-init-tools.sh go # Go + GOPROXY
|
||||
sudo ./deploy-test/00-init-tools.sh goproxy # 仅重配 GOPROXY
|
||||
sudo ./deploy-test/00-init-tools.sh node # 仅 Node / npm / pnpm / yarn
|
||||
sudo ./deploy-test/00-init-tools.sh docker # 仅 Docker
|
||||
sudo ./deploy-test/00-init-tools.sh etcdctl # 仅安装 etcdctl
|
||||
```
|
||||
|
||||
执行结束后脚本会提示:新开终端需 `source /etc/profile.d/deploy-env.sh` 或重新登录 SSH,再执行 `./deploy-test/01-init-env.sh`。
|
||||
@@ -134,7 +158,7 @@ sudo ./deploy-test/00-init-tools.sh docker # 仅 Docker
|
||||
### 分步执行
|
||||
|
||||
```bash
|
||||
# 0. (可选)裸机安装 Go / Node / Docker,见上文「步骤 0」
|
||||
# 0. (可选)裸机安装 Go / Node / Docker / etcdctl,见上文「步骤 0」
|
||||
# sudo ./deploy-test/00-init-tools.sh
|
||||
|
||||
# 1. 写入配置模板(若 .env.deploy-test 已存在会先备份为 .bak.<时间戳> 再覆盖)
|
||||
@@ -158,13 +182,18 @@ vim .env.deploy-test
|
||||
# 7. 安装前端依赖(可选)
|
||||
./deploy-test/06-install-frontend.sh
|
||||
|
||||
# 8. 启动前端开发服务器(可选)
|
||||
# 8. 构建静态前端(pc/cms/build-cms/build-down,可选)
|
||||
./deploy-test/08-build-static-frontend.sh
|
||||
|
||||
# 9. 启动开发态前端(meetingh5/h5,可选)
|
||||
./deploy-test/07-start-frontend.sh
|
||||
```
|
||||
|
||||
> **pc(Electron)依赖安装**:**优先**按上文配置 **GitHub SSH 密钥**;裸机还需 **`00-init-tools.sh`**(见「步骤 0」)。若仍无法用 SSH,再依赖脚本内的 GitHub **HTTPS 重写**。详见「pc 前端 yarn install 失败」。`pc` 建议仅用 **Yarn**;可删除 `package-lock.json` 消除混用锁文件警告。
|
||||
|
||||
> **`07-start-frontend.sh`**:启动前会读取 `.env.deploy-test`(若存在),文末「访问地址」等处使用 `DEPLOY_TEST_IP`;未设置时默认为 `127.0.0.1`,避免脚本在 `set -u` 下因未定义变量退出。
|
||||
> **`08-build-static-frontend.sh`**:构建前会读取 `.env.deploy-test`(若存在),其中 `pc` 会注入 `VITE_*` 并确保 wasm 资源存在。
|
||||
>
|
||||
> **`07-start-frontend.sh`**:现在只用于 `meetingh5`、`h5` 这类开发态前端。
|
||||
|
||||
---
|
||||
|
||||
@@ -259,14 +288,23 @@ TENCENT_SDK_SECRET_KEY=xxx
|
||||
|
||||
### 前端开发服务器(可选)
|
||||
|
||||
**开发态前端**
|
||||
|
||||
| 项目 | 端口 | 说明 |
|
||||
|------|------|------|
|
||||
| pc (Electron) | :7777 | Electron 桌面客户端 |
|
||||
| meetingh5 | :5188 | 直播观看 H5(弹幕+视频) |
|
||||
| h5 | :3003 | 移动端 H5 |
|
||||
| cms | :8001 | 后台管理 |
|
||||
| build-cms | :8002 | 构建管理后台 |
|
||||
| build-down | :8003 | 下载页 |
|
||||
|
||||
**静态站点**
|
||||
|
||||
| 项目 | 访问地址 | 说明 |
|
||||
|------|----------|------|
|
||||
| pc | `https://pc-jack.imharry.work/` | Nginx -> `/app/pc/dist` |
|
||||
| cms | `http://cms-jack.imharry.work/` | Nginx -> `/app/cms/dist` |
|
||||
| build-cms | `http://build-jack.imharry.work/` | Nginx -> `/app/build-cms/dist` |
|
||||
| build-down | `http://down-jack.imharry.work/` | Nginx -> `/app/build-down/dist` |
|
||||
|
||||
> `pc`、`build-down` 在 `deploy-test` 中都按静态站点方式提供,不再通过 `07-start-frontend.sh` 起 Web dev server。若需要 Electron 桌面端调试,请进入 `pc/` 目录单独执行项目自身命令。
|
||||
|
||||
> **meetingh5 访问方式**
|
||||
>
|
||||
@@ -283,22 +321,58 @@ TENCENT_SDK_SECRET_KEY=xxx
|
||||
> - `ws` → meetingmsg 弹幕 WebSocket `:8000`
|
||||
> - `liveApi` → livestream 直播间 API `:8888`
|
||||
|
||||
### Nginx 反代(仅公网 IP,供 PC / 浏览器访问后端)
|
||||
### Nginx 反代(域名 HTTPS 入口,供 PC / 浏览器访问后端)
|
||||
|
||||
无域名时,在**测试服务器**上部署 Nginx,统一监听 **HTTP :80**,把路径转发到本机 `openim-server` / `chat-api`:
|
||||
在**测试服务器**上部署 Nginx,本机 Nginx 仅监听 HTTP `:80`,统一提供 PC/CMS/Build-CMS 的静态 `dist` 资源,并反代 OpenIM API、chat-api、admin-api、build-server 与 MsgGateway WebSocket。外部访问仍使用对应域名,HTTPS 可由外层 LB/CDN/其它网关终止后转发到本机 `:80`。
|
||||
|
||||
| 路径前缀 | 后端 |
|
||||
|----------|------|
|
||||
| `/api/im/` | `127.0.0.1:10002` |
|
||||
| `/api/user/`、`/api/chat/` | `127.0.0.1:10008` |
|
||||
| `/msg_gateway` | `127.0.0.1:10001`(WebSocket) |
|
||||
| `Host: pc-jack.imharry.work /` | `/app/pc/dist` |
|
||||
| `Host: cms-jack.imharry.work /` | `/app/cms/dist` |
|
||||
| `Host: build-jack.imharry.work /` | `/app/build-cms/dist` |
|
||||
| `Host: down-jack.imharry.work /` | `/app/build-down/dist` |
|
||||
|
||||
1. 服务器上已执行 `05-start.sh` 等,保证 `10001/10002/10008` 在监听。
|
||||
2. 仓库根目录执行:`sudo ./deploy-test/00-init-tools.sh nginx`(会安装 `nginx` 并写入配置 `deploy-test/nginx/openim-pc-proxy.conf`;亦已包含在 `00-init-tools.sh` 无参的 **all** 流程末尾,需 root)。
|
||||
3. 云安全组放行 **TCP 80**。
|
||||
4. `.env.deploy-test` 中设置 **`PC_BACKEND_ORIGIN=http://<DEPLOY_TEST_IP>`**(无末尾 `/`),与 `DEPLOY_TEST_IP` 一致;再 `./deploy-test/07-start-frontend.sh pc` 启动 PC 时即注入 `VITE_*`。
|
||||
2. 域名 `pc-jack.imharry.work` 已解析到测试服务器公网 IP。
|
||||
3. `.env.deploy-test` 中设置 **`PC_BACKEND_ORIGIN=https://pc-jack.imharry.work`**(无末尾 `/`)。
|
||||
4. 仓库根目录执行:`./deploy-test/08-build-static-frontend.sh pc` 或 `./deploy-test/dt.sh fe-build-static pc`,构建 PC 静态资源。
|
||||
5. 如需同时构建 PC / CMS / Build-CMS / Build-Down,可直接执行:`./deploy-test/08-build-static-frontend.sh`;也可分别构建单个项目。
|
||||
6. 仓库根目录执行:`sudo ./deploy-test/00-init-tools.sh nginx`(会安装 `nginx`,并写入配置 `deploy-test/nginx/openim-pc-proxy.conf`;亦已包含在 `00-init-tools.sh` 无参的 **all** 流程末尾,需 root)。
|
||||
7. 执行:`./deploy-test/09-verify-static-frontends.sh`,确认 `dist` 和域名入口都正常。
|
||||
8. 测试服务器本机/安全组放行 **TCP 80**;外层 LB/CDN/网关对公网提供 HTTPS `443` 并转发到本机 `80`。
|
||||
9. 浏览器打开 **`https://pc-jack.imharry.work/`**、**`http://cms-jack.imharry.work/`**、**`http://build-jack.imharry.work/`**、**`http://down-jack.imharry.work/`**。
|
||||
|
||||
**仅 IP、:80 只做 API(不出现 CMS 静态站)**:本配置中 `openim-pc-proxy` 使用 **`listen 80 default_server`**,`GET /` 返回简短说明文本(非 CMS)。`00-init-tools.sh nginx` 会禁用 `sites-enabled/default`,并尝试去掉 `sites-available/default` 里的 `default_server`。若机上还有其它站点也写了 **`default_server`**,`nginx -t` 会报错,需在该站点配置中删除 `default_server`(保留 `listen 80;` 即可,用 **域名** 访问 CMS)。**CMS 开发**请用 **`http://IP:8001`**(UMI)。
|
||||
**推荐验收命令**
|
||||
|
||||
```bash
|
||||
# 1. 构建全部静态前端
|
||||
./deploy-test/08-build-static-frontend.sh
|
||||
|
||||
# 2. 安装 / 更新 nginx 配置并立即校验 Host 路由
|
||||
sudo ./deploy-test/00-init-tools.sh nginx
|
||||
|
||||
# 3. 统一校验 dist / nginx-health / 首页 HTML / title
|
||||
./deploy-test/09-verify-static-frontends.sh
|
||||
|
||||
# 4. 查看当前状态(包含静态站点 title 摘要)
|
||||
./deploy-test/status.sh
|
||||
|
||||
# 5. 本机直连 nginx 做 Host 路由抽样
|
||||
curl -H 'Host: pc-jack.imharry.work' http://127.0.0.1/ | head
|
||||
curl -H 'Host: cms-jack.imharry.work' http://127.0.0.1/ | head
|
||||
curl -H 'Host: build-jack.imharry.work' http://127.0.0.1/ | head
|
||||
curl -H 'Host: down-jack.imharry.work' http://127.0.0.1/ | head
|
||||
|
||||
# 或者直接走一条命令(需 root / sudo)
|
||||
sudo ./deploy-test/dt.sh fe-publish
|
||||
```
|
||||
|
||||
**域名访问**:本配置中 `openim-pc-proxy` 使用 `server_name pc-jack.imharry.work`、`cms-jack.imharry.work`、`build-jack.imharry.work`、`down-jack.imharry.work`,只监听本机 `80`;推荐外部域名流量经 HTTPS 入口转发到本机 `80` 后由 Nginx 分发到对应静态站点与后端 API。`00-init-tools.sh nginx` 会禁用 `sites-enabled/default`,并尝试去掉 `sites-available/default` 里的 `default_server`,避免默认站点抢占 `:80`。若机上还有其它站点也写了 `default_server`,`nginx -t` 会报错,需在该站点配置中删除 `default_server`(保留 `listen 80;` 即可)。
|
||||
|
||||
**PC 静态构建**:`08-build-static-frontend.sh pc` 会在构建前注入 `VITE_API_URL`、`VITE_WS_URL`、`VITE_CHAT_URL`、`VITE_USER_URL` 等变量,并确保 `openIM.wasm`、`sql-wasm.wasm`、`wasm_exec.js` 存在于 `pc/public`。更新 `pc` 代码或 `.env.deploy-test` 后,需要重新构建:`./deploy-test/08-build-static-frontend.sh pc`。
|
||||
|
||||
### Docker 基础设施
|
||||
|
||||
@@ -374,6 +448,8 @@ TENCENT_SDK_SECRET_KEY=xxx
|
||||
|
||||
# 查看状态
|
||||
./deploy-test/status.sh
|
||||
# status.sh 会输出 Etcd 中已注册的独立 RPC 服务 key;
|
||||
# openim-server 内部 RPC 是 standalone 进程内注册,不要求出现在 Etcd。
|
||||
|
||||
# 重启单个后端服务
|
||||
./deploy-test/restart.sh chat-api
|
||||
|
||||
143
common.sh
143
common.sh
@@ -226,9 +226,37 @@ print_svc_status() {
|
||||
# ── 所有后端服务名列表 ──────────────────────────────────────────────────────────
|
||||
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
|
||||
@@ -247,3 +275,118 @@ pc_export_vite_backend_env() {
|
||||
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
|
||||
}
|
||||
|
||||
252
dt.sh
Executable file
252
dt.sh
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# dt.sh — deploy-test 总控脚本
|
||||
#
|
||||
# 放置位置:deploy-test 目录内。
|
||||
#
|
||||
# 功能:
|
||||
# 1. 一键拉取 deploy-test 上级目录下所有 Git 工程的最新代码
|
||||
# 2. 快捷调用 deploy-test 的编译、启动、停止、重启、状态检查
|
||||
# 3. 快捷管理 deploy-test 前端服务
|
||||
#
|
||||
# 注意:
|
||||
# - pull 只执行 fetch + pull --ff-only,不会 git reset,不会覆盖本地改动。
|
||||
# - 如果某工程存在本地提交/本地改动导致无法快进,会跳过并提示手动处理。
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
DEPLOY_TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(cd "$DEPLOY_TEST_DIR/.." && pwd)"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
用法:
|
||||
./deploy-test/dt.sh <command> [args...]
|
||||
|
||||
代码更新:
|
||||
pull 拉取 deploy-test 上级目录下所有 Git 工程
|
||||
|
||||
deploy-test 后端/基础服务:
|
||||
build 调用 deploy-test/04-build.sh
|
||||
start 调用 deploy-test/05-start.sh
|
||||
stop 调用 deploy-test/stop.sh
|
||||
restart 调用 deploy-test/restart.sh
|
||||
status 调用 deploy-test/status.sh
|
||||
|
||||
deploy-test 前端:
|
||||
fe-start [project] 调用 deploy-test/07-start-frontend.sh [project](开发态前端)
|
||||
fe-stop [project] 调用 deploy-test/stop-frontend.sh [project]
|
||||
fe-build-static [project] 调用 deploy-test/08-build-static-frontend.sh [project](pc/cms/build-cms/build-down)
|
||||
fe-verify-static 调用 deploy-test/09-verify-static-frontends.sh
|
||||
fe-publish [project] 构建静态前端 + 更新 Nginx + 校验(默认 all)
|
||||
|
||||
常用组合:
|
||||
up pull + build + restart
|
||||
deploy pull + build + restart + status
|
||||
|
||||
其它:
|
||||
help 显示帮助
|
||||
|
||||
示例:
|
||||
./deploy-test/dt.sh pull
|
||||
./deploy-test/dt.sh build
|
||||
./deploy-test/dt.sh restart
|
||||
./deploy-test/dt.sh fe-build-static pc
|
||||
./deploy-test/dt.sh fe-verify-static
|
||||
sudo ./deploy-test/dt.sh fe-publish
|
||||
./deploy-test/dt.sh deploy
|
||||
EOF
|
||||
}
|
||||
|
||||
require_deploy_test() {
|
||||
if [[ ! -d "$DEPLOY_TEST_DIR" ]]; then
|
||||
err "未找到 deploy-test 目录: $DEPLOY_TEST_DIR"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_root_for_publish() {
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
err "fe-publish 需要 root / sudo,因为会执行 nginx 安装与重载"
|
||||
echo "请执行: sudo ./deploy-test/dt.sh fe-publish${1:+ $1}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_deploy_test_script() {
|
||||
local script="$1"
|
||||
shift || true
|
||||
require_deploy_test
|
||||
local path="$DEPLOY_TEST_DIR/$script"
|
||||
if [[ ! -f "$path" ]]; then
|
||||
err "脚本不存在: $path"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -x "$path" ]]; then
|
||||
chmod +x "$path" 2>/dev/null || true
|
||||
fi
|
||||
info "执行: ./deploy-test/$script $*"
|
||||
(
|
||||
cd "$ROOT_DIR"
|
||||
"./deploy-test/$script" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
current_branch() {
|
||||
git symbolic-ref --quiet --short HEAD 2>/dev/null || true
|
||||
}
|
||||
|
||||
update_one_repo() {
|
||||
local repo_dir="$1"
|
||||
local name
|
||||
name="$(basename "$repo_dir")"
|
||||
|
||||
echo ""
|
||||
info "更新工程: $name"
|
||||
|
||||
(
|
||||
cd "$repo_dir"
|
||||
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
warn "$name 不是有效 Git 工作区,跳过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local branch
|
||||
branch="$(current_branch)"
|
||||
if [[ -z "$branch" ]]; then
|
||||
warn "$name 当前是 detached HEAD,跳过自动 pull"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local upstream
|
||||
upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)"
|
||||
if [[ -z "$upstream" ]]; then
|
||||
warn "$name 分支 $branch 未设置 upstream,跳过自动 pull"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! git fetch --all --prune; then
|
||||
warn "$name git fetch 失败,跳过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
if git pull --ff-only; then
|
||||
ok "$name 已更新到最新 ($branch -> $upstream)"
|
||||
else
|
||||
warn "$name 无法 fast-forward,请手动处理分支差异"
|
||||
fi
|
||||
else
|
||||
warn "$name 存在本地未提交改动,只 fetch 不 pull,避免覆盖你的修改"
|
||||
local behind
|
||||
behind="$(git rev-list --count "HEAD..$upstream" 2>/dev/null || echo "?")"
|
||||
warn "$name 当前落后 upstream: $behind 个提交"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
update_all_repos() {
|
||||
info "扫描 Git 工程: $ROOT_DIR"
|
||||
local found=0
|
||||
local repo
|
||||
for repo in "$ROOT_DIR"/*; do
|
||||
[[ -d "$repo/.git" ]] || continue
|
||||
found=1
|
||||
update_one_repo "$repo"
|
||||
done
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
warn "未发现一级 Git 工程"
|
||||
else
|
||||
echo ""
|
||||
ok "代码更新流程完成"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
local cmd="${1:-help}"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
help|-h|--help)
|
||||
usage
|
||||
;;
|
||||
pull|update)
|
||||
update_all_repos
|
||||
;;
|
||||
build)
|
||||
run_deploy_test_script "04-build.sh" "$@"
|
||||
;;
|
||||
start)
|
||||
run_deploy_test_script "05-start.sh" "$@"
|
||||
;;
|
||||
stop)
|
||||
run_deploy_test_script "stop.sh" "$@"
|
||||
;;
|
||||
restart)
|
||||
run_deploy_test_script "restart.sh" "$@"
|
||||
;;
|
||||
status)
|
||||
run_deploy_test_script "status.sh" "$@"
|
||||
;;
|
||||
fe-start)
|
||||
run_deploy_test_script "07-start-frontend.sh" "$@"
|
||||
;;
|
||||
fe-stop)
|
||||
run_deploy_test_script "stop-frontend.sh" "$@"
|
||||
;;
|
||||
fe-build-static)
|
||||
run_deploy_test_script "08-build-static-frontend.sh" "$@"
|
||||
;;
|
||||
fe-verify-static)
|
||||
run_deploy_test_script "09-verify-static-frontends.sh" "$@"
|
||||
;;
|
||||
fe-publish)
|
||||
publish_target="${1:-all}"
|
||||
require_root_for_publish "$publish_target"
|
||||
run_deploy_test_script "08-build-static-frontend.sh" "$@"
|
||||
run_deploy_test_script "00-init-tools.sh" nginx
|
||||
run_deploy_test_script "09-verify-static-frontends.sh"
|
||||
echo ""
|
||||
ok "静态前端发布完成: ${publish_target}"
|
||||
echo "已执行:"
|
||||
echo " 1. 构建 dist"
|
||||
echo " 2. 更新并重载 Nginx"
|
||||
echo " 3. 校验静态站点入口"
|
||||
echo "建议复查:"
|
||||
echo " ./deploy-test/status.sh"
|
||||
;;
|
||||
up)
|
||||
update_all_repos
|
||||
run_deploy_test_script "04-build.sh"
|
||||
run_deploy_test_script "restart.sh"
|
||||
;;
|
||||
deploy)
|
||||
update_all_repos
|
||||
run_deploy_test_script "04-build.sh"
|
||||
run_deploy_test_script "restart.sh"
|
||||
run_deploy_test_script "status.sh"
|
||||
;;
|
||||
*)
|
||||
err "未知命令: $cmd"
|
||||
echo ""
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
7
logs.sh
7
logs.sh
@@ -16,7 +16,8 @@
|
||||
#
|
||||
# 后端服务: openim-server, chat-rpc, admin-rpc, chat-api, admin-api,
|
||||
# meetingmsg, livecloud, livestream, build-server
|
||||
# 前端服务: pc, meetingh5, h5, cms, build-cms, build-down
|
||||
# 前端服务: meetingh5, h5
|
||||
# 静态前端: pc, cms, build-cms, build-down(构建产物在 dist/,不通过本脚本查看 dev server 日志)
|
||||
# Docker: redis, kafka, etcd, livekit
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
@@ -63,7 +64,7 @@ if [[ -z "$SVC" ]]; then
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}前端服务日志${NC} ($LOG_DIR/fe-*.log):"
|
||||
FE_LIST=(pc meetingh5 h5 cms build-cms build-down)
|
||||
FE_LIST=(meetingh5 h5)
|
||||
for fe in "${FE_LIST[@]}"; do
|
||||
fe_log="$LOG_DIR/fe-${fe}.log"
|
||||
if [[ -f "$fe_log" ]]; then
|
||||
@@ -146,7 +147,7 @@ case "$SVC" in
|
||||
esac
|
||||
|
||||
# ── 前端服务日志 ──────────────────────────────────────────────────────────────
|
||||
FE_LIST=(pc meetingh5 h5 cms build-cms build-down)
|
||||
FE_LIST=(meetingh5 h5)
|
||||
for _fe in "${FE_LIST[@]}"; do
|
||||
if [[ "$SVC" == "$_fe" ]]; then
|
||||
LOGFILE="$LOG_DIR/fe-${SVC}.log"
|
||||
|
||||
@@ -6,36 +6,39 @@
|
||||
#
|
||||
# 安全组 / 防火墙须放行 TCP 80;后端 10001/10002/10008 仅需本机访问(127.0.0.1)
|
||||
#
|
||||
# CORS:Vite 开发服在 :5173,API 经 :80 反代,浏览器视为跨域,需在此返回允许头并处理 OPTIONS 预检
|
||||
# chat-api / openim 等上游若自带 Access-Control-Allow-Origin(如 *),会与下方 add_header 合并成多个值导致浏览器报错,故用 proxy_hide_header 剥掉上游 CORS
|
||||
# CORS:chat-api(:10008)与 openim(:10001/:10002)已在应用内通过 openimsdk/tools/mw.CorsHandler
|
||||
# 返回 Access-Control-Allow-Origin: *。若在此再用 add_header 追加 $http_origin,浏览器会收到
|
||||
# 「*, http://IP」两个值并报错。故本配置不在 Nginx 层添加 CORS,预检 OPTIONS 也交给上游处理。
|
||||
#
|
||||
# default_server:纯 IP 访问 http://x.x.x.x/ 时命中本 server(不做 CMS 静态站,仅 API 网关)
|
||||
# CMS 开发请用 http://IP:8001(UMI dev)
|
||||
# 推荐外部访问入口:https://pc-jack.imharry.work/
|
||||
# 本 Nginx 仅监听 HTTP :80;HTTPS 由外层 LB/CDN/网关终止后转发到本机 :80。
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
log_format openim_pc_gateway
|
||||
'$remote_addr - $host [$time_local] "$request" status=$status bytes=$body_bytes_sent '
|
||||
'upgrade="$http_upgrade" connection="$http_connection" '
|
||||
'upstream="$upstream_addr" upstream_status="$upstream_status" '
|
||||
'upstream_time="$upstream_response_time" request_time="$request_time" '
|
||||
'referer="$http_referer" ua="$http_user_agent"';
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name pc-jack.imharry.work;
|
||||
|
||||
client_max_body_size 100m;
|
||||
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||
add_header Cross-Origin-Embedder-Policy "credentialless" always;
|
||||
|
||||
# 根路径:不托管前端;避免与其它站点抢 default_server 后仍误以为是 CMS
|
||||
location = / {
|
||||
default_type text/plain;
|
||||
charset utf-8;
|
||||
return 200 "OpenIM API gateway (deploy-test). Paths: /api/im/ /api/user/ /api/chat/ /msg_gateway — CMS dev: :8001\n";
|
||||
}
|
||||
access_log /var/log/nginx/openim-pc-proxy-access.log openim_pc_gateway;
|
||||
error_log /var/log/nginx/openim-pc-proxy-error.log warn;
|
||||
|
||||
# OpenIM HTTP API → openim-server :10002
|
||||
location /api/im/ {
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
add_header Access-Control-Max-Age 86400 always;
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
proxy_pass http://127.0.0.1:10002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
@@ -44,26 +47,10 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Expose-Headers;
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
}
|
||||
|
||||
# 用户 / 登录相关 → chat-api :10008(与 im-cms-nginx 一致)
|
||||
location /api/user/ {
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
add_header Access-Control-Max-Age 86400 always;
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
proxy_pass http://127.0.0.1:10008/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
@@ -72,26 +59,10 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Expose-Headers;
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
}
|
||||
|
||||
# Chat API → chat-api :10008
|
||||
location /api/chat/ {
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
add_header Access-Control-Max-Age 86400 always;
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
proxy_pass http://127.0.0.1:10008/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
@@ -100,31 +71,29 @@ server {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Expose-Headers;
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS, PATCH" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,X-Requested-With,DNT,User-Agent,If-Modified-Since,Cache-Control,Range" always;
|
||||
}
|
||||
|
||||
# Admin API → admin-api :10009
|
||||
location /api/admin/ {
|
||||
proxy_pass http://127.0.0.1:10009/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
# MsgGateway WebSocket → openim-server :10001
|
||||
location /msg_gateway {
|
||||
if ($request_method = OPTIONS) {
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Authorization,Content-Type,token,operationID,Upgrade,Connection,Sec-WebSocket-Key,Sec-WebSocket-Version,Sec-WebSocket-Protocol,Sec-WebSocket-Extensions" always;
|
||||
add_header Access-Control-Max-Age 86400 always;
|
||||
add_header Content-Length 0;
|
||||
return 204;
|
||||
}
|
||||
location ^~ /msg_gateway {
|
||||
proxy_pass http://127.0.0.1:10001;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
@@ -132,12 +101,6 @@ server {
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s;
|
||||
proxy_hide_header Access-Control-Allow-Origin;
|
||||
proxy_hide_header Access-Control-Allow-Credentials;
|
||||
proxy_hide_header Access-Control-Allow-Methods;
|
||||
proxy_hide_header Access-Control-Allow-Headers;
|
||||
proxy_hide_header Access-Control-Expose-Headers;
|
||||
add_header Access-Control-Allow-Origin $http_origin always;
|
||||
}
|
||||
|
||||
# 可选:健康检查
|
||||
@@ -146,4 +109,137 @@ server {
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
# PC 静态站点(dist).
|
||||
location / {
|
||||
root /app/pc/dist;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name cms-jack.imharry.work;
|
||||
|
||||
root /app/cms/dist;
|
||||
index index.html;
|
||||
|
||||
access_log /var/log/nginx/openim-cms-access.log openim_pc_gateway;
|
||||
error_log /var/log/nginx/openim-cms-error.log warn;
|
||||
|
||||
client_max_body_size 100m;
|
||||
|
||||
location /api/im/ {
|
||||
proxy_pass http://127.0.0.1:10002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/user/ {
|
||||
proxy_pass http://127.0.0.1:10008/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location /api/admin/ {
|
||||
proxy_pass http://127.0.0.1:10009/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location = /nginx-health {
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name build-jack.imharry.work;
|
||||
|
||||
root /app/build-cms/dist;
|
||||
index index.html;
|
||||
|
||||
access_log /var/log/nginx/openim-build-cms-access.log openim_pc_gateway;
|
||||
error_log /var/log/nginx/openim-build-cms-error.log warn;
|
||||
|
||||
client_max_body_size 100m;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8281;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location = /nginx-health {
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name down-jack.imharry.work;
|
||||
|
||||
root /app/build-down/dist;
|
||||
index index.html;
|
||||
|
||||
access_log /var/log/nginx/openim-build-down-access.log openim_pc_gateway;
|
||||
error_log /var/log/nginx/openim-build-down-error.log warn;
|
||||
|
||||
client_max_body_size 100m;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8281;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
|
||||
location = /nginx-health {
|
||||
access_log off;
|
||||
default_type text/plain;
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
311
pc-sdk-probe.sh
Executable file
311
pc-sdk-probe.sh
Executable file
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cat <<'EOF'
|
||||
在 Chrome DevTools Console 粘贴执行:
|
||||
|
||||
(() => {
|
||||
const stamp = () => new Date().toISOString();
|
||||
const counts = {};
|
||||
if (!window.__deployTestWorkerWrapped) {
|
||||
const NativeWorker = window.Worker;
|
||||
window.Worker = function (url, options) {
|
||||
console.log(`[probe ${stamp()}] new Worker`, { url: String(url), options });
|
||||
const worker = new NativeWorker(url, options);
|
||||
worker.addEventListener("error", (event) => {
|
||||
console.error("[probe worker error]", {
|
||||
message: event.message,
|
||||
filename: event.filename,
|
||||
lineno: event.lineno,
|
||||
colno: event.colno,
|
||||
});
|
||||
});
|
||||
worker.addEventListener("messageerror", (event) => {
|
||||
console.error("[probe worker messageerror]", event);
|
||||
});
|
||||
worker.addEventListener("message", (event) => {
|
||||
console.log(`[probe ${stamp()}] worker.message`, { url: String(url), data: event.data });
|
||||
});
|
||||
const originalPostMessage = worker.postMessage.bind(worker);
|
||||
worker.postMessage = (...args) => {
|
||||
console.log(`[probe ${stamp()}] worker.postMessage`, { url: String(url), args });
|
||||
return originalPostMessage(...args);
|
||||
};
|
||||
return worker;
|
||||
};
|
||||
window.Worker.prototype = NativeWorker.prototype;
|
||||
window.__deployTestWorkerWrapped = true;
|
||||
console.log("[probe] Worker: wrapped; reload page after installing this probe to catch SDK worker creation and __absurd:spawn-idb-worker");
|
||||
}
|
||||
const wrap = (name) => {
|
||||
const original = window[name];
|
||||
if (typeof original !== "function") {
|
||||
console.log(`[probe] ${name}:`, typeof original);
|
||||
return;
|
||||
}
|
||||
counts[name] = 0;
|
||||
const wrapped = function (...args) {
|
||||
counts[name] += 1;
|
||||
console.log(`[probe ${stamp()}] ${name} called`, args);
|
||||
try {
|
||||
const ret = original.apply(this, args);
|
||||
console.log(`[probe ${stamp()}] ${name} returned`, ret);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(`[probe ${stamp()}] ${name} threw`, err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
wrapped.__deployTestProbe = true;
|
||||
wrapped.__deployTestOriginal = original;
|
||||
window[name] = wrapped;
|
||||
console.log(`[probe] ${name}: wrapped`);
|
||||
};
|
||||
|
||||
["initSDK", "login", "logout", "commonEventFunc", "initDB", "setSqlWasmPath"].forEach(wrap);
|
||||
window.addEventListener("error", (event) => {
|
||||
console.error("[probe window error]", event.message, event.filename, event.lineno, event.error);
|
||||
});
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
console.error("[probe unhandledrejection]", event.reason);
|
||||
});
|
||||
console.log("[probe ready]", {
|
||||
electronAPI: Boolean(window.electronAPI),
|
||||
href: window.location.href,
|
||||
isSecureContext: window.isSecureContext,
|
||||
crossOriginIsolated: window.crossOriginIsolated,
|
||||
SharedArrayBuffer: typeof window.SharedArrayBuffer,
|
||||
Go: typeof Go,
|
||||
initSDK: typeof window.initSDK,
|
||||
login: typeof window.login,
|
||||
logout: typeof window.logout,
|
||||
commonEventFunc: typeof window.commonEventFunc,
|
||||
initDB: typeof window.initDB,
|
||||
setSqlWasmPath: typeof window.setSqlWasmPath,
|
||||
openIMRenderApi: typeof window.openIMRenderApi,
|
||||
});
|
||||
window.__deployTestProbeCounts = counts;
|
||||
window.__deployTestReadLocalForage = async (key) => {
|
||||
const tryRead = (dbName, storeName) => new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(dbName);
|
||||
req.onerror = () => reject(req.error);
|
||||
req.onsuccess = () => {
|
||||
const db = req.result;
|
||||
if (!Array.from(db.objectStoreNames || []).includes(storeName)) {
|
||||
db.close();
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
const tx = db.transaction(storeName, "readonly");
|
||||
const store = tx.objectStore(storeName);
|
||||
const getReq = store.get(key);
|
||||
getReq.onerror = () => reject(getReq.error);
|
||||
getReq.onsuccess = () => {
|
||||
db.close();
|
||||
resolve(getReq.result);
|
||||
};
|
||||
};
|
||||
});
|
||||
for (const dbName of ["localforage", "OpenCorp-Base"]) {
|
||||
for (const storeName of ["keyvaluepairs", "local-forage-detect-blob-support"]) {
|
||||
try {
|
||||
const value = await tryRead(dbName, storeName);
|
||||
if (value) return value;
|
||||
} catch (err) {
|
||||
console.warn(`[probe] read ${dbName}/${storeName}/${key} failed`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
window.__deployTestAuthState = async () => {
|
||||
const state = {
|
||||
href: window.location.href,
|
||||
hash: window.location.hash,
|
||||
isSecureContext: window.isSecureContext,
|
||||
crossOriginIsolated: window.crossOriginIsolated,
|
||||
SharedArrayBuffer: typeof window.SharedArrayBuffer,
|
||||
electronAPI: Boolean(window.electronAPI),
|
||||
Go: typeof Go,
|
||||
initSDK: typeof window.initSDK,
|
||||
login: typeof window.login,
|
||||
logout: typeof window.logout,
|
||||
initDB: typeof window.initDB,
|
||||
setSqlWasmPath: typeof window.setSqlWasmPath,
|
||||
userID: await window.__deployTestReadLocalForage("IM_USERID"),
|
||||
hasIMToken: Boolean(await window.__deployTestReadLocalForage("IM_TOKEN")),
|
||||
hasChatToken: Boolean(await window.__deployTestReadLocalForage("IM_CHAT_TOKEN")),
|
||||
counts: { ...counts },
|
||||
};
|
||||
console.log("[probe] auth state", state);
|
||||
console.log("[probe] auth state json", JSON.stringify(state, null, 2));
|
||||
return state;
|
||||
};
|
||||
if (!window.__deployTestXHRWrapped) {
|
||||
const originalOpen = XMLHttpRequest.prototype.open;
|
||||
const originalSend = XMLHttpRequest.prototype.send;
|
||||
XMLHttpRequest.prototype.open = function (method, url, ...args) {
|
||||
this.__deployTestRequest = { method, url, startedAt: Date.now() };
|
||||
return originalOpen.call(this, method, url, ...args);
|
||||
};
|
||||
XMLHttpRequest.prototype.send = function (...args) {
|
||||
const req = this.__deployTestRequest;
|
||||
if (req) {
|
||||
this.addEventListener("loadend", () => {
|
||||
if (String(req.url).includes("/account/login") || String(req.url).includes("/auth/get_user_token")) {
|
||||
console.log("[probe xhr]", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: this.status,
|
||||
ms: Date.now() - req.startedAt,
|
||||
response: String(this.responseText || "").slice(0, 500),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return originalSend.apply(this, args);
|
||||
};
|
||||
window.__deployTestXHRWrapped = true;
|
||||
console.log("[probe] XMLHttpRequest: wrapped");
|
||||
}
|
||||
window.__deployTestListBrowserDBs = async () => {
|
||||
const dbs = indexedDB.databases ? await indexedDB.databases() : [];
|
||||
console.log("[probe] indexedDB databases", dbs);
|
||||
if (window.caches?.keys) {
|
||||
console.log("[probe] cache keys", await caches.keys());
|
||||
}
|
||||
return dbs;
|
||||
};
|
||||
window.__deployTestResetBrowserStorage = async () => {
|
||||
console.warn("[probe] 清理当前站点浏览器存储:localStorage / CacheStorage / IndexedDB");
|
||||
localStorage.clear();
|
||||
if (window.caches?.keys) {
|
||||
for (const key of await caches.keys()) {
|
||||
console.warn("[probe] delete cache", key, await caches.delete(key));
|
||||
}
|
||||
}
|
||||
if (indexedDB.databases) {
|
||||
for (const db of await indexedDB.databases()) {
|
||||
if (!db.name) continue;
|
||||
await new Promise((resolve) => {
|
||||
const req = indexedDB.deleteDatabase(db.name);
|
||||
req.onsuccess = () => {
|
||||
console.warn("[probe] deleted indexedDB", db.name);
|
||||
resolve();
|
||||
};
|
||||
req.onerror = () => {
|
||||
console.warn("[probe] delete indexedDB failed", db.name, req.error);
|
||||
resolve();
|
||||
};
|
||||
req.onblocked = () => {
|
||||
console.warn("[probe] delete indexedDB blocked, close other tabs then retry", db.name);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn("[probe] indexedDB.databases() 不可用,请在 DevTools Application 面板手动清理 IndexedDB");
|
||||
}
|
||||
console.warn("[probe] 清理完成,请关闭其它同源标签页后刷新页面并重新登录");
|
||||
};
|
||||
window.__deployTestManualLogin = async ({ userID, token, apiAddr, wsAddr, dataDir = "./" } = {}) => {
|
||||
userID = userID || await window.__deployTestReadLocalForage("IM_USERID");
|
||||
token = token || await window.__deployTestReadLocalForage("IM_TOKEN");
|
||||
const operationID = `deploy-test-${Date.now()}`;
|
||||
const gatewayOrigin = window.location.origin || "https://pc-jack.imharry.work";
|
||||
const config = {
|
||||
platformID: 5,
|
||||
apiAddr: apiAddr || `${gatewayOrigin}/api/im`,
|
||||
wsAddr: wsAddr || `${gatewayOrigin.replace(/^http/, "ws")}/msg_gateway`,
|
||||
dataDir: String(dataDir),
|
||||
logLevel: 5,
|
||||
isLogStandardOutput: true,
|
||||
logFilePath: "./",
|
||||
isExternalExtensions: false,
|
||||
};
|
||||
console.log(`[probe ${stamp()}] manual initSDK`, { operationID, config });
|
||||
window.initSDK(operationID, JSON.stringify(config));
|
||||
console.log(`[probe ${stamp()}] manual login start`, { operationID, userID, hasToken: Boolean(token) });
|
||||
const timeout = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("manual window.login timeout after 15000ms")), 15000);
|
||||
});
|
||||
const result = await Promise.race([window.login(operationID, String(userID || ""), String(token || "")), timeout]);
|
||||
console.log(`[probe ${stamp()}] manual login result`, result);
|
||||
return result;
|
||||
};
|
||||
window.__deployTestManualInitDB = async (userID, dataDir = "./") => {
|
||||
userID = userID || await window.__deployTestReadLocalForage("IM_USERID") || "6087132211";
|
||||
console.log(`[probe ${stamp()}] manual setSqlWasmPath`, "/sql-wasm.wasm");
|
||||
if (typeof window.setSqlWasmPath === "function") {
|
||||
try {
|
||||
const setPathTimeout = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("manual window.setSqlWasmPath timeout after 5000ms")), 5000);
|
||||
});
|
||||
await Promise.race([window.setSqlWasmPath("/sql-wasm.wasm"), setPathTimeout]);
|
||||
console.log(`[probe ${stamp()}] manual setSqlWasmPath done`);
|
||||
} catch (err) {
|
||||
console.error(`[probe ${stamp()}] manual setSqlWasmPath failed`, err);
|
||||
}
|
||||
}
|
||||
console.log(`[probe ${stamp()}] manual initDB start`, { userID, dataDir });
|
||||
const timeout = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("manual window.initDB timeout after 15000ms")), 15000);
|
||||
});
|
||||
const result = await Promise.race([window.initDB(String(userID), String(dataDir)), timeout]);
|
||||
console.log(`[probe ${stamp()}] manual initDB result`, result);
|
||||
return result;
|
||||
};
|
||||
clearInterval(window.__deployTestProbeTimer);
|
||||
window.__deployTestProbeTimer = setInterval(() => {
|
||||
const snapshot = { ...counts };
|
||||
console.log(`[probe ${stamp()}] counts`, snapshot);
|
||||
if (!snapshot.initSDK && !snapshot.login) {
|
||||
console.warn("[probe hint] initSDK/login 仍为 0:当前页面没有重新触发 SDK 登录。若 logout > 0 且报 10008,只说明业务层在未初始化 SDK 时触发了退出。请执行 await window.__deployTestAuthState();若仍有 token,可执行 await window.__deployTestManualLogin() 手动验证 SDK 初始化链路。");
|
||||
}
|
||||
}, 5000);
|
||||
})();
|
||||
|
||||
然后退出登录/刷新页面重新登录一次,观察是否打印:
|
||||
- new Worker / worker.postMessage / worker error
|
||||
- initSDK / login / logout / initDB / setSqlWasmPath called / returned
|
||||
- window error / unhandledrejection
|
||||
- counts 里的 initSDK/login 是否从 0 变成 1
|
||||
|
||||
如果点登录后 counts 仍为 0,可用当前登录 token 手动测:
|
||||
|
||||
window.__deployTestManualLogin()
|
||||
|
||||
如果自动读取 token 失败,再手动传入:
|
||||
|
||||
window.__deployTestManualLogin({
|
||||
userID: "6087132211",
|
||||
token: "把 console 里 SDK login args 的 token 粘到这里"
|
||||
})
|
||||
|
||||
若默认 dataDir="./" 仍报 initDB timeout,可验证是否是浏览器 IDBFS 路径问题:
|
||||
|
||||
await window.__deployTestManualLogin({ dataDir: "" })
|
||||
await window.__deployTestManualInitDB(undefined, "")
|
||||
|
||||
若报 10006 init database invoke javascript timeout,先查看并清理当前站点浏览器数据库:
|
||||
|
||||
await window.__deployTestListBrowserDBs()
|
||||
await window.__deployTestResetBrowserStorage()
|
||||
|
||||
也可以单独测试 initDB:
|
||||
|
||||
await window.__deployTestManualInitDB()
|
||||
|
||||
清理后按这个顺序重测:
|
||||
1. 关闭其它 https://pc-jack.imharry.work/ 标签页
|
||||
2. 打开 https://pc-jack.imharry.work/ 并刷新页面
|
||||
3. 重新粘贴本脚本输出的整段 JS(刷新会清掉已安装的 probe)
|
||||
4. 再登录
|
||||
5. 观察 counts 是否从 0 变 1,以及 /var/log/nginx/openim-pc-proxy-access.log 是否出现 /msg_gateway
|
||||
|
||||
如果 counts 仍为 0,查看业务登录链路状态:
|
||||
|
||||
await window.__deployTestAuthState()
|
||||
|
||||
HTTPS 域名入口下 isSecureContext / crossOriginIsolated 应为 true,SharedArrayBuffer 应为 "function";同时观察 SDK 方法是否存在、login 是否返回成功,以及 /msg_gateway 是否建立连接。
|
||||
EOF
|
||||
4
setup.sh
4
setup.sh
@@ -80,6 +80,10 @@ echo " ./deploy-test/logs.sh <service> # 实时日志"
|
||||
echo " ./deploy-test/restart.sh <service> # 重启服务"
|
||||
echo " ./deploy-test/restart.sh <svc> --build # 重编译并重启"
|
||||
echo " ./deploy-test/check-conn.sh # 验证 MongoDB / MinIO"
|
||||
echo " ./deploy-test/08-build-static-frontend.sh # 构建 pc/cms/build-cms/build-down 静态资源"
|
||||
echo " sudo ./deploy-test/dt.sh fe-publish # 构建静态前端 + 更新 Nginx + 校验"
|
||||
echo " sudo ./deploy-test/00-init-tools.sh nginx # 安装 / 更新 Nginx 域名入口"
|
||||
echo " ./deploy-test/09-verify-static-frontends.sh # 验证静态前端与域名入口"
|
||||
echo ""
|
||||
echo -e "${BOLD}停止服务:${NC}"
|
||||
echo " ./deploy-test/stop.sh # 停止后端进程"
|
||||
|
||||
182
status.sh
182
status.sh
@@ -9,7 +9,14 @@
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
load_env 2>/dev/null || true
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
set -a
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
fi
|
||||
DEPLOY_TEST_IP="${DEPLOY_TEST_IP:-127.0.0.1}"
|
||||
normalize_pc_proxy_env
|
||||
|
||||
header "服务运行状态"
|
||||
|
||||
@@ -24,6 +31,78 @@ print_container_status "MinIO" "dev-minio" "${MINIO_API_PORT:-9000}"
|
||||
print_container_status "LiveKit" "dev-livekit" "7880"
|
||||
printf " ${CYAN}◉${NC} %-10s 公网 %s:50000-51000/udp (WebRTC)\n" "" "${LIVEKIT_NODE_IP:-?}"
|
||||
|
||||
# ── Etcd RPC 注册 ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ Etcd RPC 注册 ]${NC}"
|
||||
ETCD_ENDPOINT="127.0.0.1:${ETCD_PORT:-2379}"
|
||||
if ! command -v etcdctl &>/dev/null; then
|
||||
printf " ${YELLOW}○${NC} %-18s %s\n" "etcdctl" "未安装,请执行: sudo ./deploy-test/00-init-tools.sh etcdctl"
|
||||
else
|
||||
if ETCD_KEYS=$(ETCDCTL_API=3 etcdctl --endpoints="$ETCD_ENDPOINT" get --prefix "" --keys-only 2>&1); then
|
||||
if [[ -z "$ETCD_KEYS" ]]; then
|
||||
printf " ${YELLOW}○${NC} %-18s %s\n" "registry" "Etcd 可访问,但当前无注册 key"
|
||||
else
|
||||
OPENIM_STANDALONE_RPC_SERVICES=(
|
||||
auth-rpc-service
|
||||
user-rpc-service
|
||||
friend-rpc-service
|
||||
group-rpc-service
|
||||
msg-rpc-service
|
||||
conversation-rpc-service
|
||||
third-rpc-service
|
||||
push-rpc-service
|
||||
messagegateway-rpc-service
|
||||
)
|
||||
INDEPENDENT_RPC_SERVICES=(
|
||||
chat-rpc-service
|
||||
admin-rpc-service
|
||||
bot-rpc-service
|
||||
)
|
||||
|
||||
echo -e " ${CYAN}OpenIM standalone 内部 RPC(单进程内调用,不要求 Etcd 注册):${NC}"
|
||||
for svc in "${OPENIM_STANDALONE_RPC_SERVICES[@]}"; do
|
||||
count=$(printf '%s\n' "$ETCD_KEYS" | grep -c "$svc" || true)
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
printf " ${GREEN}●${NC} %-28s %s key(s)\n" "$svc" "$count"
|
||||
else
|
||||
printf " ${CYAN}◉${NC} %-28s %s\n" "$svc" "standalone 进程内"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " ${CYAN}独立进程 RPC(应注册到 Etcd):${NC}"
|
||||
found_required_rpc=0
|
||||
missing_required_rpc=0
|
||||
for svc in "${INDEPENDENT_RPC_SERVICES[@]}"; do
|
||||
count=$(printf '%s\n' "$ETCD_KEYS" | grep -c "$svc" || true)
|
||||
if [[ "$count" -gt 0 ]]; then
|
||||
found_required_rpc=1
|
||||
printf " ${GREEN}●${NC} %-28s %s key(s)\n" "$svc" "$count"
|
||||
else
|
||||
missing_required_rpc=1
|
||||
printf " ${YELLOW}○${NC} %-28s %s\n" "$svc" "未注册"
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " ${CYAN}原始注册 key(过滤 rpc/service/openim/chat/admin 等关键词,最多 80 行):${NC}"
|
||||
filtered_keys=$(printf '%s\n' "$ETCD_KEYS" | grep -Ei 'rpc|service|openim|chat|admin|auth|user|friend|group|msg|conversation|third|push|gateway|bot' | head -80 || true)
|
||||
if [[ -n "$filtered_keys" ]]; then
|
||||
printf '%s\n' "$filtered_keys" | sed 's/^/ /'
|
||||
else
|
||||
echo " (未匹配到 RPC 相关 key)"
|
||||
fi
|
||||
|
||||
if [[ "$found_required_rpc" -eq 0 ]]; then
|
||||
warn " 未发现独立进程 RPC 注册;chat-api/admin-api 调 RPC 时可能出现 name resolver error: produced zero addresses"
|
||||
elif [[ "$missing_required_rpc" -eq 1 ]]; then
|
||||
warn " 存在独立进程 RPC 未注册;如果对应 API 调用失败,请优先检查该 RPC 进程日志"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
printf " ${RED}○${NC} %-18s %s\n" "etcd" "不可访问: $ETCD_ENDPOINT"
|
||||
printf "%s\n" "$ETCD_KEYS" | sed 's/^/ /'
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 远程服务 ─────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ 远程服务(连接配置)]${NC}"
|
||||
@@ -45,31 +124,100 @@ print_svc_status "livecloud" ":8080"
|
||||
print_svc_status "livestream" ":8888"
|
||||
print_svc_status "build-server" ":8281"
|
||||
|
||||
# ── 前端服务 ─────────────────────────────────────────────────────────────────
|
||||
# ── Nginx API / WebSocket 网关 ─────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ Nginx 网关(PC API / WebSocket)]${NC}"
|
||||
if command -v curl &>/dev/null && [[ -n "${PC_BACKEND_ORIGIN:-}" ]]; then
|
||||
PC_BACKEND_ORIGIN="${PC_BACKEND_ORIGIN%/}"
|
||||
curl_tls=()
|
||||
[[ "$PC_BACKEND_ORIGIN" == https://* ]] && curl_tls=(-k)
|
||||
if curl "${curl_tls[@]}" -fsS --max-time 3 "${PC_BACKEND_ORIGIN}/nginx-health" >/dev/null 2>&1; then
|
||||
printf " ${GREEN}●${NC} %-14s %s\n" "nginx-health" "${PC_BACKEND_ORIGIN}/nginx-health"
|
||||
else
|
||||
printf " ${RED}○${NC} %-14s %s\n" "nginx-health" "${PC_BACKEND_ORIGIN}/nginx-health 不可达"
|
||||
echo " 请执行: sudo ./deploy-test/00-init-tools.sh nginx,并确认安全组/防火墙放行 TCP 80"
|
||||
fi
|
||||
gateway_host=$(printf '%s' "$PC_BACKEND_ORIGIN" | sed -E 's#^https?://([^/]+).*#\1#')
|
||||
if [[ "$PC_BACKEND_ORIGIN" == https://* ]]; then
|
||||
default_ws_url="wss://${gateway_host}/msg_gateway"
|
||||
else
|
||||
default_ws_url="ws://${gateway_host}/msg_gateway"
|
||||
fi
|
||||
echo " PC IM API: ${PC_VITE_API_URL:-${PC_BACKEND_ORIGIN}/api/im}"
|
||||
echo " PC User API: ${PC_VITE_USER_URL:-${PC_BACKEND_ORIGIN}/api/user}"
|
||||
echo " PC Chat API: ${PC_VITE_CHAT_URL:-${PC_BACKEND_ORIGIN}/api/chat}"
|
||||
echo " PC Admin API: ${PC_VITE_ADMIN_URL:-${PC_BACKEND_ORIGIN}/api/admin}"
|
||||
echo " PC WebSocket: ${PC_VITE_WS_URL:-$default_ws_url}"
|
||||
echo " PC 页面入口: ${PC_BACKEND_ORIGIN}/ (外部 HTTPS → 本机 Nginx :80 → /app/pc/dist)"
|
||||
echo " Nginx 日志: /var/log/nginx/openim-pc-proxy-access.log"
|
||||
pc_probe_msg_gateway "$PC_BACKEND_ORIGIN"
|
||||
else
|
||||
printf " ${YELLOW}○${NC} %-14s %s\n" "nginx-health" "跳过(未安装 curl 或未设置 PC_BACKEND_ORIGIN)"
|
||||
fi
|
||||
|
||||
# ── 静态前端 ─────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ 静态前端 ]${NC}"
|
||||
print_static_site_status() {
|
||||
local label="$1" dist_file="$2" build_hint="$3" host_header="$4" access_url="$5"
|
||||
local body title
|
||||
|
||||
if [[ -f "$dist_file" ]]; then
|
||||
printf " ${GREEN}●${NC} %-14s %s\n" "${label}-dist" "$dist_file"
|
||||
else
|
||||
printf " ${YELLOW}○${NC} %-14s %s\n" "${label}-dist" "未构建(执行 ${build_hint})"
|
||||
fi
|
||||
|
||||
if command -v curl &>/dev/null; then
|
||||
if curl -fsS --max-time 3 -H "Host: ${host_header}" http://127.0.0.1/nginx-health >/dev/null 2>&1; then
|
||||
printf " ${GREEN}●${NC} %-14s %s\n" "${label}-nginx" "$access_url"
|
||||
body="$(curl -fsS --max-time 5 -H "Host: ${host_header}" http://127.0.0.1/ 2>/dev/null || true)"
|
||||
if [[ -n "$body" ]] && printf '%s' "$body" | grep -qi "<html"; then
|
||||
title="$(printf '%s' "$body" | grep -oiE '<title>[^<]*</title>' | head -1 | sed -E 's#</?title>##g' || true)"
|
||||
if [[ -n "$title" ]]; then
|
||||
printf " ${CYAN}◉${NC} %-14s %s\n" "${label}-title" "$title"
|
||||
else
|
||||
printf " ${CYAN}◉${NC} %-14s %s\n" "${label}-title" "(未解析到 <title>)"
|
||||
fi
|
||||
else
|
||||
printf " ${YELLOW}○${NC} %-14s %s\n" "${label}-title" "首页返回异常,未识别为 HTML"
|
||||
fi
|
||||
else
|
||||
printf " ${YELLOW}○${NC} %-14s %s\n" "${label}-nginx" "不可达(执行 sudo ./deploy-test/00-init-tools.sh nginx)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
print_static_site_status "pc" "$ROOT_DIR/pc/dist/index.html" "./deploy-test/08-build-static-frontend.sh pc" "${PC_PROXY_DOMAIN}" "${PC_BACKEND_ORIGIN}/"
|
||||
print_static_site_status "cms" "$ROOT_DIR/cms/dist/index.html" "./deploy-test/08-build-static-frontend.sh cms" "cms-jack.imharry.work" "http://cms-jack.imharry.work/"
|
||||
print_static_site_status "build-cms" "$ROOT_DIR/build-cms/dist/index.html" "./deploy-test/08-build-static-frontend.sh build-cms" "build-jack.imharry.work" "http://build-jack.imharry.work/"
|
||||
print_static_site_status "build-down" "$ROOT_DIR/build-down/dist/index.html" "./deploy-test/08-build-static-frontend.sh build-down" "down-jack.imharry.work" "http://down-jack.imharry.work/"
|
||||
|
||||
# ── 前端开发服务器 ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ 前端开发服务器 ]${NC}"
|
||||
declare -A FE_PORT_MAP=(
|
||||
[pc]="5173 (yarn dev:web)"
|
||||
[meetingh5]="5188"
|
||||
[h5]="3003"
|
||||
[cms]="8001"
|
||||
[build-cms]="8002"
|
||||
[build-down]="8003"
|
||||
)
|
||||
for fe in pc meetingh5 h5 cms build-cms build-down; do
|
||||
fe_port_desc() {
|
||||
case "$1" in
|
||||
meetingh5) echo "5188" ;;
|
||||
h5) echo "3003" ;;
|
||||
*) echo "?" ;;
|
||||
esac
|
||||
}
|
||||
for fe in meetingh5 h5; do
|
||||
pidfile="$PID_DIR/fe-${fe}.pid"
|
||||
logfile="$LOG_DIR/fe-${fe}.log"
|
||||
port_desc="$(fe_port_desc "$fe")"
|
||||
if [[ -f "$pidfile" ]] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
|
||||
printf " ${GREEN}●${NC} %-14s PID=%-7s :%s\n" "$fe" "$(cat "$pidfile")" "${FE_PORT_MAP[$fe]}"
|
||||
printf " ${GREEN}●${NC} %-14s PID=%-7s :%s\n" "$fe" "$(cat "$pidfile")" "$port_desc"
|
||||
else
|
||||
printf " ${RED}○${NC} %-14s 未运行 :%s\n" "$fe" "${FE_PORT_MAP[$fe]}"
|
||||
printf " ${RED}○${NC} %-14s 未运行 :%s\n" "$fe" "$port_desc"
|
||||
fi
|
||||
done
|
||||
|
||||
# ── 端口占用检查 ──────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ 端口占用 ]${NC}"
|
||||
PORTS=(10002 10001 10008 10009 8000 8080 8888 8281 5173 5188 3003 8001 8002 8003)
|
||||
PORTS=(10002 10001 10008 10009 8000 8080 8888 8281 5188 3003)
|
||||
for port in "${PORTS[@]}"; do
|
||||
pid=$(lsof -ti :"$port" 2>/dev/null | head -1 || true)
|
||||
if [[ -n "$pid" ]]; then
|
||||
@@ -83,10 +231,12 @@ done
|
||||
# ── 快速操作提示 ──────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${BOLD}[ 快捷命令 ]${NC}"
|
||||
echo " ./deploy-test/logs.sh <service> 查看日志(支持前端: pc/h5/cms 等)"
|
||||
echo " ./deploy-test/logs.sh <service> 查看日志(支持前端: meetingh5/h5)"
|
||||
echo " ./deploy-test/restart.sh <service> 重启后端服务"
|
||||
echo " ./deploy-test/restart.sh <svc> --build 重编译并重启"
|
||||
echo " ./deploy-test/07-start-frontend.sh 启动前端服务"
|
||||
echo " ./deploy-test/07-start-frontend.sh 启动开发态前端"
|
||||
echo " ./deploy-test/08-build-static-frontend.sh 构建 pc/cms/build-cms/build-down 静态资源"
|
||||
echo " sudo ./deploy-test/dt.sh fe-publish 构建静态前端 + 更新 Nginx + 校验"
|
||||
echo " ./deploy-test/stop-frontend.sh 停止前端服务"
|
||||
echo " ./deploy-test/check-conn.sh 验证 MongoDB/S3 连接"
|
||||
echo ""
|
||||
|
||||
@@ -6,32 +6,87 @@
|
||||
# ./stop-frontend.sh # 停止全部前端服务
|
||||
# ./stop-frontend.sh <project> # 只停止指定项目
|
||||
#
|
||||
# 可用项目: pc, meetingh5, h5, cms, build-cms, build-down
|
||||
# 可用项目: meetingh5, h5
|
||||
# 注意: pc / cms / build-cms / build-down 已改为静态构建,不再通过本脚本管理
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
||||
init_dirs
|
||||
init_script_log # ← 脚本执行日志
|
||||
|
||||
FE_PROJECTS=(pc meetingh5 h5 cms build-cms build-down)
|
||||
FE_PROJECTS=(meetingh5 h5)
|
||||
STATIC_FRONTENDS=(pc cms build-cms build-down)
|
||||
TARGET="${1:-all}"
|
||||
|
||||
declare -A FE_PORT_NUM=(
|
||||
[meetingh5]="5188"
|
||||
[h5]="3003"
|
||||
)
|
||||
|
||||
_stop_fe() {
|
||||
local name="$1"
|
||||
local pidfile="$PID_DIR/fe-${name}.pid"
|
||||
local port="${FE_PORT_NUM[$name]}"
|
||||
if [[ -f "$pidfile" ]]; then
|
||||
local pid; pid=$(cat "$pidfile")
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
# 杀掉整个进程组(覆盖 npm/pnpm/yarn 子进程)
|
||||
kill -- -"$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')" 2>/dev/null || kill "$pid" 2>/dev/null || true
|
||||
success "$name 已停止 (PID=$pid)"
|
||||
# 只杀该前端的进程树,避免同一脚本启动的其它前端共享进程组时被误杀。
|
||||
_kill_tree "$pid" TERM
|
||||
sleep 1
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
_kill_tree "$pid" KILL
|
||||
fi
|
||||
success "$name 已停止 (端口=$port, PID=$pid)"
|
||||
else
|
||||
warn "$name 进程不存在(可能已退出)"
|
||||
warn "$name 进程不存在(端口=$port,可能已退出)"
|
||||
fi
|
||||
rm -f "$pidfile"
|
||||
else
|
||||
warn "$name 没有 PID 记录(未运行)"
|
||||
warn "$name 没有 PID 记录(端口=$port,未运行)"
|
||||
fi
|
||||
|
||||
_stop_fe_port "$name"
|
||||
}
|
||||
|
||||
_kill_tree() {
|
||||
local pid="$1" signal="${2:-TERM}"
|
||||
local children child
|
||||
children=$(pgrep -P "$pid" 2>/dev/null || true)
|
||||
for child in $children; do
|
||||
_kill_tree "$child" "$signal"
|
||||
done
|
||||
kill "-$signal" "$pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
_stop_fe_port() {
|
||||
local name="$1"
|
||||
local port="${FE_PORT_NUM[$name]}"
|
||||
local pids pid
|
||||
|
||||
pids="$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
|
||||
if [[ -z "$pids" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "$name 端口 $port 仍被占用,按端口清理监听进程: $pids"
|
||||
for pid in $pids; do
|
||||
_kill_tree "$pid" TERM
|
||||
done
|
||||
|
||||
sleep 1
|
||||
for pid in $pids; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
_kill_tree "$pid" KILL
|
||||
fi
|
||||
done
|
||||
|
||||
pids="$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null || true)"
|
||||
if [[ -n "$pids" ]]; then
|
||||
error "$name 端口 $port 清理失败,仍被占用: $pids"
|
||||
return 1
|
||||
fi
|
||||
|
||||
success "$name 端口 $port 已释放"
|
||||
}
|
||||
|
||||
if [[ "$TARGET" == "all" ]]; then
|
||||
@@ -41,13 +96,20 @@ if [[ "$TARGET" == "all" ]]; then
|
||||
done
|
||||
success "所有前端服务已停止"
|
||||
else
|
||||
for static_fe in "${STATIC_FRONTENDS[@]}"; do
|
||||
if [[ "$TARGET" == "$static_fe" ]]; then
|
||||
warn "$TARGET 已改为静态构建,没有前端开发服务器可停止"
|
||||
echo "如需更新页面,请执行: ./deploy-test/08-build-static-frontend.sh $TARGET"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
local_valid=false
|
||||
for p in "${FE_PROJECTS[@]}"; do
|
||||
[[ "$p" == "$TARGET" ]] && local_valid=true && break
|
||||
done
|
||||
if ! $local_valid; then
|
||||
error "未知项目: $TARGET"
|
||||
echo "可用: ${FE_PROJECTS[*]}"
|
||||
echo "开发态前端可用: ${FE_PROJECTS[*]}"
|
||||
exit 1
|
||||
fi
|
||||
step "停止: $TARGET"
|
||||
|
||||
Reference in New Issue
Block a user