chore: scaffold private deployment repository
这个提交包含在:
当前提交
4ada03183a
14
.deploy-state/current.json
普通文件
14
.deploy-state/current.json
普通文件
@ -0,0 +1,14 @@
|
||||
{
|
||||
"privateVersion": "2026.05.18-private.1",
|
||||
"profiles": ["base"],
|
||||
"mysql": {"mode": "external", "status": "UNKNOWN"},
|
||||
"redis": {"mode": "external", "status": "UNKNOWN"},
|
||||
"services": {
|
||||
"file": true,
|
||||
"im": false,
|
||||
"push": false,
|
||||
"update": false,
|
||||
"license": false
|
||||
},
|
||||
"lastHealthcheck": null
|
||||
}
|
||||
1
.deploy-state/history.log
普通文件
1
.deploy-state/history.log
普通文件
@ -0,0 +1 @@
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
{
|
||||
"status": "UNKNOWN",
|
||||
"checks": []
|
||||
}
|
||||
|
||||
9
.deploy-state/progress.md
普通文件
9
.deploy-state/progress.md
普通文件
@ -0,0 +1,9 @@
|
||||
# Deployment Progress
|
||||
|
||||
| Time | Step | Status | Notes |
|
||||
|------|------|--------|-------|
|
||||
|
||||
| 2026-05-18T19:48:27+0800 | configure | STARTED | rendering initial config |
|
||||
| 2026-05-18T19:48:27+0800 | render-config | STARTED | rendering runtime files |
|
||||
| 2026-05-18T19:48:27+0800 | render-config | DONE | runtime files rendered |
|
||||
| 2026-05-18T19:48:27+0800 | configure | DONE | config ready |
|
||||
41
.env.example
普通文件
41
.env.example
普通文件
@ -0,0 +1,41 @@
|
||||
PRIVATE_VERSION=2026.05.18-private.1
|
||||
REGISTRY=registry.example.com/xuqm
|
||||
IMAGE_TAG=2026.05.18-private.1
|
||||
|
||||
COMPOSE_PROFILES=base
|
||||
|
||||
ENABLE_FILE=true
|
||||
ENABLE_IM=false
|
||||
ENABLE_PUSH=false
|
||||
ENABLE_UPDATE=false
|
||||
ENABLE_LICENSE=false
|
||||
|
||||
MYSQL_MODE=external
|
||||
MYSQL_HOST=127.0.0.1
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=xuqm_private
|
||||
MYSQL_USERNAME=xuqm
|
||||
MYSQL_PASSWORD=change-me
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
MYSQL_DATA_DIR=./data/mysql
|
||||
|
||||
REDIS_MODE=external
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=change-me
|
||||
REDIS_DATABASE=0
|
||||
REDIS_DATA_DIR=./data/redis
|
||||
|
||||
CONSOLE_DOMAIN=https://console.customer.com
|
||||
OPS_DOMAIN=https://ops.customer.com
|
||||
DOCS_DOMAIN=https://docs.customer.com
|
||||
FILE_DOMAIN=https://file.customer.com
|
||||
IM_DOMAIN=https://im.customer.com
|
||||
UPDATE_DOMAIN=https://update.customer.com
|
||||
LICENSE_DOMAIN=https://license.customer.com
|
||||
PUSH_DOMAIN=https://push.customer.com
|
||||
|
||||
TENANT_BOOTSTRAP_EMAIL=admin@customer.com
|
||||
TENANT_BOOTSTRAP_PASSWORD=change-me-on-first-login
|
||||
TENANT_BOOTSTRAP_APP_KEY=ak_private_default
|
||||
|
||||
18
.gitignore
vendored
普通文件
18
.gitignore
vendored
普通文件
@ -0,0 +1,18 @@
|
||||
.env
|
||||
config/secrets.env
|
||||
logs/*.log
|
||||
data/mysql/*
|
||||
data/redis/*
|
||||
data/uploads/*
|
||||
data/update/*
|
||||
data/backups/*
|
||||
!data/mysql/.gitkeep
|
||||
!data/redis/.gitkeep
|
||||
!data/uploads/.gitkeep
|
||||
!data/update/.gitkeep
|
||||
!data/backups/.gitkeep
|
||||
dist/
|
||||
*.tar.gz
|
||||
*.bak
|
||||
.DS_Store
|
||||
|
||||
75
README.md
普通文件
75
README.md
普通文件
@ -0,0 +1,75 @@
|
||||
# XuqmGroup Private Deploy
|
||||
|
||||
私有化部署仓库只负责客户环境交付,不包含业务源码和 demo 前后端。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
./scripts/configure.sh
|
||||
vim .env
|
||||
vim config/secrets.env
|
||||
./scripts/install.sh --profile base
|
||||
./scripts/healthcheck.sh
|
||||
```
|
||||
|
||||
生产部署前必须完成:
|
||||
|
||||
- 配置镜像仓库 `REGISTRY` 和版本 `IMAGE_TAG`。
|
||||
- 选择 MySQL/Redis 模式:`external` 使用客户自备服务,`managed` 由脚本创建容器服务。
|
||||
- 配置控制台、文档站、文件、IM、Push、Update、License 域名。
|
||||
- 配置 SMTP、Push 厂商、应用市场发布凭据。
|
||||
- 确认证书和反向代理策略,默认 Nginx 配置只作为模板入口。
|
||||
|
||||
## 部署模式
|
||||
|
||||
MySQL、Redis 支持两种模式:
|
||||
|
||||
- `external`:客户自备连接,脚本只校验连通性和权限。
|
||||
- `managed`:脚本新建服务,自动创建数据库、账号、密码和数据目录。
|
||||
|
||||
生产环境默认推荐 `external/external`。
|
||||
|
||||
托管模式示例:
|
||||
|
||||
```bash
|
||||
./scripts/install.sh --profile base --mysql-mode managed --redis-mode managed
|
||||
```
|
||||
|
||||
外部模式示例:
|
||||
|
||||
```bash
|
||||
./scripts/install.sh --profile base --mysql-mode external --redis-mode external
|
||||
```
|
||||
|
||||
## 可选服务
|
||||
|
||||
- `base`:基础控制台、运营平台、文档站、文件服务。
|
||||
- `im`:IM HTTP / WebSocket。
|
||||
- `push`:厂商推送。
|
||||
- `update`:版本管理、RN 热更新、应用市场自动发布。
|
||||
- `license`:License 校验。
|
||||
|
||||
后期启用:
|
||||
|
||||
```bash
|
||||
./scripts/enable-service.sh im
|
||||
./scripts/enable-service.sh push
|
||||
./scripts/enable-service.sh update
|
||||
./scripts/enable-service.sh license
|
||||
```
|
||||
|
||||
禁用服务只修改部署配置并停止对应容器,不删除数据:
|
||||
|
||||
```bash
|
||||
./scripts/disable-service.sh im
|
||||
```
|
||||
|
||||
## 接手入口
|
||||
|
||||
- 实时部署进度:`.deploy-state/progress.md`
|
||||
- 最近运行状态:`.deploy-state/current.json`
|
||||
- 最近健康检查:`.deploy-state/last-healthcheck.json`
|
||||
- 脚本审计日志:`logs/audit.log`
|
||||
- 交付说明:`docs/runbook.md`
|
||||
- 配置说明:`docs/configuration.md`
|
||||
- 验收清单:`docs/acceptance-checklist.md`
|
||||
20
config/docs/docs-runtime.json
普通文件
20
config/docs/docs-runtime.json
普通文件
@ -0,0 +1,20 @@
|
||||
{
|
||||
"deployment": "PRIVATE",
|
||||
"privateVersion": "2026.05.18-private.1",
|
||||
"domains": {
|
||||
"console": "https://console.customer.com",
|
||||
"docs": "https://docs.customer.com",
|
||||
"file": "https://file.customer.com",
|
||||
"im": "https://im.customer.com",
|
||||
"update": "https://update.customer.com",
|
||||
"license": "https://license.customer.com",
|
||||
"push": "https://push.customer.com"
|
||||
},
|
||||
"features": {
|
||||
"file": true,
|
||||
"im": false,
|
||||
"push": false,
|
||||
"update": false,
|
||||
"license": false
|
||||
}
|
||||
}
|
||||
6
config/infra.env
普通文件
6
config/infra.env
普通文件
@ -0,0 +1,6 @@
|
||||
MYSQL_MODE=external
|
||||
MYSQL_DATA_DIR=./data/mysql
|
||||
REDIS_MODE=external
|
||||
REDIS_DATA_DIR=./data/redis
|
||||
FILE_STORAGE_MODE=local
|
||||
|
||||
6
config/mail/smtp.env
普通文件
6
config/mail/smtp.env
普通文件
@ -0,0 +1,6 @@
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=465
|
||||
SMTP_USERNAME=
|
||||
SMTP_TLS=true
|
||||
SMTP_SSL=true
|
||||
|
||||
13
config/nginx/conf.d/xuqm.conf
普通文件
13
config/nginx/conf.d/xuqm.conf
普通文件
@ -0,0 +1,13 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location /health {
|
||||
return 200 "ok\n";
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://tenant-web:80;
|
||||
}
|
||||
}
|
||||
|
||||
12
config/nginx/nginx.conf
普通文件
12
config/nginx/nginx.conf
普通文件
@ -0,0 +1,12 @@
|
||||
events {}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
client_max_body_size 1024m;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
LOG_FORMAT=json
|
||||
LOG_RETENTION_DAYS=30
|
||||
LOG_COLLECTOR=none
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
METRICS_ENABLED=false
|
||||
PROMETHEUS_ENABLED=false
|
||||
|
||||
24
config/sdk/xuqm-private-sdk.json
普通文件
24
config/sdk/xuqm-private-sdk.json
普通文件
@ -0,0 +1,24 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"configVersion": "2026.05.18-private.1",
|
||||
"deployment": "PRIVATE",
|
||||
"appKey": "ak_private_default",
|
||||
"controlBaseUrl": "https://console.customer.com",
|
||||
"fileBaseUrl": "https://file.customer.com",
|
||||
"imApiBaseUrl": "https://im.customer.com",
|
||||
"imWsUrl": "wss://im.customer.com/ws/im",
|
||||
"pushBaseUrl": "https://push.customer.com",
|
||||
"updateBaseUrl": "https://update.customer.com",
|
||||
"licenseBaseUrl": "https://license.customer.com",
|
||||
"docsBaseUrl": "https://docs.customer.com",
|
||||
"features": {
|
||||
"file": true,
|
||||
"im": false,
|
||||
"push": false,
|
||||
"update": false,
|
||||
"license": false
|
||||
},
|
||||
"connectTimeoutMs": 10000,
|
||||
"readTimeoutMs": 30000,
|
||||
"logLevel": "WARN"
|
||||
}
|
||||
5
config/secrets.env.example
普通文件
5
config/secrets.env.example
普通文件
@ -0,0 +1,5 @@
|
||||
MYSQL_PASSWORD=change-me
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
REDIS_PASSWORD=change-me
|
||||
SMTP_PASSWORD=
|
||||
|
||||
4
config/tenant/bootstrap.env
普通文件
4
config/tenant/bootstrap.env
普通文件
@ -0,0 +1,4 @@
|
||||
TENANT_BOOTSTRAP_EMAIL=admin@customer.com
|
||||
TENANT_BOOTSTRAP_PASSWORD=change-me-on-first-login
|
||||
TENANT_BOOTSTRAP_APP_KEY=ak_private_default
|
||||
|
||||
7
config/vendors/push.env
vendored
普通文件
7
config/vendors/push.env
vendored
普通文件
@ -0,0 +1,7 @@
|
||||
HUAWEI_PUSH_ENABLED=false
|
||||
MI_PUSH_ENABLED=false
|
||||
OPPO_PUSH_ENABLED=false
|
||||
VIVO_PUSH_ENABLED=false
|
||||
HONOR_PUSH_ENABLED=false
|
||||
APNS_ENABLED=false
|
||||
|
||||
6
config/vendors/store-submit.env
vendored
普通文件
6
config/vendors/store-submit.env
vendored
普通文件
@ -0,0 +1,6 @@
|
||||
HUAWEI_STORE_ENABLED=false
|
||||
MI_STORE_ENABLED=false
|
||||
OPPO_STORE_ENABLED=false
|
||||
VIVO_STORE_ENABLED=false
|
||||
HONOR_STORE_ENABLED=false
|
||||
|
||||
28
config/xuqm.env
普通文件
28
config/xuqm.env
普通文件
@ -0,0 +1,28 @@
|
||||
DEPLOYMENT_MODE=PRIVATE
|
||||
TENANT_REGISTER_ENABLED=false
|
||||
TENANT_BOOTSTRAP_ENABLED=true
|
||||
|
||||
ENABLE_FILE=true
|
||||
ENABLE_IM=false
|
||||
ENABLE_PUSH=false
|
||||
ENABLE_UPDATE=false
|
||||
ENABLE_LICENSE=false
|
||||
|
||||
MYSQL_HOST=127.0.0.1
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=xuqm_private
|
||||
MYSQL_USERNAME=xuqm
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_DATABASE=0
|
||||
|
||||
CONSOLE_DOMAIN=https://console.customer.com
|
||||
OPS_DOMAIN=https://ops.customer.com
|
||||
DOCS_DOMAIN=https://docs.customer.com
|
||||
FILE_DOMAIN=https://file.customer.com
|
||||
IM_DOMAIN=https://im.customer.com
|
||||
UPDATE_DOMAIN=https://update.customer.com
|
||||
LICENSE_DOMAIN=https://license.customer.com
|
||||
PUSH_DOMAIN=https://push.customer.com
|
||||
|
||||
1
data/backups/.gitkeep
普通文件
1
data/backups/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
1
data/mysql/.gitkeep
普通文件
1
data/mysql/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
1
data/redis/.gitkeep
普通文件
1
data/redis/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
1
data/update/.gitkeep
普通文件
1
data/update/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
1
data/uploads/.gitkeep
普通文件
1
data/uploads/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
30
docker-compose.infra.yml
普通文件
30
docker-compose.infra.yml
普通文件
@ -0,0 +1,30 @@
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.4
|
||||
profiles: ["infra-mysql"]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: ${MYSQL_DATABASE}
|
||||
MYSQL_USER: ${MYSQL_USERNAME}
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||
TZ: Asia/Shanghai
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
- --default-time-zone=+08:00
|
||||
ports:
|
||||
- "${MYSQL_PORT:-3306}:3306"
|
||||
volumes:
|
||||
- ./data/mysql:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7.4-alpine
|
||||
profiles: ["infra-redis"]
|
||||
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"]
|
||||
ports:
|
||||
- "${REDIS_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
restart: unless-stopped
|
||||
|
||||
90
docker-compose.yml
普通文件
90
docker-compose.yml
普通文件
@ -0,0 +1,90 @@
|
||||
services:
|
||||
tenant-service:
|
||||
image: ${REGISTRY}/tenant-service:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
- ./config/tenant/bootstrap.env
|
||||
restart: unless-stopped
|
||||
|
||||
file-service:
|
||||
image: ${REGISTRY}/file-service:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
volumes:
|
||||
- ./data/uploads:/data/uploads
|
||||
restart: unless-stopped
|
||||
|
||||
tenant-web:
|
||||
image: ${REGISTRY}/tenant-web:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
restart: unless-stopped
|
||||
|
||||
ops-web:
|
||||
image: ${REGISTRY}/ops-web:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
restart: unless-stopped
|
||||
|
||||
docs-site:
|
||||
image: ${REGISTRY}/docs-site:${IMAGE_TAG}
|
||||
profiles: ["base"]
|
||||
volumes:
|
||||
- ./config/docs/docs-runtime.json:/app/config/docs-runtime.json:ro
|
||||
- ./config/sdk/xuqm-private-sdk.json:/app/config/xuqm-private-sdk.json:ro
|
||||
restart: unless-stopped
|
||||
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
profiles: ["base"]
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./config/nginx/conf.d:/etc/nginx/conf.d:ro
|
||||
depends_on:
|
||||
- tenant-service
|
||||
- tenant-web
|
||||
- ops-web
|
||||
- docs-site
|
||||
restart: unless-stopped
|
||||
|
||||
im-service:
|
||||
image: ${REGISTRY}/im-service:${IMAGE_TAG}
|
||||
profiles: ["im"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
restart: unless-stopped
|
||||
|
||||
push-service:
|
||||
image: ${REGISTRY}/push-service:${IMAGE_TAG}
|
||||
profiles: ["push"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
- ./config/vendors/push.env
|
||||
restart: unless-stopped
|
||||
|
||||
update-service:
|
||||
image: ${REGISTRY}/update-service:${IMAGE_TAG}
|
||||
profiles: ["update"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
- ./config/vendors/store-submit.env
|
||||
volumes:
|
||||
- ./data/update:/data/update
|
||||
restart: unless-stopped
|
||||
|
||||
license-service:
|
||||
image: ${REGISTRY}/license-service:${IMAGE_TAG}
|
||||
profiles: ["license"]
|
||||
env_file:
|
||||
- ./config/xuqm.env
|
||||
- ./config/secrets.env
|
||||
restart: unless-stopped
|
||||
|
||||
36
docs/acceptance-checklist.md
普通文件
36
docs/acceptance-checklist.md
普通文件
@ -0,0 +1,36 @@
|
||||
# 验收清单
|
||||
|
||||
## 基础部署
|
||||
|
||||
- `./scripts/configure.sh` 可重复执行。
|
||||
- `./scripts/install.sh --profile base` 可完成部署。
|
||||
- `./scripts/healthcheck.sh` 输出 `PASS` 或明确错误码。
|
||||
- 文档站加载 `config/sdk/xuqm-private-sdk.json`。
|
||||
- 租户注册入口关闭。
|
||||
- 首次启动自动创建内置租户。
|
||||
|
||||
## 中间件
|
||||
|
||||
- `external` MySQL/Redis 不启动本地容器。
|
||||
- `managed` MySQL/Redis 自动启动并持久化数据。
|
||||
- 托管模式自动生成密码并写入 `config/secrets.env`。
|
||||
|
||||
## 可选服务
|
||||
|
||||
- `im` 可后期独立启用和禁用。
|
||||
- `push` 可后期独立启用和禁用。
|
||||
- `update` 可后期独立启用和禁用。
|
||||
- `license` 可后期独立启用和禁用。
|
||||
- 禁用任一可选服务时,基础服务可继续运行。
|
||||
|
||||
## 厂商能力
|
||||
|
||||
- Push 支持华为、小米、OPPO、vivo、荣耀。
|
||||
- 应用市场自动发布支持华为、小米、OPPO、vivo、荣耀。
|
||||
- 厂商凭据缺失时返回明确诊断,不影响基础服务启动。
|
||||
|
||||
## 公有化隔离
|
||||
|
||||
- 公有化域名 `dev.xuqinmin.com` 不写入私有化 SDK 配置。
|
||||
- 私有化改造不影响公有化配置和部署链路。
|
||||
- 公有化回归通过后才能发布私有化版本。
|
||||
56
docs/configuration.md
普通文件
56
docs/configuration.md
普通文件
@ -0,0 +1,56 @@
|
||||
# 配置说明
|
||||
|
||||
## `.env`
|
||||
|
||||
部署入口配置,控制镜像版本、服务 profile、域名和 MySQL/Redis 模式。
|
||||
|
||||
关键字段:
|
||||
|
||||
- `REGISTRY`:私有 Docker 镜像仓库。
|
||||
- `IMAGE_TAG`:本次部署镜像版本。
|
||||
- `COMPOSE_PROFILES`:启用的服务集合,例如 `base,im,push,update,license`。
|
||||
- `MYSQL_MODE`:`external` 或 `managed`。
|
||||
- `REDIS_MODE`:`external` 或 `managed`。
|
||||
- `ENABLE_IM`、`ENABLE_PUSH`、`ENABLE_UPDATE`、`ENABLE_LICENSE`:运行时功能开关。
|
||||
|
||||
## `config/secrets.env`
|
||||
|
||||
敏感配置文件,不提交 Git。
|
||||
|
||||
关键字段:
|
||||
|
||||
- `MYSQL_PASSWORD`
|
||||
- `MYSQL_ROOT_PASSWORD`
|
||||
- `REDIS_PASSWORD`
|
||||
- `SMTP_PASSWORD`
|
||||
|
||||
托管模式下,如果密码为空或为 `change-me`,脚本会自动生成并写回该文件。
|
||||
|
||||
## `config/xuqm.env`
|
||||
|
||||
业务服务共享配置,包含私有化运行模式、单租户初始化、域名和基础中间件连接信息。
|
||||
|
||||
私有化必须保持:
|
||||
|
||||
```env
|
||||
DEPLOYMENT_MODE=PRIVATE
|
||||
TENANT_REGISTER_ENABLED=false
|
||||
TENANT_BOOTSTRAP_ENABLED=true
|
||||
```
|
||||
|
||||
## `config/sdk/xuqm-private-sdk.json`
|
||||
|
||||
私有化 SDK 初始化配置,由 `scripts/render-config.sh` 生成。
|
||||
|
||||
文档站和客户应用示例必须使用该文件,不再指向 `dev.xuqinmin.com` 公有化逻辑。
|
||||
|
||||
## `config/vendors`
|
||||
|
||||
厂商能力配置:
|
||||
|
||||
- `push.env`:华为、小米、OPPO、vivo、荣耀 Push 凭据。
|
||||
- `store-submit.env`:华为、小米、OPPO、vivo、荣耀应用市场自动发布凭据。
|
||||
|
||||
## `config/mail/smtp.env`
|
||||
|
||||
邮件服务配置。生产环境必须使用客户提供的 SMTP 服务。
|
||||
58
docs/runbook.md
普通文件
58
docs/runbook.md
普通文件
@ -0,0 +1,58 @@
|
||||
# 私有化部署运行手册
|
||||
|
||||
## 目标
|
||||
|
||||
本仓库用于客户私有环境交付,只编排已有 Docker 镜像,不包含业务源码、不构建 demo 服务。
|
||||
|
||||
## 标准流程
|
||||
|
||||
1. 执行 `./scripts/configure.sh` 生成 `.env` 和 `config/secrets.env`。
|
||||
2. 修改 `.env`:镜像仓库、镜像版本、域名、可选服务、MySQL/Redis 模式。
|
||||
3. 修改 `config/secrets.env`:数据库密码、Redis 密码、SMTP 密码等敏感配置。
|
||||
4. 修改 `config/mail/smtp.env`、`config/vendors/*.env`。
|
||||
5. 执行 `./scripts/install.sh --profile base` 部署基础服务。
|
||||
6. 按需执行 `./scripts/enable-service.sh im|push|update|license` 启用可选服务。
|
||||
7. 执行 `./scripts/healthcheck.sh` 并检查 `.deploy-state/last-healthcheck.json`。
|
||||
|
||||
## MySQL/Redis 模式
|
||||
|
||||
`external` 模式:
|
||||
|
||||
- 客户提供连接地址、账号、密码、数据库名。
|
||||
- 部署脚本只使用连接配置,不启动新服务。
|
||||
- 生产默认使用该模式。
|
||||
|
||||
`managed` 模式:
|
||||
|
||||
- 部署脚本通过 Docker Compose 启动 MySQL/Redis。
|
||||
- 密码为空或为 `change-me` 时自动生成并写入 `config/secrets.env`。
|
||||
- 数据目录写入 `data/mysql` 和 `data/redis`。
|
||||
|
||||
## 可选服务
|
||||
|
||||
`im`、`push`、`update`、`license` 均可不部署,后期独立启用。
|
||||
|
||||
启用命令:
|
||||
|
||||
```bash
|
||||
./scripts/enable-service.sh im
|
||||
./scripts/install.sh --profile base,im
|
||||
```
|
||||
|
||||
禁用命令:
|
||||
|
||||
```bash
|
||||
./scripts/disable-service.sh im
|
||||
```
|
||||
|
||||
禁用服务不会删除数据,重新启用后继续使用原配置和数据目录。
|
||||
|
||||
## 接手规则
|
||||
|
||||
任何 agent 开始执行前必须先查看:
|
||||
|
||||
- `.deploy-state/progress.md`
|
||||
- `.deploy-state/current.json`
|
||||
- `logs/audit.log`
|
||||
|
||||
执行关键步骤后必须追加进度,确保中断后可继续。
|
||||
16
image-manifest.json
普通文件
16
image-manifest.json
普通文件
@ -0,0 +1,16 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"privateVersion": "2026.05.18-private.1",
|
||||
"registry": "registry.example.com/xuqm",
|
||||
"images": [
|
||||
{"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"},
|
||||
{"name": "update-service", "required": false, "profile": "update"},
|
||||
{"name": "license-service", "required": false, "profile": "license"}
|
||||
]
|
||||
}
|
||||
1
logs/.gitkeep
普通文件
1
logs/.gitkeep
普通文件
@ -0,0 +1 @@
|
||||
|
||||
16
scripts/backup.sh
可执行文件
16
scripts/backup.sh
可执行文件
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
audit "backup" "STARTED" "creating config backup"
|
||||
progress "backup" "STARTED" "creating config backup"
|
||||
|
||||
mkdir -p "$ROOT_DIR/data/backups"
|
||||
tar --exclude='config/secrets.env' -czf "$ROOT_DIR/data/backups/config-$(date +%Y%m%d%H%M%S).tar.gz" \
|
||||
-C "$ROOT_DIR" VERSION .env config .deploy-state
|
||||
|
||||
audit "backup" "DONE" "backup created"
|
||||
progress "backup" "DONE" "backup created"
|
||||
|
||||
19
scripts/configure.sh
可执行文件
19
scripts/configure.sh
可执行文件
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
audit "configure" "STARTED" "rendering initial config"
|
||||
progress "configure" "STARTED" "rendering initial config"
|
||||
|
||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||
cp "$ROOT_DIR/.env.example" "$ROOT_DIR/.env"
|
||||
fi
|
||||
|
||||
ensure_secret_file
|
||||
|
||||
"$ROOT_DIR/scripts/render-config.sh"
|
||||
|
||||
audit "configure" "DONE" "config ready"
|
||||
progress "configure" "DONE" "config ready"
|
||||
30
scripts/disable-service.sh
可执行文件
30
scripts/disable-service.sh
可执行文件
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
SERVICE="${1:-}"
|
||||
[ -n "$SERVICE" ] || fail_json "XUQM_PRIVATE_1002" "service name is required" "disable-service"
|
||||
|
||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||
cp "$ROOT_DIR/.env.example" "$ROOT_DIR/.env"
|
||||
fi
|
||||
load_env
|
||||
|
||||
audit "disable-service" "STARTED" "$SERVICE"
|
||||
progress "disable-service" "STARTED" "$SERVICE"
|
||||
|
||||
case "$SERVICE" in
|
||||
im) set_env_value "$ROOT_DIR/.env" "ENABLE_IM" "false" ;;
|
||||
push) set_env_value "$ROOT_DIR/.env" "ENABLE_PUSH" "false" ;;
|
||||
update) set_env_value "$ROOT_DIR/.env" "ENABLE_UPDATE" "false" ;;
|
||||
license) set_env_value "$ROOT_DIR/.env" "ENABLE_LICENSE" "false" ;;
|
||||
*) fail_json "XUQM_PRIVATE_1002" "unknown service: $SERVICE" "disable-service" ;;
|
||||
esac
|
||||
|
||||
set_env_value "$ROOT_DIR/.env" "COMPOSE_PROFILES" "$(remove_profile "${COMPOSE_PROFILES:-base}" "$SERVICE")"
|
||||
"$ROOT_DIR/scripts/render-config.sh"
|
||||
|
||||
audit "disable-service" "DONE" "$SERVICE"
|
||||
progress "disable-service" "DONE" "$SERVICE"
|
||||
17
scripts/doctor.sh
可执行文件
17
scripts/doctor.sh
可执行文件
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
audit "doctor" "STARTED" "collecting diagnostics"
|
||||
progress "doctor" "STARTED" "collecting diagnostics"
|
||||
|
||||
mkdir -p "$ROOT_DIR/dist"
|
||||
tar --exclude='config/secrets.env' --exclude='data' --exclude='*.tar.gz' \
|
||||
-czf "$ROOT_DIR/dist/doctor-$(date +%Y%m%d%H%M%S).tar.gz" \
|
||||
-C "$ROOT_DIR" VERSION .deploy-state config logs README.md
|
||||
|
||||
audit "doctor" "DONE" "diagnostics collected"
|
||||
progress "doctor" "DONE" "diagnostics collected"
|
||||
|
||||
30
scripts/enable-service.sh
可执行文件
30
scripts/enable-service.sh
可执行文件
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
SERVICE="${1:-}"
|
||||
[ -n "$SERVICE" ] || fail_json "XUQM_PRIVATE_1002" "service name is required" "enable-service"
|
||||
|
||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||
cp "$ROOT_DIR/.env.example" "$ROOT_DIR/.env"
|
||||
fi
|
||||
load_env
|
||||
|
||||
audit "enable-service" "STARTED" "$SERVICE"
|
||||
progress "enable-service" "STARTED" "$SERVICE"
|
||||
|
||||
case "$SERVICE" in
|
||||
im) set_env_value "$ROOT_DIR/.env" "ENABLE_IM" "true" ;;
|
||||
push) set_env_value "$ROOT_DIR/.env" "ENABLE_PUSH" "true" ;;
|
||||
update) set_env_value "$ROOT_DIR/.env" "ENABLE_UPDATE" "true" ;;
|
||||
license) set_env_value "$ROOT_DIR/.env" "ENABLE_LICENSE" "true" ;;
|
||||
*) fail_json "XUQM_PRIVATE_1002" "unknown service: $SERVICE" "enable-service" ;;
|
||||
esac
|
||||
|
||||
set_env_value "$ROOT_DIR/.env" "COMPOSE_PROFILES" "$(add_profile "${COMPOSE_PROFILES:-base}" "$SERVICE")"
|
||||
"$ROOT_DIR/scripts/render-config.sh"
|
||||
|
||||
audit "enable-service" "DONE" "$SERVICE"
|
||||
progress "enable-service" "DONE" "$SERVICE"
|
||||
17
scripts/export-offline-bundle.sh
可执行文件
17
scripts/export-offline-bundle.sh
可执行文件
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
OUT_DIR="${1:-$ROOT_DIR/dist}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
audit "export-offline-bundle" "STARTED" "$OUT_DIR"
|
||||
progress "export-offline-bundle" "STARTED" "$OUT_DIR"
|
||||
|
||||
tar --exclude='data' --exclude='dist' --exclude='.git' -czf "$OUT_DIR/xuqm-private-$(cat "$ROOT_DIR/VERSION").tar.gz" -C "$ROOT_DIR" .
|
||||
|
||||
audit "export-offline-bundle" "DONE" "$OUT_DIR"
|
||||
progress "export-offline-bundle" "DONE" "$OUT_DIR"
|
||||
|
||||
34
scripts/healthcheck.sh
可执行文件
34
scripts/healthcheck.sh
可执行文件
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
load_env
|
||||
|
||||
audit "healthcheck" "STARTED" "running checks"
|
||||
progress "healthcheck" "STARTED" "running checks"
|
||||
|
||||
require_cmd docker
|
||||
|
||||
STATUS="UP"
|
||||
WARNINGS="[]"
|
||||
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
STATUS="DOWN"
|
||||
fi
|
||||
|
||||
cat > "$ROOT_DIR/.deploy-state/last-healthcheck.json" <<EOF
|
||||
{
|
||||
"status": "$STATUS",
|
||||
"version": "${PRIVATE_VERSION:-2026.05.18-private.1}",
|
||||
"mysql": {"mode": "${MYSQL_MODE:-external}", "status": "UNKNOWN"},
|
||||
"redis": {"mode": "${REDIS_MODE:-external}", "status": "UNKNOWN"},
|
||||
"warnings": $WARNINGS
|
||||
}
|
||||
EOF
|
||||
|
||||
audit "healthcheck" "$STATUS" "healthcheck finished"
|
||||
progress "healthcheck" "$STATUS" "healthcheck finished"
|
||||
|
||||
[ "$STATUS" = "UP" ] || fail_json "XUQM_PRIVATE_4001" "docker is not available" "healthcheck"
|
||||
|
||||
25
scripts/install-mysql.sh
可执行文件
25
scripts/install-mysql.sh
可执行文件
@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
load_env
|
||||
|
||||
[ "${MYSQL_MODE:-external}" = "managed" ] || exit 0
|
||||
|
||||
audit "install-mysql" "STARTED" "managed mysql"
|
||||
progress "install-mysql" "STARTED" "managed mysql"
|
||||
|
||||
require_cmd docker
|
||||
ensure_secret_file
|
||||
|
||||
MYSQL_PASSWORD="$(ensure_env_value "$ROOT_DIR/config/secrets.env" "MYSQL_PASSWORD" "${MYSQL_PASSWORD:-}" "$(random_secret)")"
|
||||
MYSQL_ROOT_PASSWORD="$(ensure_env_value "$ROOT_DIR/config/secrets.env" "MYSQL_ROOT_PASSWORD" "${MYSQL_ROOT_PASSWORD:-}" "$(random_secret)")"
|
||||
load_env
|
||||
|
||||
export MYSQL_ROOT_PASSWORD MYSQL_DATABASE MYSQL_USERNAME MYSQL_PASSWORD MYSQL_PORT
|
||||
|
||||
COMPOSE_PROFILES=infra-mysql compose up -d mysql
|
||||
|
||||
audit "install-mysql" "DONE" "managed mysql started"
|
||||
progress "install-mysql" "DONE" "managed mysql started"
|
||||
23
scripts/install-redis.sh
可执行文件
23
scripts/install-redis.sh
可执行文件
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
load_env
|
||||
|
||||
[ "${REDIS_MODE:-external}" = "managed" ] || exit 0
|
||||
|
||||
audit "install-redis" "STARTED" "managed redis"
|
||||
progress "install-redis" "STARTED" "managed redis"
|
||||
|
||||
require_cmd docker
|
||||
ensure_secret_file
|
||||
|
||||
REDIS_PASSWORD="$(ensure_env_value "$ROOT_DIR/config/secrets.env" "REDIS_PASSWORD" "${REDIS_PASSWORD:-}" "$(random_secret)")"
|
||||
load_env
|
||||
|
||||
export REDIS_PASSWORD REDIS_PORT
|
||||
COMPOSE_PROFILES=infra-redis compose up -d redis
|
||||
|
||||
audit "install-redis" "DONE" "managed redis started"
|
||||
progress "install-redis" "DONE" "managed redis started"
|
||||
109
scripts/install.sh
可执行文件
109
scripts/install.sh
可执行文件
@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
load_env
|
||||
|
||||
if [ ! -f "$ROOT_DIR/.env" ]; then
|
||||
cp "$ROOT_DIR/.env.example" "$ROOT_DIR/.env"
|
||||
fi
|
||||
ensure_secret_file
|
||||
load_env
|
||||
|
||||
PROFILE="${COMPOSE_PROFILES:-base}"
|
||||
OFFLINE_BUNDLE=""
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./scripts/install.sh [options]
|
||||
|
||||
Options:
|
||||
--profile <profiles> Docker Compose profiles, for example: base,im,push,update,license
|
||||
--mysql-mode <mode> external or managed
|
||||
--redis-mode <mode> external or managed
|
||||
--offline-bundle <path> Load images from an offline bundle before deployment
|
||||
-h, --help Show help
|
||||
EOF
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--profile)
|
||||
PROFILE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--profile=*)
|
||||
PROFILE="${1#--profile=}"
|
||||
shift
|
||||
;;
|
||||
--mysql-mode)
|
||||
MYSQL_MODE="${2:-}"
|
||||
set_env_value "$ROOT_DIR/.env" "MYSQL_MODE" "$MYSQL_MODE"
|
||||
set_env_value "$ROOT_DIR/config/infra.env" "MYSQL_MODE" "$MYSQL_MODE"
|
||||
shift 2
|
||||
;;
|
||||
--mysql-mode=*)
|
||||
MYSQL_MODE="${1#--mysql-mode=}"
|
||||
set_env_value "$ROOT_DIR/.env" "MYSQL_MODE" "$MYSQL_MODE"
|
||||
set_env_value "$ROOT_DIR/config/infra.env" "MYSQL_MODE" "$MYSQL_MODE"
|
||||
shift
|
||||
;;
|
||||
--redis-mode)
|
||||
REDIS_MODE="${2:-}"
|
||||
set_env_value "$ROOT_DIR/.env" "REDIS_MODE" "$REDIS_MODE"
|
||||
set_env_value "$ROOT_DIR/config/infra.env" "REDIS_MODE" "$REDIS_MODE"
|
||||
shift 2
|
||||
;;
|
||||
--redis-mode=*)
|
||||
REDIS_MODE="${1#--redis-mode=}"
|
||||
set_env_value "$ROOT_DIR/.env" "REDIS_MODE" "$REDIS_MODE"
|
||||
set_env_value "$ROOT_DIR/config/infra.env" "REDIS_MODE" "$REDIS_MODE"
|
||||
shift
|
||||
;;
|
||||
--offline-bundle)
|
||||
OFFLINE_BUNDLE="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--offline-bundle=*)
|
||||
OFFLINE_BUNDLE="${1#--offline-bundle=}"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
fail_json "XUQM_PRIVATE_4002" "unknown install option: $1" "install"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$PROFILE" ] || fail_json "XUQM_PRIVATE_4003" "profile cannot be empty" "install"
|
||||
set_env_value "$ROOT_DIR/.env" "COMPOSE_PROFILES" "$PROFILE"
|
||||
|
||||
audit "install" "STARTED" "profile=$PROFILE"
|
||||
progress "install" "STARTED" "profile=$PROFILE"
|
||||
|
||||
require_cmd docker
|
||||
|
||||
if [ -n "$OFFLINE_BUNDLE" ]; then
|
||||
[ -f "$OFFLINE_BUNDLE" ] || fail_json "XUQM_PRIVATE_4004" "offline bundle not found: $OFFLINE_BUNDLE" "install"
|
||||
docker load -i "$OFFLINE_BUNDLE"
|
||||
fi
|
||||
|
||||
if [ "${MYSQL_MODE:-external}" = "managed" ]; then
|
||||
"$ROOT_DIR/scripts/install-mysql.sh"
|
||||
fi
|
||||
|
||||
if [ "${REDIS_MODE:-external}" = "managed" ]; then
|
||||
"$ROOT_DIR/scripts/install-redis.sh"
|
||||
fi
|
||||
|
||||
"$ROOT_DIR/scripts/render-config.sh"
|
||||
|
||||
COMPOSE_PROFILES="$PROFILE" compose up -d
|
||||
"$ROOT_DIR/scripts/healthcheck.sh"
|
||||
|
||||
audit "install" "DONE" "profile=$PROFILE"
|
||||
progress "install" "DONE" "profile=$PROFILE"
|
||||
136
scripts/lib.sh
可执行文件
136
scripts/lib.sh
可执行文件
@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
AUDIT_LOG="$ROOT_DIR/logs/audit.log"
|
||||
PROGRESS_FILE="$ROOT_DIR/.deploy-state/progress.md"
|
||||
|
||||
now() {
|
||||
date +"%Y-%m-%dT%H:%M:%S%z"
|
||||
}
|
||||
|
||||
audit() {
|
||||
local action="$1"
|
||||
local status="$2"
|
||||
local message="${3:-}"
|
||||
mkdir -p "$(dirname "$AUDIT_LOG")"
|
||||
printf '{"time":"%s","action":"%s","status":"%s","message":"%s"}\n' \
|
||||
"$(now)" "$action" "$status" "$message" >> "$AUDIT_LOG"
|
||||
}
|
||||
|
||||
progress() {
|
||||
local step="$1"
|
||||
local status="$2"
|
||||
local notes="${3:-}"
|
||||
mkdir -p "$(dirname "$PROGRESS_FILE")"
|
||||
if [ ! -f "$PROGRESS_FILE" ]; then
|
||||
printf '# Deployment Progress\n\n| Time | Step | Status | Notes |\n|------|------|--------|-------|\n' > "$PROGRESS_FILE"
|
||||
fi
|
||||
printf '| %s | %s | %s | %s |\n' "$(now)" "$step" "$status" "$notes" >> "$PROGRESS_FILE"
|
||||
}
|
||||
|
||||
fail_json() {
|
||||
local code="$1"
|
||||
local message="$2"
|
||||
local step="${3:-unknown}"
|
||||
printf '{"code":"%s","message":"%s","step":"%s","recoverable":true}\n' "$code" "$message" "$step" >&2
|
||||
audit "$step" "FAILED" "$message"
|
||||
progress "$step" "FAILED" "$message"
|
||||
exit 1
|
||||
}
|
||||
|
||||
load_env() {
|
||||
set -a
|
||||
[ -f "$ROOT_DIR/.env" ] && . "$ROOT_DIR/.env"
|
||||
[ -f "$ROOT_DIR/config/infra.env" ] && . "$ROOT_DIR/config/infra.env"
|
||||
[ -f "$ROOT_DIR/config/xuqm.env" ] && . "$ROOT_DIR/config/xuqm.env"
|
||||
[ -f "$ROOT_DIR/config/secrets.env" ] && . "$ROOT_DIR/config/secrets.env"
|
||||
set +a
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || fail_json "XUQM_PRIVATE_4001" "missing required command: $1" "preflight"
|
||||
}
|
||||
|
||||
random_secret() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -base64 32 | tr -d '\n'
|
||||
else
|
||||
printf '%s_%s' "$(date +%s)" "$RANDOM"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_secret_file() {
|
||||
if [ ! -f "$ROOT_DIR/config/secrets.env" ]; then
|
||||
cp "$ROOT_DIR/config/secrets.env.example" "$ROOT_DIR/config/secrets.env"
|
||||
chmod 600 "$ROOT_DIR/config/secrets.env" || true
|
||||
fi
|
||||
}
|
||||
|
||||
set_env_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local value="$3"
|
||||
mkdir -p "$(dirname "$file")"
|
||||
touch "$file"
|
||||
if grep -q "^${key}=" "$file"; then
|
||||
sed -i.bak "s|^${key}=.*|${key}=${value}|" "$file"
|
||||
rm -f "${file}.bak"
|
||||
else
|
||||
printf '%s=%s\n' "$key" "$value" >> "$file"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_env_value() {
|
||||
local file="$1"
|
||||
local key="$2"
|
||||
local current="${3:-}"
|
||||
local generated="$4"
|
||||
if [ -z "$current" ] || [ "$current" = "change-me" ]; then
|
||||
set_env_value "$file" "$key" "$generated"
|
||||
printf '%s' "$generated"
|
||||
else
|
||||
printf '%s' "$current"
|
||||
fi
|
||||
}
|
||||
|
||||
profile_contains() {
|
||||
local profiles="$1"
|
||||
local target="$2"
|
||||
case ",${profiles}," in
|
||||
*,"${target}",*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
add_profile() {
|
||||
local profiles="${1:-base}"
|
||||
local target="$2"
|
||||
if profile_contains "$profiles" "$target"; then
|
||||
printf '%s' "$profiles"
|
||||
else
|
||||
printf '%s,%s' "$profiles" "$target"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_profile() {
|
||||
local profiles="$1"
|
||||
local target="$2"
|
||||
local result=""
|
||||
local item
|
||||
IFS=',' read -r -a items <<< "$profiles"
|
||||
for item in "${items[@]}"; do
|
||||
[ "$item" = "$target" ] && continue
|
||||
[ -z "$item" ] && continue
|
||||
if [ -z "$result" ]; then
|
||||
result="$item"
|
||||
else
|
||||
result="$result,$item"
|
||||
fi
|
||||
done
|
||||
printf '%s' "${result:-base}"
|
||||
}
|
||||
|
||||
compose() {
|
||||
docker compose --env-file "$ROOT_DIR/.env" -f "$ROOT_DIR/docker-compose.yml" -f "$ROOT_DIR/docker-compose.infra.yml" "$@"
|
||||
}
|
||||
99
scripts/render-config.sh
可执行文件
99
scripts/render-config.sh
可执行文件
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
load_env
|
||||
|
||||
strip_protocol() {
|
||||
local value="$1"
|
||||
value="${value#https://}"
|
||||
value="${value#http://}"
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
ws_url() {
|
||||
local value="$1"
|
||||
local host
|
||||
host="$(strip_protocol "$value")"
|
||||
if [ -z "$host" ]; then
|
||||
printf ''
|
||||
else
|
||||
printf 'wss://%s/ws/im' "$host"
|
||||
fi
|
||||
}
|
||||
|
||||
audit "render-config" "STARTED" "rendering runtime files"
|
||||
progress "render-config" "STARTED" "rendering runtime files"
|
||||
|
||||
mkdir -p "$ROOT_DIR/config/docs" "$ROOT_DIR/config/sdk"
|
||||
|
||||
cat > "$ROOT_DIR/config/docs/docs-runtime.json" <<EOF
|
||||
{
|
||||
"deployment": "PRIVATE",
|
||||
"privateVersion": "${PRIVATE_VERSION:-2026.05.18-private.1}",
|
||||
"domains": {
|
||||
"console": "${CONSOLE_DOMAIN:-}",
|
||||
"docs": "${DOCS_DOMAIN:-}",
|
||||
"file": "${FILE_DOMAIN:-}",
|
||||
"im": "${IM_DOMAIN:-}",
|
||||
"update": "${UPDATE_DOMAIN:-}",
|
||||
"license": "${LICENSE_DOMAIN:-}",
|
||||
"push": "${PUSH_DOMAIN:-}"
|
||||
},
|
||||
"features": {
|
||||
"file": ${ENABLE_FILE:-true},
|
||||
"im": ${ENABLE_IM:-false},
|
||||
"push": ${ENABLE_PUSH:-false},
|
||||
"update": ${ENABLE_UPDATE:-false},
|
||||
"license": ${ENABLE_LICENSE:-false}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$ROOT_DIR/config/sdk/xuqm-private-sdk.json" <<EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"configVersion": "${PRIVATE_VERSION:-2026.05.18-private.1}",
|
||||
"deployment": "PRIVATE",
|
||||
"appKey": "${TENANT_BOOTSTRAP_APP_KEY:-ak_private_default}",
|
||||
"controlBaseUrl": "${CONSOLE_DOMAIN:-}",
|
||||
"fileBaseUrl": "${FILE_DOMAIN:-}",
|
||||
"imApiBaseUrl": "${IM_DOMAIN:-}",
|
||||
"imWsUrl": "$(ws_url "${IM_DOMAIN:-}")",
|
||||
"pushBaseUrl": "${PUSH_DOMAIN:-}",
|
||||
"updateBaseUrl": "${UPDATE_DOMAIN:-}",
|
||||
"licenseBaseUrl": "${LICENSE_DOMAIN:-}",
|
||||
"docsBaseUrl": "${DOCS_DOMAIN:-}",
|
||||
"features": {
|
||||
"file": ${ENABLE_FILE:-true},
|
||||
"im": ${ENABLE_IM:-false},
|
||||
"push": ${ENABLE_PUSH:-false},
|
||||
"update": ${ENABLE_UPDATE:-false},
|
||||
"license": ${ENABLE_LICENSE:-false}
|
||||
},
|
||||
"connectTimeoutMs": 10000,
|
||||
"readTimeoutMs": 30000,
|
||||
"logLevel": "WARN"
|
||||
}
|
||||
EOF
|
||||
|
||||
cat > "$ROOT_DIR/.deploy-state/current.json" <<EOF
|
||||
{
|
||||
"privateVersion": "${PRIVATE_VERSION:-2026.05.18-private.1}",
|
||||
"profiles": ["${COMPOSE_PROFILES:-base}"],
|
||||
"mysql": {"mode": "${MYSQL_MODE:-external}", "status": "UNKNOWN"},
|
||||
"redis": {"mode": "${REDIS_MODE:-external}", "status": "UNKNOWN"},
|
||||
"services": {
|
||||
"file": ${ENABLE_FILE:-true},
|
||||
"im": ${ENABLE_IM:-false},
|
||||
"push": ${ENABLE_PUSH:-false},
|
||||
"update": ${ENABLE_UPDATE:-false},
|
||||
"license": ${ENABLE_LICENSE:-false}
|
||||
},
|
||||
"lastHealthcheck": null
|
||||
}
|
||||
EOF
|
||||
|
||||
audit "render-config" "DONE" "runtime files rendered"
|
||||
progress "render-config" "DONE" "runtime files rendered"
|
||||
8
scripts/restore.sh
可执行文件
8
scripts/restore.sh
可执行文件
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
fail_json "XUQM_PRIVATE_4003" "restore is not implemented yet; use backup manifest manually" "restore"
|
||||
|
||||
8
scripts/rollback.sh
可执行文件
8
scripts/rollback.sh
可执行文件
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
fail_json "XUQM_PRIVATE_4003" "rollback implementation pending release history" "rollback"
|
||||
|
||||
11
scripts/upgrade.sh
可执行文件
11
scripts/upgrade.sh
可执行文件
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
. "$ROOT_DIR/scripts/lib.sh"
|
||||
|
||||
audit "upgrade" "STARTED" "${1:-}"
|
||||
progress "upgrade" "STARTED" "${1:-}"
|
||||
"$ROOT_DIR/scripts/backup.sh"
|
||||
fail_json "XUQM_PRIVATE_4003" "upgrade implementation pending compatibility matrix" "upgrade"
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
# 客户交付记录
|
||||
|
||||
## 基础信息
|
||||
|
||||
- 客户:
|
||||
- 部署日期:
|
||||
- 私有化版本:
|
||||
- 镜像版本:
|
||||
- 部署模式:external / managed
|
||||
|
||||
## 域名
|
||||
|
||||
- 控制台:
|
||||
- 运营后台:
|
||||
- 文档站:
|
||||
- 文件服务:
|
||||
- IM:
|
||||
- Push:
|
||||
- Update:
|
||||
- License:
|
||||
|
||||
## 可选服务
|
||||
|
||||
- IM:未部署 / 已部署
|
||||
- Push:未部署 / 已部署
|
||||
- Update:未部署 / 已部署
|
||||
- License:未部署 / 已部署
|
||||
|
||||
## 验收
|
||||
|
||||
- 基础部署:
|
||||
- 单租户初始化:
|
||||
- 文档站 SDK 引导:
|
||||
- 厂商 Push:
|
||||
- 应用市场自动发布:
|
||||
- 备份恢复:
|
||||
24
templates/env.external.tpl
普通文件
24
templates/env.external.tpl
普通文件
@ -0,0 +1,24 @@
|
||||
PRIVATE_VERSION=2026.05.18-private.1
|
||||
REGISTRY=registry.example.com/xuqm
|
||||
IMAGE_TAG=2026.05.18-private.1
|
||||
COMPOSE_PROFILES=base
|
||||
|
||||
MYSQL_MODE=external
|
||||
MYSQL_HOST=mysql.customer.internal
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=xuqm_private
|
||||
MYSQL_USERNAME=xuqm
|
||||
|
||||
REDIS_MODE=external
|
||||
REDIS_HOST=redis.customer.internal
|
||||
REDIS_PORT=6379
|
||||
REDIS_DATABASE=0
|
||||
|
||||
CONSOLE_DOMAIN=https://console.customer.com
|
||||
OPS_DOMAIN=https://ops.customer.com
|
||||
DOCS_DOMAIN=https://docs.customer.com
|
||||
FILE_DOMAIN=https://file.customer.com
|
||||
IM_DOMAIN=https://im.customer.com
|
||||
UPDATE_DOMAIN=https://update.customer.com
|
||||
LICENSE_DOMAIN=https://license.customer.com
|
||||
PUSH_DOMAIN=https://push.customer.com
|
||||
24
templates/env.managed.tpl
普通文件
24
templates/env.managed.tpl
普通文件
@ -0,0 +1,24 @@
|
||||
PRIVATE_VERSION=2026.05.18-private.1
|
||||
REGISTRY=registry.example.com/xuqm
|
||||
IMAGE_TAG=2026.05.18-private.1
|
||||
COMPOSE_PROFILES=base
|
||||
|
||||
MYSQL_MODE=managed
|
||||
MYSQL_HOST=mysql
|
||||
MYSQL_PORT=3306
|
||||
MYSQL_DATABASE=xuqm_private
|
||||
MYSQL_USERNAME=xuqm
|
||||
|
||||
REDIS_MODE=managed
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_DATABASE=0
|
||||
|
||||
CONSOLE_DOMAIN=https://console.customer.com
|
||||
OPS_DOMAIN=https://ops.customer.com
|
||||
DOCS_DOMAIN=https://docs.customer.com
|
||||
FILE_DOMAIN=https://file.customer.com
|
||||
IM_DOMAIN=https://im.customer.com
|
||||
UPDATE_DOMAIN=https://update.customer.com
|
||||
LICENSE_DOMAIN=https://license.customer.com
|
||||
PUSH_DOMAIN=https://push.customer.com
|
||||
正在加载...
在新工单中引用
屏蔽一个用户