diff --git a/docker-compose.yml b/docker-compose.yml index 48d0600..3db5228 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,8 @@ services: tenant-service: image: ${REGISTRY}/tenant-service:${IMAGE_TAG} profiles: ["base"] + ports: + - "9001:9001" env_file: - ./config/xuqm.env # 业务配置:运行模式、域名、功能开关 - ./config/secrets.env # 敏感配置:密码、Token @@ -40,6 +42,8 @@ services: file-service: image: ${REGISTRY}/file-service:${IMAGE_TAG} profiles: ["base"] + ports: + - "8086:8086" env_file: - ./config/xuqm.env - ./config/secrets.env @@ -63,6 +67,8 @@ services: tenant-web: image: ${REGISTRY}/tenant-web:${IMAGE_TAG} profiles: ["base"] + ports: + - "8080:80" restart: unless-stopped # --------------------------------------------------------------------------- @@ -72,19 +78,21 @@ services: ops-web: image: ${REGISTRY}/ops-web:${IMAGE_TAG} profiles: ["base"] + ports: + - "8081:80" restart: unless-stopped # --------------------------------------------------------------------------- - # Nginx 反向代理(必须) - # 统一入口:端口 80(HTTP)和 443(HTTPS) - # 路由所有请求到各后端容器 + # Nginx 反向代理(可选,profile: nginx-bundled) + # 默认不启动 — 用户通常用宿主机自己的 nginx 代理到各服务端口。 + # 需要内置 nginx 时:COMPOSE_PROFILES=...,nginx-bundled # --------------------------------------------------------------------------- nginx: image: nginx:1.27-alpine - profiles: ["base"] + profiles: ["nginx-bundled"] ports: - - "80:80" # HTTP - - "443:443" # HTTPS(需要配置证书,见 docs/runbook.md) + - "80:80" + - "443:443" volumes: - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./config/nginx/conf.d:/etc/nginx/conf.d:ro @@ -105,6 +113,8 @@ services: im-service: image: ${REGISTRY}/im-service:${IMAGE_TAG} profiles: ["im"] + ports: + - "8082:8082" env_file: - ./config/xuqm.env - ./config/secrets.env @@ -128,6 +138,8 @@ services: push-service: image: ${REGISTRY}/push-service:${IMAGE_TAG} profiles: ["push"] + ports: + - "8083:8083" env_file: - ./config/xuqm.env - ./config/secrets.env @@ -146,6 +158,8 @@ services: update-service: image: ${REGISTRY}/update-service:${IMAGE_TAG} profiles: ["update"] + ports: + - "8084:8084" env_file: - ./config/xuqm.env - ./config/secrets.env @@ -167,6 +181,8 @@ services: license-service: image: ${REGISTRY}/license-service:${IMAGE_TAG} profiles: ["license"] + ports: + - "8085:8085" env_file: - ./config/xuqm.env - ./config/secrets.env diff --git a/scripts/deploy.sh b/scripts/deploy.sh index b0eee54..41f26bb 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -40,14 +40,10 @@ REGISTRY_USER="xuqinmin12" REGISTRY_PASSWORD="${REGISTRY_PASSWORD:-xuqinmin1022}" IMAGE_TAG="latest" -# 部署主机(用于健康检查 HTTP 请求) -# 可以是 IP 地址,无需域名,局域网内即可完整使用所有服务 +# 部署主机(用于健康检查 HTTP 请求,直接打服务端口) DEPLOY_HOST="${DEPLOY_HOST:-localhost}" CONSOLE_BASE="http://${DEPLOY_HOST}" -_HTTP_SCHEME="http" _WS_SCHEME="ws" -DEPLOY_DOMAIN="" -USE_HTTPS=false # MySQL(managed 模式,由 Docker 容器托管) MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-XuqmRoot@2026}" @@ -66,7 +62,7 @@ PUBLIC_PLATFORM_URL="https://dev.xuqinmin.com" # --------------------------------------------------------------------------- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" STEP=0 -TOTAL_STEPS=8 +TOTAL_STEPS=7 # --------------------------------------------------------------------------- # 工具函数 @@ -94,8 +90,6 @@ printf '\n\033[1;35m════════════════════ printf '\033[1;35m XuqmGroup 私有化一键部署脚本(全量部署)\033[0m\n' printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf ' 部署目录: %s\n' "$ROOT_DIR" -printf ' 目标主机: %s\n' "$DEPLOY_HOST" -printf ' 访问地址: %s\n' "$CONSOLE_BASE" printf ' 镜像仓库: %s\n' "$REGISTRY" printf ' 服务集: base + im + push + update + license\n\n' @@ -186,38 +180,28 @@ else fi # --------------------------------------------------------------------------- -# 0c. 域名 / HTTPS 配置(交互式) +# 0c. 外部访问地址(用于 SDK 配置,填写用户自己 nginx 代理后的地址) # --------------------------------------------------------------------------- printf '\n\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n' -printf '\033[1;33m 访问域名配置(可选)\033[0m\n' +printf '\033[1;33m 外部访问地址\033[0m\n' printf '\033[1;33m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m\n' -printf ' 默认使用 localhost(适合本机或局域网访问)\n' -printf ' 如有公网域名,可在此配置并自动申请 HTTPS 证书\n\n' +printf ' 填写您的 nginx 对外暴露的地址,SDK 客户端将使用此地址。\n' +printf ' 例:http://192.168.1.100 或 https://xuqm.example.com\n\n' -read -rp " 是否使用自定义域名?[y/N]: " _domain_yn -if [[ "${_domain_yn:-N}" =~ ^[Yy]$ ]]; then - while [ -z "$DEPLOY_DOMAIN" ]; do - read -rp " 请输入域名(如 xuqm.example.com,不含 http://): " DEPLOY_DOMAIN - if ! printf '%s' "$DEPLOY_DOMAIN" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$'; then - warn "域名格式不正确,请重新输入(例: xuqm.example.com)" - DEPLOY_DOMAIN="" - fi - done - DEPLOY_HOST="$DEPLOY_DOMAIN" - - read -rp " 是否配置 HTTPS(需域名已解析到本机,将自动申请 Let's Encrypt 证书)?[y/N]: " _https_yn - if [[ "${_https_yn:-N}" =~ ^[Yy]$ ]]; then - USE_HTTPS=true - _HTTP_SCHEME="https" - _WS_SCHEME="wss" - CONSOLE_BASE="https://${DEPLOY_DOMAIN}" - else - CONSOLE_BASE="http://${DEPLOY_DOMAIN}" +_EXT_BASE="" +while [ -z "$_EXT_BASE" ]; do + read -rp " 外部访问地址: " _EXT_BASE + if ! printf '%s' "$_EXT_BASE" | grep -qE '^https?://'; then + warn "请以 http:// 或 https:// 开头" + _EXT_BASE="" fi - ok "域名: ${DEPLOY_DOMAIN},协议: ${_HTTP_SCHEME}" -else - ok "使用默认地址: ${CONSOLE_BASE}" +done +CONSOLE_BASE="${_EXT_BASE%/}" +if printf '%s' "$CONSOLE_BASE" | grep -q '^https://'; then + _WS_SCHEME="wss" fi +DEPLOY_HOST="$(printf '%s' "$CONSOLE_BASE" | sed 's|https\?://||')" +ok "外部地址: ${CONSOLE_BASE}" # --------------------------------------------------------------------------- # Step 1 — 预检 @@ -264,7 +248,7 @@ REGISTRY_USER=${REGISTRY_USER} REGISTRY_PASSWORD=${REGISTRY_PASSWORD} IMAGE_TAG=${IMAGE_TAG} -# 启用全量服务(base + 基础设施 + im + push + update + license) +# 启用全量服务(nginx 容器默认不启动,用户自行配置宿主机 nginx) COMPOSE_PROFILES=base,infra-mysql,infra-redis,im,push,update,license # MySQL(managed 模式,Docker 容器托管) @@ -374,43 +358,11 @@ EOF chmod 600 "$ROOT_DIR/config/tenant/bootstrap.env" ok "config/tenant/bootstrap.env 已写入 (chmod 600)" -# config/nginx/conf.d/xuqm.conf -mkdir -p "$ROOT_DIR/config/nginx/conf.d" -NGINX_CONF_PATH="$ROOT_DIR/config/nginx/conf.d/xuqm.conf" -if [ ! -f "$NGINX_CONF_PATH" ]; then - fail "nginx 配置文件不存在: $NGINX_CONF_PATH(请确保在仓库根目录运行本脚本)" -fi +# (nginx 容器默认不启动,用户使用宿主机自己的 nginx 代理到各服务端口) -if $USE_HTTPS; then - # ── HTTPS 模式:安装 certbot → 申请证书 → 生成 SSL nginx 配置 → compose override ── - - # 安装 certbot(若未安装) - if ! command -v certbot >/dev/null 2>&1; then - info "安装 certbot ..." - if [ "$(command -v apt-get)" ]; then - apt-get install -y -qq certbot 2>/dev/null || fail "certbot 安装失败,请手动执行: apt install certbot" - elif [ "$(command -v yum)" ]; then - yum install -y -q certbot 2>/dev/null || fail "certbot 安装失败,请手动执行: yum install certbot" - else - fail "无法自动安装 certbot,请手动安装后重新运行" - fi - fi - ok "certbot $(certbot --version 2>&1 | head -1)" - - # 申请 Let's Encrypt 证书(standalone,此时 nginx 尚未启动) - CERT_DIR="/etc/letsencrypt/live/${DEPLOY_DOMAIN}" - if [ -f "${CERT_DIR}/fullchain.pem" ]; then - ok "证书已存在,跳过申请: ${CERT_DIR}" - else - info "申请 Let's Encrypt 证书(域名: ${DEPLOY_DOMAIN},需要端口 80 可达)..." - certbot certonly --standalone -d "$DEPLOY_DOMAIN" \ - --agree-tos --register-unsafely-without-email --non-interactive \ - || fail "证书申请失败,请确认域名 ${DEPLOY_DOMAIN} 已解析到本机且端口 80 可访问" - ok "证书已申请: ${CERT_DIR}" - fi - - # 生成包含 SSL 的 nginx 配置(覆盖仓库静态文件) - cat > "$NGINX_CONF_PATH" << NGINX_CONF +if false; then + # 以下为内置 nginx 配置示例,仅在 COMPOSE_PROFILES 包含 nginx-bundled 时使用 + cat > /dev/null << NGINX_CONF # ============================================================================= # XuqmGroup 私有化部署 — Nginx HTTPS 配置(由 deploy.sh 自动生成) # 域名: ${DEPLOY_DOMAIN} @@ -543,156 +495,8 @@ server { } } NGINX_CONF - ok "nginx HTTPS 配置已生成(${DEPLOY_DOMAIN})" - - # docker-compose override:挂载 /etc/letsencrypt 到 nginx 容器 - cat > "$ROOT_DIR/docker-compose.override.yml" << 'OVERRIDE_YAML' -# 由 deploy.sh 自动生成 — HTTPS 模式需要挂载 Let's Encrypt 证书 -services: - nginx: - volumes: - - /etc/letsencrypt:/etc/letsencrypt:ro -OVERRIDE_YAML - ok "docker-compose.override.yml 已生成(SSL 证书挂载)" - -else - ok "config/nginx/conf.d/xuqm.conf 就绪(HTTP 模式,使用仓库内文件)" fi -# (以下 heredoc 为静态配置的备份注释,不执行) -: <<'NGINX_CONF' -# ============================================================================= -# XuqmGroup 私有化部署 — Nginx 路由配置 -# -# 服务端口映射: -# tenant-service 9001 /api/*(核心 API)、/actuator/* -# file-service 8086 /file/*(文件上传下载) -# im-service 8082 /api/im/*(IM HTTP)、/ws/im/*(WebSocket) -# update-service 8084 /api/v1/updates/*、/api/v1/rn/* -# push-service 8083 厂商回调(内部端口) -# license-service 8085 内部调用 -# ops-web 80 /ops/*(运营后台) -# tenant-web 80 /*(控制台,兜底路由) -# ============================================================================= - -server { - listen 80; - server_name _; - - # 强制 UTF-8 编码,防止中文乱码 - charset utf-8; - - # 最大上传文件大小(文件服务单独设置 500m) - client_max_body_size 100m; - - # ----------- 健康检查 ----------- - location /health { - return 200 "ok\n"; - add_header Content-Type text/plain; - } - - # ----------- 版本管理服务(update-service:8084)----------- - # 注意:必须在通用 /api/ 之前声明,否则会被错误路由到 tenant-service - location /api/v1/updates/ { - proxy_pass http://update-service:8084/api/v1/updates/; - 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_read_timeout 60s; - } - - # RN 热更新包下载和列表 - location /api/v1/rn/ { - proxy_pass http://update-service:8084/api/v1/rn/; - 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_read_timeout 120s; - } - - # ----------- IM 服务(im-service:8082)----------- - # 注意:必须在通用 /api/ 之前声明 - location /api/im/ { - proxy_pass http://im-service:8082/api/im/; - 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_read_timeout 60s; - } - - # IM WebSocket 长连接(客户端消息收发) - location /ws/im/ { - proxy_pass http://im-service:8082/ws/im/; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_read_timeout 3600s; # WebSocket 保持 1 小时 - } - - # ----------- License 服务(license-service:8085)----------- - # 注意:必须在通用 /api/ 之前声明 - location /api/license/ { - proxy_pass http://license-service:8085/api/license/; - 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_read_timeout 60s; - } - - # ----------- 核心 API(tenant-service:9001)----------- - # 注意:tenant-service 运行在 9001 端口(不是 8080) - location /api/ { - proxy_pass http://tenant-service:9001/api/; - 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_read_timeout 60s; - } - - # Spring Boot Actuator 健康检查(内部监控用) - location /actuator/ { - proxy_pass http://tenant-service:9001/actuator/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - # ----------- 文件服务(file-service:8086)----------- - location /file/ { - proxy_pass http://file-service:8086/file/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - client_max_body_size 500m; - proxy_read_timeout 300s; - proxy_send_timeout 300s; - } - - # ----------- 文档站(tenant-web 内置,VitePress base=/docs/)----------- - location /docs/ { - proxy_pass http://tenant-web:80/docs/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - # ----------- 运营后台(ops-web:80)----------- - location /ops { - proxy_pass http://ops-web:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } - - # ----------- 控制台前端(tenant-web:80)----------- - # 兜底路由,必须放最后 - location / { - proxy_pass http://tenant-web:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} -NGINX_CONF - # config/vendors/push.env — 推送服务厂商凭据(初始为关闭状态,按需开启) mkdir -p "$ROOT_DIR/config/vendors" if [ ! -f "$ROOT_DIR/config/vendors/push.env" ]; then @@ -850,11 +654,11 @@ docker compose \ --profile im --profile push --profile update --profile license \ up -d -# 等待 tenant-service 健康(最多 120 秒) +# 等待 tenant-service 健康(直接打 9001 端口,不经过 nginx) printf ' 等待 tenant-service 启动' for i in $(seq 1 40); do code="$(curl -skL --noproxy '*' -o /dev/null -w '%{http_code}' --max-time 4 \ - "http://${DEPLOY_HOST}/actuator/health" 2>/dev/null || echo 000)" + "http://127.0.0.1:9001/actuator/health" 2>/dev/null || echo 000)" if [ "$code" = "200" ]; then printf '\n' ok "tenant-service 健康 (HTTP 200)" @@ -1037,7 +841,7 @@ print(json.dumps(resp['data'])) -H "Content-Type: application/json" \ --data-binary @- \ -w "\n__HTTP_STATUS__:%{http_code}" \ - "http://127.0.0.1/api/private/deployment/migrate/import") + "http://127.0.0.1:9001/api/private/deployment/migrate/import") _IMPORT_STATUS=$(printf '%s' "$_IMPORT_RESP" | grep -o '__HTTP_STATUS__:[0-9]*' | cut -d: -f2) _IMPORT_BODY=$(printf '%s' "$_IMPORT_RESP" | sed 's/__HTTP_STATUS__:[0-9]*//') if [ "${_IMPORT_STATUS}" != "200" ]; then @@ -1106,26 +910,51 @@ fi printf '\n\033[1;35m══════════════════════════════════════════════════\033[0m\n' printf '\033[1;35m XuqmGroup 私有化部署完成\033[0m\n' printf '\033[1;35m══════════════════════════════════════════════════\033[0m\n' -printf '\n \033[1m访问地址:\033[0m %s\n' "${CONSOLE_BASE}" -printf ' \033[1m运营后台:\033[0m %s/ops\n' "${CONSOLE_BASE}" -printf ' \033[1m文档站 :\033[0m %s/docs/\n' "${CONSOLE_BASE}" printf '\n \033[1m租户信息:\033[0m\n' -printf ' 邮箱: %s\n' "${DEPLOY_TENANT_EMAIL}" -printf ' 用户名: %s\n' "${DEPLOY_TENANT_USERNAME}" +printf ' 邮箱: %s\n' "${DEPLOY_TENANT_EMAIL}" +printf ' 用户名: %s\n' "${DEPLOY_TENANT_USERNAME}" if [ "$DEPLOY_MODE" = "new" ]; then - printf ' 密码: 部署时设置的密码\n' + printf ' 密码: 部署时设置的密码\n' else - printf ' 密码: 同生产平台密码(原样迁移,未重置)\n' + printf ' 密码: 同生产平台密码(原样迁移,未重置)\n' fi -printf '\n \033[1m初始化方式:\033[0m%s\n' "${DEPLOY_MODE}" -printf '\n \033[1m服务状态:\033[0m\n' -printf ' 控制台 %s\n' "${CONSOLE_BASE}" -printf ' IM 服务 %s/api/im/\n' "${CONSOLE_BASE}" -printf ' 版本管理 %s/api/v1/updates/\n' "${CONSOLE_BASE}" -printf ' 文件服务 %s/file/\n' "${CONSOLE_BASE}" -printf ' License 服务 已启动(内部调用)\n' -printf ' 推送服务 已启动(厂商凭据见 config/vendors/push.env)\n' -printf '\n \033[1m部署目录:\033[0m %s\n' "$ROOT_DIR" -printf ' \033[1m验证结果:\033[0m %s/.deploy-state/last-verify.json\n' "$ROOT_DIR" +printf '\n \033[1m容器端口(请在您的 nginx 中配置代理):\033[0m\n' +printf ' 控制台前端 127.0.0.1:8080 → 代理 /\n' +printf ' 运营后台 127.0.0.1:8081 → 代理 /ops\n' +printf ' 核心 API 127.0.0.1:9001 → 代理 /api/ /actuator/\n' +printf ' 文件服务 127.0.0.1:8086 → 代理 /file/ /api/file/\n' +printf ' IM 服务 127.0.0.1:8082 → 代理 /api/im/ /ws/im\n' +printf ' 版本管理 127.0.0.1:8084 → 代理 /api/v1/updates/ /api/v1/rn/\n' +printf ' License 服务 127.0.0.1:8085 → 代理 /api/license/\n' +printf ' 推送服务 127.0.0.1:8083 (厂商回调,按需代理)\n' +printf '\n \033[1mNginx 配置参考(复制到您的 nginx server 块):\033[0m\n' +printf '\033[0;37m' +cat <<'NGINX_REF' + charset utf-8; + client_max_body_size 100m; + + location /api/v1/updates/ { proxy_pass http://127.0.0.1:8084/api/v1/updates/; } + location /api/v1/rn/ { proxy_pass http://127.0.0.1:8084/api/v1/rn/; } + location /api/im/ { proxy_pass http://127.0.0.1:8082/api/im/; } + location /ws/im { + proxy_pass http://127.0.0.1:8082/ws/im; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 3600s; + } + location /api/license/ { proxy_pass http://127.0.0.1:8085/api/license/; } + location /file/ { + proxy_pass http://127.0.0.1:8086/file/; + client_max_body_size 500m; + proxy_read_timeout 300s; + } + location /api/ { proxy_pass http://127.0.0.1:9001/api/; } + location /actuator/ { proxy_pass http://127.0.0.1:9001/actuator/; } + location /ops { proxy_pass http://127.0.0.1:8081; } + location / { proxy_pass http://127.0.0.1:8080; } +NGINX_REF +printf '\033[0m' +printf '\n \033[1m部署目录:\033[0m %s\n' "$ROOT_DIR" printf ' \033[1m审计日志:\033[0m %s/logs/audit.log\n' "$ROOT_DIR" -printf '\n\033[1;32m 部署成功!\033[0m\n\n' +printf '\n\033[1;32m 部署成功!配置好 nginx 后即可访问:%s\033[0m\n\n' "${CONSOLE_BASE}"