feat(deploy): 移除 ops-web、修复 SDK URL 注入、新增一键升级
核心变更: - 完全移除 ops-web 容器(私有化部署无需运营后台) - nginx sub_filter 替换前端 JS bundle 中的公网 SDK URL - deploy.sh 写入正确的 SDK_IM_WS_URL / SDK_IM_API_URL / SDK_FILE_SERVICE_URL - 新增 scripts/update.sh:热更新脚本,修复配置 + 可选拉镜像 + 重启 + 验证 - 新增 upgrade.sh:一键升级入口,curl 下载后直接执行,流程同 install.sh - install.sh 检测已有部署(.env 存在),自动路由到 update.sh 而非重跑向导 - 关键配置文件(.env / secrets.env / xuqm.env)在 tarball 解压前备份后恢复 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
0c4802b20a
当前提交
a327a262dd
14
README.md
14
README.md
@ -2,6 +2,8 @@
|
||||
|
||||
## 快速开始
|
||||
|
||||
**首次部署:**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh \
|
||||
-o install.sh && bash install.sh
|
||||
@ -9,6 +11,15 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai
|
||||
|
||||
脚本自动完成:依赖检测 → 配置生成 → 镜像拉取 → 容器启动 → 租户初始化(新建或迁移)→ 全量验证。
|
||||
|
||||
**升级已有部署:**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \
|
||||
-o upgrade.sh && bash upgrade.sh
|
||||
```
|
||||
|
||||
升级脚本自动完成:下载最新脚本 → 保留全部数据和配置 → 修复配置问题 → 可选拉取新镜像 → 重启容器 → 全量验证。
|
||||
|
||||
部署完成后根据输出的端口表配置宿主机 nginx,详见 [docs/runbook.md](docs/runbook.md)。
|
||||
|
||||
## 部署架构
|
||||
@ -21,7 +32,6 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai
|
||||
├── tenant-service /api/ /actuator/
|
||||
├── file-service /file/
|
||||
├── tenant-web /(兜底)
|
||||
├── ops-web /ops
|
||||
├── im-service /api/im/ /ws/im
|
||||
├── update-service /api/v1/updates/ /api/v1/rn/
|
||||
├── license-service /api/license/
|
||||
@ -41,7 +51,7 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai
|
||||
|
||||
| Profile | 服务 | 说明 |
|
||||
|---------|------|------|
|
||||
| base | tenant-service, file-service, tenant-web, ops-web, nginx | 必选核心服务(含内置路由 nginx) |
|
||||
| base | tenant-service, file-service, tenant-web, nginx | 必选核心服务(含内置路由 nginx) |
|
||||
| infra-mysql | mysql | 托管数据库 |
|
||||
| infra-redis | redis | 托管缓存 |
|
||||
| im | im-service | IM HTTP + WebSocket |
|
||||
|
||||
@ -83,17 +83,17 @@ server {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# 运营后台
|
||||
location /ops {
|
||||
proxy_pass http://ops-web:80;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# 控制台前端(兜底路由,必须最后)
|
||||
# sub_filter 替换前端 JS bundle 中硬编码的公有平台地址为私有部署地址
|
||||
location / {
|
||||
proxy_pass http://tenant-web:80;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter 'wss://im.dev.xuqinmin.com/ws/im' 'wss://$host/ws/im';
|
||||
sub_filter 'https://im.dev.xuqinmin.com' 'https://$host';
|
||||
sub_filter 'https://dev.xuqinmin.com' 'https://$host';
|
||||
sub_filter_once off;
|
||||
sub_filter_types text/html text/javascript application/javascript;
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,17 +71,6 @@ services:
|
||||
- "127.0.0.1:11226:80"
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 运营后台前端(必须)
|
||||
# 管理员登录界面,nginx 代理 /ops
|
||||
# ---------------------------------------------------------------------------
|
||||
ops-web:
|
||||
image: ${REGISTRY}/ops-web:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
ports:
|
||||
- "127.0.0.1:11227:80"
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 内置路由 nginx(必须)
|
||||
# 统一处理所有内部路由,绑定端口由 .env NGINX_BIND 控制:
|
||||
@ -101,8 +90,6 @@ services:
|
||||
condition: service_started
|
||||
tenant-web:
|
||||
condition: service_started
|
||||
ops-web:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -74,8 +74,7 @@ environment:
|
||||
| 11224 | tenant-service | 9001 | `/api/` `/actuator/` |
|
||||
| 11225 | file-service | 8086 | `/file/` |
|
||||
| 11226 | tenant-web | 80 | `/`(兜底路由) |
|
||||
| 11227 | ops-web | 80 | `/ops` |
|
||||
| 11228 | im-service | 8082 | `/api/im/` `/ws/im` |
|
||||
| 11227 | im-service | 8082 | `/api/im/` `/ws/im` |
|
||||
| 11229 | update-service | 8084 | `/api/v1/updates/` `/api/v1/rn/` |
|
||||
| 11230 | license-service | 8085 | `/api/license/` |
|
||||
| 11231 | push-service | 8083 | 厂商回调(按需) |
|
||||
|
||||
@ -43,8 +43,7 @@
|
||||
| 11224 | tenant-service | 9001 |
|
||||
| 11225 | file-service | 8086 |
|
||||
| 11226 | tenant-web | 80 |
|
||||
| 11227 | ops-web | 80 |
|
||||
| 11228 | im-service | 8082 |
|
||||
| 11227 | im-service | 8082 |
|
||||
| 11229 | update-service | 8084 |
|
||||
| 11230 | license-service | 8085 |
|
||||
| 11231 | push-service | 8083 |
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
{"name": "tenant-service", "required": true},
|
||||
{"name": "file-service", "required": true},
|
||||
{"name": "tenant-web", "required": true},
|
||||
{"name": "ops-web", "required": true},
|
||||
{"name": "docs-site", "required": true},
|
||||
{"name": "im-service", "required": false, "profile": "im"},
|
||||
{"name": "push-service", "required": false, "profile": "push"},
|
||||
|
||||
73
install.sh
73
install.sh
@ -1,11 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# XuqmGroup 私有化部署 — 一键安装脚本
|
||||
# XuqmGroup 私有化部署 — 一键安装脚本(已部署环境重新执行将自动升级)
|
||||
#
|
||||
# 用法一(推荐,先下载再审查):
|
||||
# 首次部署:
|
||||
# curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh \
|
||||
# -o install.sh && bash install.sh
|
||||
#
|
||||
# 用法二(直接执行,保留终端交互):
|
||||
# 升级已有部署(推荐使用专用升级脚本):
|
||||
# curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \
|
||||
# -o upgrade.sh && bash upgrade.sh
|
||||
#
|
||||
# 或直接执行(bash <(curl ...) 形式保留终端交互):
|
||||
# bash <(curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh)
|
||||
#
|
||||
# 环境变量(可选):
|
||||
@ -169,17 +173,26 @@ printf '\n'
|
||||
info "下载部署包 ${ARCHIVE_URL} ..."
|
||||
|
||||
TMP_PKG="$(mktemp /tmp/xuqm-deploy-XXXXXX.tar.gz)"
|
||||
trap 'rm -f "$TMP_PKG"' EXIT
|
||||
_BACKUP_DIR="$(mktemp -d /tmp/xuqm-backup-XXXXXX)"
|
||||
trap 'rm -f "$TMP_PKG"; rm -rf "$_BACKUP_DIR"' EXIT
|
||||
|
||||
curl -fsSL --progress-bar "$ARCHIVE_URL" -o "$TMP_PKG" \
|
||||
|| fail "部署包下载失败,请检查网络或 Gitea 是否可达: $ARCHIVE_URL"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 7 — 解压到安装目录
|
||||
# Step 7 — 解压到安装目录(更新时保留关键配置)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/docker-compose.yml" ]; then
|
||||
warn "安装目录已存在部署文件: $INSTALL_DIR"
|
||||
printf ' 将覆盖脚本和配置模板(数据目录 data/ 不受影响)\n'
|
||||
|
||||
# 检测是否为已有部署
|
||||
_IS_UPDATE=0
|
||||
[ -f "$INSTALL_DIR/.env" ] && [ -f "$INSTALL_DIR/config/secrets.env" ] && _IS_UPDATE=1
|
||||
|
||||
# 备份关键配置,防止 tarball 中同名文件意外覆盖
|
||||
if [ "$_IS_UPDATE" -eq 1 ]; then
|
||||
for _cf in .env config/secrets.env config/xuqm.env; do
|
||||
[ -f "$INSTALL_DIR/$_cf" ] && \
|
||||
cp "$INSTALL_DIR/$_cf" "$_BACKUP_DIR/$(basename "$_cf").bak"
|
||||
done
|
||||
fi
|
||||
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
@ -187,11 +200,45 @@ tar -xzf "$TMP_PKG" -C "$INSTALL_DIR" --strip-components=1 --exclude='*/data'
|
||||
chmod +x "$INSTALL_DIR/scripts/"*.sh "$INSTALL_DIR/install.sh" 2>/dev/null || true
|
||||
ok "部署包解压完成: $INSTALL_DIR"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 8 — 启动交互式部署
|
||||
# ---------------------------------------------------------------------------
|
||||
printf '\n%b 依赖安装完毕,即将进入交互式部署向导 ...%b\n\n' "$GREEN" "$RESET"
|
||||
# 恢复关键配置文件(确保现有配置不被模板覆盖)
|
||||
if [ "$_IS_UPDATE" -eq 1 ]; then
|
||||
for _cf in .env config/secrets.env config/xuqm.env; do
|
||||
_bak="$_BACKUP_DIR/$(basename "$_cf").bak"
|
||||
[ -f "$_bak" ] && cp "$_bak" "$INSTALL_DIR/$_cf"
|
||||
done
|
||||
ok "已恢复现有配置文件"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 8 — 路由到更新或全量安装
|
||||
# ---------------------------------------------------------------------------
|
||||
export DEPLOY_HOST
|
||||
cd "$INSTALL_DIR"
|
||||
exec bash scripts/deploy.sh
|
||||
|
||||
if [ "$_IS_UPDATE" -eq 1 ]; then
|
||||
printf '\n'
|
||||
printf '%b 检测到已有部署(%s/.env 存在)%b\n' "$YELLOW" "$INSTALL_DIR" "$RESET"
|
||||
printf ' 请选择操作:\n\n'
|
||||
printf ' %b1%b 仅更新 — 修复配置问题,可选拉取新镜像,重启容器(保留全部数据)\n' "$BOLD" "$RESET"
|
||||
printf ' %b2%b 全量重部署 — 重新运行完整安装向导(会覆盖现有配置,谨慎使用)\n' "$BOLD" "$RESET"
|
||||
printf ' %b3%b 退出\n\n' "$BOLD" "$RESET"
|
||||
read -rp " 请输入 [1/2/3](回车默认 1): " _choice
|
||||
_choice="${_choice:-1}"
|
||||
case "$_choice" in
|
||||
2)
|
||||
printf '\n%b → 进入全量部署向导 ...%b\n\n' "$GREEN" "$RESET"
|
||||
exec bash scripts/deploy.sh
|
||||
;;
|
||||
3)
|
||||
printf ' 已退出\n'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
printf '\n%b → 进入更新流程 ...%b\n\n' "$GREEN" "$RESET"
|
||||
exec bash scripts/update.sh
|
||||
;;
|
||||
esac
|
||||
else
|
||||
printf '\n%b 依赖安装完毕,即将进入交互式部署向导 ...%b\n\n' "$GREEN" "$RESET"
|
||||
exec bash scripts/deploy.sh
|
||||
fi
|
||||
|
||||
@ -293,7 +293,6 @@ REDIS_DATABASE=0
|
||||
|
||||
# 访问域名(IP 部署时直接使用 IP,无需配置域名)
|
||||
CONSOLE_DOMAIN=${CONSOLE_BASE}
|
||||
OPS_DOMAIN=${CONSOLE_BASE}
|
||||
DOCS_DOMAIN=${CONSOLE_BASE}/docs
|
||||
FILE_DOMAIN=${CONSOLE_BASE}
|
||||
IM_DOMAIN=${CONSOLE_BASE}
|
||||
@ -355,7 +354,6 @@ REDIS_DATABASE=0
|
||||
|
||||
# 访问域名(IP 部署时直接使用 IP)
|
||||
CONSOLE_DOMAIN=${CONSOLE_BASE}
|
||||
OPS_DOMAIN=${CONSOLE_BASE}
|
||||
DOCS_DOMAIN=${CONSOLE_BASE}/docs
|
||||
FILE_DOMAIN=${CONSOLE_BASE}
|
||||
IM_DOMAIN=${CONSOLE_BASE}
|
||||
@ -368,8 +366,7 @@ SDK_FILE_SERVICE_URL=${CONSOLE_BASE}
|
||||
SDK_IM_API_URL=${CONSOLE_BASE}
|
||||
SDK_IM_WS_URL=${_WS_SCHEME}://${DEPLOY_HOST}/ws/im
|
||||
|
||||
# 系统 IM 通信应用 key(私有化服务间消息通知使用此 app_key 连接 IM 服务)
|
||||
# 与公有化平台 xuqinmin12 租户下的平台系统应用 key 保持一致
|
||||
# 系统 IM 通信应用 key(私有化服务间消息通知使用)
|
||||
SYSTEM_APP_KEY=ak_409e217e4aa14254ad73ad3c
|
||||
EOF
|
||||
ok "config/xuqm.env 已写入"
|
||||
|
||||
@ -44,7 +44,7 @@ pull_and_save() {
|
||||
}
|
||||
|
||||
# Always include base images
|
||||
for svc in tenant-service file-service tenant-web ops-web docs-site; do
|
||||
for svc in tenant-service file-service tenant-web docs-site; do
|
||||
pull_and_save "$svc"
|
||||
done
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ else
|
||||
fi
|
||||
|
||||
# Container states — docs-site is optional (image may not exist in all registries)
|
||||
for svc in tenant-service file-service tenant-web ops-web nginx; do
|
||||
for svc in tenant-service file-service tenant-web nginx; do
|
||||
STATE="$(compose ps -q "$svc" 2>/dev/null | head -1 || true)"
|
||||
if [ -n "$STATE" ]; then
|
||||
STATUS="$(docker inspect --format='{{.State.Status}}' "$STATE" 2>/dev/null || echo 'unknown')"
|
||||
|
||||
281
scripts/update.sh
可执行文件
281
scripts/update.sh
可执行文件
@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env bash
|
||||
# update.sh — XuqmGroup 私有化部署热更新脚本
|
||||
#
|
||||
# 用途:在已部署的环境上执行,无需重新配置基础信息:
|
||||
# 1. 自动读取现有配置(.env / config/secrets.env)
|
||||
# 2. 修复已知配置问题(SDK 地址、残留公网 URL 等)
|
||||
# 3. 可选:拉取最新镜像
|
||||
# 4. 重启受影响的容器
|
||||
# 5. 重新运行全量验证
|
||||
#
|
||||
# 前提:已执行过 install.sh 完成初始部署
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 自动定位安装目录:优先脚本所在目录的上级,否则搜索常见路径
|
||||
_script_parent="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
if [ -f "$_script_parent/docker-compose.yml" ]; then
|
||||
ROOT_DIR="$_script_parent"
|
||||
else
|
||||
ROOT_DIR=""
|
||||
for _d in /opt/xuqm-private /opt/xuqm /root/xuqm-private; do
|
||||
[ -f "$_d/docker-compose.yml" ] && ROOT_DIR="$_d" && break
|
||||
done
|
||||
if [ -z "$ROOT_DIR" ]; then
|
||||
read -rp " 请输入部署目录路径(如 /opt/xuqm-private): " ROOT_DIR
|
||||
[ -f "$ROOT_DIR/docker-compose.yml" ] || \
|
||||
{ printf "\nERROR: %s 下未找到 docker-compose.yml\n" "$ROOT_DIR" >&2; exit 1; }
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 工具函数
|
||||
# ---------------------------------------------------------------------------
|
||||
BOLD='\033[1m'; RESET='\033[0m'
|
||||
CYAN='\033[1;36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[1;31m'
|
||||
|
||||
log() { printf "\n${CYAN}[update] %s${RESET}\n" "$*"; }
|
||||
ok() { printf " ${GREEN}✓${RESET} %s\n" "$*"; }
|
||||
warn() { printf " ${YELLOW}⚠${RESET} %s\n" "$*"; }
|
||||
fail() { printf "\n${RED}ERROR: %s${RESET}\n" "$*" >&2; exit 1; }
|
||||
info() { printf " → %s\n" "$*"; }
|
||||
|
||||
# 修改 env 文件中某个 key 的值;若 key 不存在则追加
|
||||
_set_env() {
|
||||
local file="$1" key="$2" val="$3"
|
||||
local tmp; tmp="$(mktemp)"
|
||||
if grep -q "^${key}=" "$file" 2>/dev/null; then
|
||||
# 用 python3 替换,避免 sed 在 URL 值中的斜杠转义问题
|
||||
python3 - "$file" "$key" "$val" <<'PY'
|
||||
import sys, re
|
||||
path, key, val = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
content = open(path).read()
|
||||
new = re.sub(r'^' + re.escape(key) + r'=.*$', key + '=' + val, content, flags=re.MULTILINE)
|
||||
open(path, 'w').write(new)
|
||||
PY
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$val" >> "$file"
|
||||
fi
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 前置检查
|
||||
# ---------------------------------------------------------------------------
|
||||
printf "\n${BOLD}══════════════════════════════════════════════════${RESET}\n"
|
||||
printf "${BOLD} XuqmGroup 私有化部署热更新${RESET}\n"
|
||||
printf "${BOLD}══════════════════════════════════════════════════${RESET}\n"
|
||||
printf " 部署目录: %s\n\n" "$ROOT_DIR"
|
||||
|
||||
[ -f "$ROOT_DIR/.env" ] || fail "未找到 .env,请先执行 install.sh 完成初始部署"
|
||||
[ -f "$ROOT_DIR/config/xuqm.env" ] || fail "未找到 config/xuqm.env,请先执行 install.sh 完成初始部署"
|
||||
[ -f "$ROOT_DIR/config/secrets.env" ] || fail "未找到 config/secrets.env,请先执行 install.sh 完成初始部署"
|
||||
|
||||
command -v docker >/dev/null 2>&1 || fail "Docker 未安装"
|
||||
command -v python3 >/dev/null 2>&1 || fail "python3 未安装"
|
||||
docker info >/dev/null 2>&1 || fail "Docker daemon 未运行"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 加载现有配置
|
||||
# ---------------------------------------------------------------------------
|
||||
log "加载现有配置"
|
||||
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
set -a
|
||||
. "$ROOT_DIR/.env"
|
||||
. "$ROOT_DIR/config/secrets.env"
|
||||
set +a
|
||||
|
||||
# 从 config/xuqm.env 中读取 CONSOLE_DOMAIN(不用 source,避免覆盖已加载值)
|
||||
_CONSOLE_DOMAIN="$(grep '^CONSOLE_DOMAIN=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")"
|
||||
_NGINX_BIND="${NGINX_BIND:-80}"
|
||||
_NGINX_PORT="${_NGINX_BIND##*:}"
|
||||
|
||||
ok "CONSOLE_DOMAIN=${_CONSOLE_DOMAIN:-(未设置)}"
|
||||
ok "NGINX_BIND=${_NGINX_BIND}"
|
||||
|
||||
[ -n "$_CONSOLE_DOMAIN" ] || fail "config/xuqm.env 中 CONSOLE_DOMAIN 未设置,请手动补充后重试"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. 修复配置问题
|
||||
# ---------------------------------------------------------------------------
|
||||
log "检查并修复配置"
|
||||
|
||||
# 根据 CONSOLE_DOMAIN 派生正确的 SDK 地址
|
||||
if printf '%s' "$_CONSOLE_DOMAIN" | grep -q '^https://'; then
|
||||
_WS_SCHEME="wss"
|
||||
else
|
||||
_WS_SCHEME="ws"
|
||||
fi
|
||||
_DEPLOY_HOST="$(printf '%s' "$_CONSOLE_DOMAIN" | sed 's|https\?://||')"
|
||||
_SDK_IM_WS_URL="${_WS_SCHEME}://${_DEPLOY_HOST}/ws/im"
|
||||
_SDK_IM_API_URL="${_CONSOLE_DOMAIN}"
|
||||
_SDK_FILE_URL="${_CONSOLE_DOMAIN}"
|
||||
|
||||
_FIXED=0
|
||||
|
||||
# 检查 SDK_IM_WS_URL
|
||||
_CURRENT_WS="$(grep '^SDK_IM_WS_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')"
|
||||
if [ -z "$_CURRENT_WS" ] || printf '%s' "$_CURRENT_WS" | grep -qi 'xuqinmin\.com'; then
|
||||
_set_env "$ROOT_DIR/config/xuqm.env" "SDK_IM_WS_URL" "$_SDK_IM_WS_URL"
|
||||
ok "SDK_IM_WS_URL 已更新 → ${_SDK_IM_WS_URL}"
|
||||
_FIXED=1
|
||||
else
|
||||
ok "SDK_IM_WS_URL 正常: ${_CURRENT_WS}"
|
||||
fi
|
||||
|
||||
# 检查 SDK_IM_API_URL
|
||||
_CURRENT_IM_API="$(grep '^SDK_IM_API_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')"
|
||||
if [ -z "$_CURRENT_IM_API" ] || printf '%s' "$_CURRENT_IM_API" | grep -qi 'xuqinmin\.com'; then
|
||||
_set_env "$ROOT_DIR/config/xuqm.env" "SDK_IM_API_URL" "$_SDK_IM_API_URL"
|
||||
ok "SDK_IM_API_URL 已更新 → ${_SDK_IM_API_URL}"
|
||||
_FIXED=1
|
||||
else
|
||||
ok "SDK_IM_API_URL 正常: ${_CURRENT_IM_API}"
|
||||
fi
|
||||
|
||||
# 检查 SDK_FILE_SERVICE_URL
|
||||
_CURRENT_FILE="$(grep '^SDK_FILE_SERVICE_URL=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null | cut -d= -f2- || echo '')"
|
||||
if [ -z "$_CURRENT_FILE" ] || printf '%s' "$_CURRENT_FILE" | grep -qi 'xuqinmin\.com'; then
|
||||
_set_env "$ROOT_DIR/config/xuqm.env" "SDK_FILE_SERVICE_URL" "$_SDK_FILE_URL"
|
||||
ok "SDK_FILE_SERVICE_URL 已更新 → ${_SDK_FILE_URL}"
|
||||
_FIXED=1
|
||||
else
|
||||
ok "SDK_FILE_SERVICE_URL 正常: ${_CURRENT_FILE}"
|
||||
fi
|
||||
|
||||
# 检查 .env 中是否有残留 OPS_DOMAIN
|
||||
if grep -q '^OPS_DOMAIN=' "$ROOT_DIR/.env" 2>/dev/null; then
|
||||
python3 - "$ROOT_DIR/.env" <<'PY'
|
||||
import sys, re
|
||||
path = sys.argv[1]
|
||||
content = open(path).read()
|
||||
content = re.sub(r'^OPS_DOMAIN=.*\n?', '', content, flags=re.MULTILINE)
|
||||
open(path, 'w').write(content)
|
||||
PY
|
||||
ok ".env 中已清理 OPS_DOMAIN"
|
||||
_FIXED=1
|
||||
fi
|
||||
|
||||
# 扫描 config/xuqm.env 是否还有其它公网地址残留
|
||||
if grep -qi 'xuqinmin\.com' "$ROOT_DIR/config/xuqm.env" 2>/dev/null; then
|
||||
warn "config/xuqm.env 中仍有 xuqinmin.com 字样,请人工核查:"
|
||||
grep -n 'xuqinmin\.com' "$ROOT_DIR/config/xuqm.env" | while IFS= read -r line; do
|
||||
printf ' %s\n' "$line"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ "$_FIXED" -eq 0 ]; then
|
||||
ok "配置无需修复"
|
||||
fi
|
||||
|
||||
# 同步更新 config/sdk/xuqm-private-sdk.json(SDK 初始化配置文件)
|
||||
if [ -f "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" ]; then
|
||||
python3 - "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" \
|
||||
"$_CONSOLE_DOMAIN" "$_SDK_IM_WS_URL" <<'PY'
|
||||
import json, sys
|
||||
path, console, ws = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
try:
|
||||
d = json.load(open(path))
|
||||
d['controlBaseUrl'] = console
|
||||
d['fileBaseUrl'] = console
|
||||
d['imApiBaseUrl'] = console
|
||||
d['imWsUrl'] = ws
|
||||
json.dump(d, open(path, 'w'), ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f'warning: {e}', file=sys.stderr)
|
||||
PY
|
||||
ok "config/sdk/xuqm-private-sdk.json 已同步更新"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. 可选:拉取最新镜像
|
||||
# ---------------------------------------------------------------------------
|
||||
log "拉取最新镜像(可选)"
|
||||
|
||||
read -rp " 是否拉取最新镜像?(y/N): " _pull_choice
|
||||
if [ "${_pull_choice:-n}" = "y" ] || [ "${_pull_choice:-n}" = "Y" ]; then
|
||||
_REGISTRY="${REGISTRY:-}"
|
||||
_REGISTRY_HOST="${REGISTRY_HOST:-}"
|
||||
_REGISTRY_USER="${REGISTRY_USER:-}"
|
||||
_REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-}"
|
||||
|
||||
if [ -n "$_REGISTRY_PASSWORD" ] && [ -n "$_REGISTRY_HOST" ]; then
|
||||
printf '%s' "$_REGISTRY_PASSWORD" | \
|
||||
docker login "$_REGISTRY_HOST" -u "$_REGISTRY_USER" --password-stdin 2>/dev/null \
|
||||
&& ok "镜像仓库登录成功" \
|
||||
|| warn "镜像仓库登录失败,将使用本地缓存镜像"
|
||||
fi
|
||||
|
||||
docker compose \
|
||||
--env-file "$ROOT_DIR/.env" \
|
||||
-f "$ROOT_DIR/docker-compose.yml" \
|
||||
-f "$ROOT_DIR/docker-compose.infra.yml" \
|
||||
pull --ignore-pull-failures 2>/dev/null \
|
||||
&& ok "镜像已拉取" \
|
||||
|| warn "部分镜像拉取失败,继续使用本地缓存"
|
||||
else
|
||||
info "跳过镜像拉取"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. 重启受影响的容器
|
||||
# ---------------------------------------------------------------------------
|
||||
log "重启受影响的容器"
|
||||
|
||||
# 加载 secrets 到环境(docker compose 需要)
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
. "$ROOT_DIR/config/secrets.env"
|
||||
set +a
|
||||
|
||||
_COMPOSE="docker compose --env-file ${ROOT_DIR}/.env -f ${ROOT_DIR}/docker-compose.yml -f ${ROOT_DIR}/docker-compose.infra.yml"
|
||||
|
||||
# tenant-service:读取 env_file(包含 SDK_IM_WS_URL),必须重启
|
||||
info "重启 tenant-service ..."
|
||||
$_COMPOSE restart tenant-service
|
||||
ok "tenant-service 已重启"
|
||||
|
||||
# nginx:挂载 config/nginx/conf.d(可能有 sub_filter 更新),重启生效
|
||||
info "重启 nginx ..."
|
||||
$_COMPOSE restart nginx
|
||||
ok "nginx 已重启"
|
||||
|
||||
# 等待 tenant-service 健康
|
||||
printf ' 等待 tenant-service 就绪'
|
||||
for i in $(seq 1 30); do
|
||||
code="$(curl -skL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 4 \
|
||||
"http://127.0.0.1:11224/actuator/health" 2>/dev/null || echo 000)"
|
||||
if [ "$code" = "200" ]; then
|
||||
printf '\n'
|
||||
ok "tenant-service 健康 (HTTP 200)"
|
||||
break
|
||||
fi
|
||||
printf '.'
|
||||
sleep 3
|
||||
[ "$i" -eq 30 ] && {
|
||||
printf '\n'
|
||||
warn "tenant-service 未在 90s 内响应,请手动检查:docker compose logs --tail 50 tenant-service"
|
||||
break
|
||||
}
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. 全量验证
|
||||
# ---------------------------------------------------------------------------
|
||||
log "运行全量验证"
|
||||
|
||||
if BASE_URL="http://127.0.0.1:${_NGINX_PORT}" bash "$ROOT_DIR/scripts/verify.sh"; then
|
||||
ok "全量验证通过"
|
||||
else
|
||||
warn "部分验证项未通过,请查看上方输出"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 完成
|
||||
# ---------------------------------------------------------------------------
|
||||
printf "\n${BOLD}══════════════════════════════════════════════════${RESET}\n"
|
||||
printf "${BOLD} 热更新完成${RESET}\n"
|
||||
printf "${BOLD}══════════════════════════════════════════════════${RESET}\n"
|
||||
printf "\n 访问地址:${BOLD}%s${RESET}\n" "$_CONSOLE_DOMAIN"
|
||||
printf " SDK IM WS:${BOLD}%s${RESET}\n\n" "$_SDK_IM_WS_URL"
|
||||
@ -133,7 +133,7 @@ printf " 验证时间: %s\n" "$(date '+%Y-%m-%d %H:%M:%S')"
|
||||
# ---------------------------------------------------------------------------
|
||||
section "1. 容器状态"
|
||||
|
||||
REQUIRED_CONTAINERS="tenant-service nginx tenant-web ops-web"
|
||||
REQUIRED_CONTAINERS="tenant-service nginx tenant-web"
|
||||
for CTR_SUFFIX in $REQUIRED_CONTAINERS; do
|
||||
CTR_NAME=$(docker ps --filter "name=$CTR_SUFFIX" --filter "status=running" --format '{{.Names}}' 2>/dev/null | head -1)
|
||||
if [ -n "$CTR_NAME" ]; then
|
||||
@ -234,13 +234,6 @@ else
|
||||
fail "控制台前端不可访问 (HTTP $WEB_CODE)"
|
||||
fi
|
||||
|
||||
OPS_CODE=$(http_get "$BASE_URL/ops")
|
||||
if echo "$OPS_CODE" | grep -qE '^(200|301|302)$'; then
|
||||
pass "运营后台可访问 (HTTP $OPS_CODE)"
|
||||
else
|
||||
fail "运营后台不可访问 (HTTP $OPS_CODE)"
|
||||
fi
|
||||
|
||||
DOCS_CODE=$(http_get "$BASE_URL/docs/")
|
||||
if echo "$DOCS_CODE" | grep -qE '^(200|301|302)$'; then
|
||||
pass "文档站可访问 (HTTP $DOCS_CODE)"
|
||||
@ -422,7 +415,6 @@ else
|
||||
fi
|
||||
|
||||
printf "\n 访问地址:${BOLD}%s${RESET}\n" "$BASE_URL"
|
||||
printf " 运营后台:${BOLD}%s/ops${RESET}\n" "$BASE_URL"
|
||||
printf " 文档站 :${BOLD}%s/docs/${RESET}\n" "$BASE_URL"
|
||||
printf "\n"
|
||||
|
||||
|
||||
127
upgrade.sh
普通文件
127
upgrade.sh
普通文件
@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# upgrade.sh — XuqmGroup 私有化部署一键升级脚本
|
||||
#
|
||||
# 用法一(推荐,先下载再审查):
|
||||
# curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \
|
||||
# -o upgrade.sh && bash upgrade.sh
|
||||
#
|
||||
# 用法二(直接执行,保留终端交互):
|
||||
# bash <(curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh)
|
||||
#
|
||||
# 环境变量(可选):
|
||||
# INSTALL_DIR 安装目录(默认自动检测,优先 /opt/xuqm-private)
|
||||
# XUQM_BRANCH Gitea 分支(默认 main)
|
||||
#
|
||||
# 升级内容:
|
||||
# 1. 下载最新部署包(更新脚本、配置模板、docker-compose 文件)
|
||||
# 2. 保留全部数据目录和现有配置(.env / secrets.env / xuqm.env 不被覆盖)
|
||||
# 3. 修复已知配置问题(SDK 地址残留公网 URL 等)
|
||||
# 4. 可选拉取最新镜像
|
||||
# 5. 重启受影响容器并运行全量验证
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 若 stdin 不是终端(curl | bash),强制从 /dev/tty 读取用户输入
|
||||
if [ ! -t 0 ]; then
|
||||
exec bash "$0" "$@" </dev/tty
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 配置
|
||||
# ---------------------------------------------------------------------------
|
||||
GITEA_BASE="https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy"
|
||||
XUQM_BRANCH="${XUQM_BRANCH:-main}"
|
||||
ARCHIVE_URL="${GITEA_BASE}/archive/${XUQM_BRANCH}.tar.gz"
|
||||
INSTALL_DIR="${INSTALL_DIR:-/opt/xuqm-private}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 工具函数
|
||||
# ---------------------------------------------------------------------------
|
||||
BOLD='\033[1m'; RESET='\033[0m'
|
||||
CYAN='\033[1;36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[1;31m'
|
||||
|
||||
info() { printf "${CYAN} →${RESET} %s\n" "$*"; }
|
||||
ok() { printf "${GREEN} ✓${RESET} %s\n" "$*"; }
|
||||
warn() { printf "${YELLOW} ⚠${RESET} %s\n" "$*"; }
|
||||
fail() { printf "${RED}\nERROR: %s${RESET}\n" "$*" >&2; exit 1; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Banner
|
||||
# ---------------------------------------------------------------------------
|
||||
printf '\n%b══════════════════════════════════════════════════%b\n' "$CYAN" "$RESET"
|
||||
printf '%b XuqmGroup 私有化部署 — 一键升级%b\n' "$BOLD" "$RESET"
|
||||
printf '%b══════════════════════════════════════════════════%b\n\n' "$CYAN" "$RESET"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 检测安装目录
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
||||
for _d in /opt/xuqm /root/xuqm-private; do
|
||||
if [ -f "$_d/.env" ] && [ -f "$_d/docker-compose.yml" ]; then
|
||||
INSTALL_DIR="$_d"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ! -f "$INSTALL_DIR/.env" ]; then
|
||||
read -rp " 请输入部署目录路径(如 /opt/xuqm-private): " INSTALL_DIR
|
||||
[ -f "$INSTALL_DIR/docker-compose.yml" ] || \
|
||||
fail "$INSTALL_DIR 下未找到 docker-compose.yml,请先执行 install.sh 完成初始部署"
|
||||
[ -f "$INSTALL_DIR/.env" ] || \
|
||||
fail "$INSTALL_DIR 下未找到 .env,请先执行 install.sh 完成初始部署"
|
||||
fi
|
||||
|
||||
printf ' 安装目录: %b%s%b\n' "$BOLD" "$INSTALL_DIR" "$RESET"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 前置检查
|
||||
# ---------------------------------------------------------------------------
|
||||
command -v docker >/dev/null 2>&1 || fail "Docker 未安装"
|
||||
command -v python3 >/dev/null 2>&1 || fail "python3 未安装"
|
||||
docker info >/dev/null 2>&1 || fail "Docker daemon 未运行,请执行: systemctl start docker"
|
||||
|
||||
[ -f "$INSTALL_DIR/config/secrets.env" ] || fail "未找到 config/secrets.env,请先执行 install.sh 完成初始部署"
|
||||
[ -f "$INSTALL_DIR/config/xuqm.env" ] || fail "未找到 config/xuqm.env,请先执行 install.sh 完成初始部署"
|
||||
|
||||
ok "前置检查通过"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 下载最新部署包
|
||||
# ---------------------------------------------------------------------------
|
||||
printf '\n'
|
||||
info "下载最新版本 ${ARCHIVE_URL} ..."
|
||||
|
||||
TMP_PKG="$(mktemp /tmp/xuqm-upgrade-XXXXXX.tar.gz)"
|
||||
_BACKUP_DIR="$(mktemp -d /tmp/xuqm-backup-XXXXXX)"
|
||||
trap 'rm -f "$TMP_PKG"; rm -rf "$_BACKUP_DIR"' EXIT
|
||||
|
||||
curl -fsSL --progress-bar "$ARCHIVE_URL" -o "$TMP_PKG" \
|
||||
|| fail "下载失败,请检查网络或 Gitea 是否可达: $ARCHIVE_URL"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 备份关键配置 → 解压 → 恢复
|
||||
# ---------------------------------------------------------------------------
|
||||
for _cf in .env config/secrets.env config/xuqm.env; do
|
||||
[ -f "$INSTALL_DIR/$_cf" ] && \
|
||||
cp "$INSTALL_DIR/$_cf" "$_BACKUP_DIR/$(basename "$_cf").bak"
|
||||
done
|
||||
|
||||
tar -xzf "$TMP_PKG" -C "$INSTALL_DIR" --strip-components=1 --exclude='*/data'
|
||||
chmod +x "$INSTALL_DIR/scripts/"*.sh \
|
||||
"$INSTALL_DIR/install.sh" \
|
||||
"$INSTALL_DIR/upgrade.sh" 2>/dev/null || true
|
||||
ok "脚本和配置模板已更新"
|
||||
|
||||
for _cf in .env config/secrets.env config/xuqm.env; do
|
||||
_bak="$_BACKUP_DIR/$(basename "$_cf").bak"
|
||||
[ -f "$_bak" ] && cp "$_bak" "$INSTALL_DIR/$_cf"
|
||||
done
|
||||
ok "已恢复现有配置文件"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 执行升级(修复配置 + 重启 + 验证)
|
||||
# ---------------------------------------------------------------------------
|
||||
printf '\n%b → 进入升级流程 ...%b\n\n' "$GREEN" "$RESET"
|
||||
cd "$INSTALL_DIR"
|
||||
exec bash scripts/update.sh
|
||||
正在加载...
在新工单中引用
屏蔽一个用户