diff --git a/README.md b/README.md index dd6a184..86569fe 100644 --- a/README.md +++ b/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 | diff --git a/VERSION b/VERSION index 1ef8378..e5a635c 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1 @@ -2026.05.18-private.1 - +2026.05.20-private.1 diff --git a/config/nginx/conf.d/xuqm.conf b/config/nginx/conf.d/xuqm.conf index 0fdbf58..827dd9c 100644 --- a/config/nginx/conf.d/xuqm.conf +++ b/config/nginx/conf.d/xuqm.conf @@ -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; } } diff --git a/docker-compose.yml b/docker-compose.yml index 245b391..89ad789 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 # --------------------------------------------------------------------------- diff --git a/docs/configuration.md b/docs/configuration.md index cd6d9b1..ff33155 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 | 厂商回调(按需) | diff --git a/docs/deployment-defaults.md b/docs/deployment-defaults.md index b6f546e..f72101b 100644 --- a/docs/deployment-defaults.md +++ b/docs/deployment-defaults.md @@ -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 | diff --git a/image-manifest.json b/image-manifest.json index 5ba0891..4aeb5d0 100644 --- a/image-manifest.json +++ b/image-manifest.json @@ -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"}, diff --git a/install.sh b/install.sh index 787f8c9..d8bfe49 100755 --- a/install.sh +++ b/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 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 22bbd7a..261139a 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -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 已写入" diff --git a/scripts/export-offline-bundle.sh b/scripts/export-offline-bundle.sh index ac254a4..bb94fdf 100755 --- a/scripts/export-offline-bundle.sh +++ b/scripts/export-offline-bundle.sh @@ -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 diff --git a/scripts/healthcheck.sh b/scripts/healthcheck.sh index 9536053..b972c83 100755 --- a/scripts/healthcheck.sh +++ b/scripts/healthcheck.sh @@ -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')" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..da1ed32 --- /dev/null +++ b/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" diff --git a/scripts/verify.sh b/scripts/verify.sh index 72e456c..65110b6 100644 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -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" diff --git a/upgrade.sh b/upgrade.sh new file mode 100644 index 0000000..4796110 --- /dev/null +++ b/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" "$@" &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