Files
update-server/scripts/package-offline-server.sh
T

615 lines
20 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
VERSION="0.1.0"
OUTPUT_DIR=""
SKIP_BUILD=0
SKIP_PULL=0
usage() {
cat <<'EOF'
Usage: ./scripts/package-offline-server.sh [options]
Options:
--version VERSION Image/package version, default: 0.1.0
--output-dir DIR Output package directory, default: ./dist/SimCAEServerDockerPackage
--skip-build Do not build simcae-update-server image
--skip-pull Do not pull MinIO images
-h, --help Show this help
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--version)
VERSION="${2:?--version requires a value}"
shift 2
;;
--output-dir)
OUTPUT_DIR="${2:?--output-dir requires a value}"
shift 2
;;
--skip-build)
SKIP_BUILD=1
shift
;;
--skip-pull)
SKIP_PULL=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
esac
done
require_file() {
local path="$1"
local message="$2"
if [[ ! -e "$path" ]]; then
echo "$message" >&2
exit 1
fi
}
require_cmd() {
local name="$1"
if ! command -v "$name" >/dev/null 2>&1; then
echo "Required command is missing: $name" >&2
exit 1
fi
}
require_cmd docker
require_cmd tar
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
if [[ -z "$OUTPUT_DIR" ]]; then
OUTPUT_DIR="$PROJECT_ROOT/dist/SimCAEServerDockerPackage"
fi
IMAGE_NAME="simcae-update-server:$VERSION"
MINIO_IMAGE="minio/minio:RELEASE.2025-04-22T22-12-26Z"
MC_IMAGE="minio/mc:RELEASE.2025-04-16T18-13-26Z"
PACKAGE_DIR="$OUTPUT_DIR"
IMAGES_DIR="$PACKAGE_DIR/images"
KEYS_DIR="$PACKAGE_DIR/keys"
TAR_FILE="$IMAGES_DIR/simcae-server-all-images_$VERSION.tar"
ARCHIVE_FILE="$PACKAGE_DIR.tar.gz"
require_file "$PROJECT_ROOT/Dockerfile" "Dockerfile is missing."
require_file "$PROJECT_ROOT/docker-compose.image.yml" "docker-compose.image.yml is missing."
require_file "$PROJECT_ROOT/.env.docker.example" ".env.docker.example is missing."
require_file "$PROJECT_ROOT/admin-ui/package.json" "admin-ui/package.json is missing."
require_file "$PROJECT_ROOT/keys/manifest_private_key.pem" "keys/manifest_private_key.pem is missing."
require_file "$PROJECT_ROOT/keys/manifest_public_key.pem" "keys/manifest_public_key.pem is missing."
if [[ "$SKIP_BUILD" -eq 0 ]]; then
docker build -t "$IMAGE_NAME" -f "$PROJECT_ROOT/Dockerfile" "$PROJECT_ROOT"
fi
if [[ "$SKIP_PULL" -eq 0 ]]; then
docker pull "$MINIO_IMAGE"
docker pull "$MC_IMAGE"
fi
rm -rf "$PACKAGE_DIR"
mkdir -p "$IMAGES_DIR" "$KEYS_DIR"
cp "$PROJECT_ROOT/docker-compose.image.yml" "$PACKAGE_DIR/docker-compose.yml"
cp "$PROJECT_ROOT/.env.docker.example" "$PACKAGE_DIR/.env.example"
cp "$PROJECT_ROOT/keys/manifest_private_key.pem" "$KEYS_DIR/manifest_private_key.pem"
cp "$PROJECT_ROOT/keys/manifest_public_key.pem" "$KEYS_DIR/manifest_public_key.pem"
cat > "$PACKAGE_DIR/README.md" <<'EOF_README'
# SimCAE 服务端 Docker 打包和部署指南
本文从“维护者打包”到“使用者部署”完整说明 SimCAE 服务端 Docker 包的交付流程。
服务端 Docker 包用于把 SimCAE 自动升级服务端部署到你的服务器。包里已经包含后端 API、pure-adminVue3 + Element Plus)管理后台、MinIO 对象存储、初始化工具、配置模板、签名密钥和 Docker 编排文件;目标服务器即使不能联网,也可以按本文完成部署。
如果你是维护者,要先按“一、维护者:生成 Docker 离线部署包”打包。如果你已经拿到 `SimCAEServerDockerPackage.tar.gz`,直接从“三、使用者:最小部署流程”开始。
## 先看这里:你要做哪件事
| 你的目标 | 直接看哪一节 |
| --- | --- |
| 从源码重新生成离线部署包 | 一、维护者:生成 Docker 离线部署包 |
| 了解交付包里有哪些文件 | 二、这个包里面有什么 |
| 在服务器上部署并启动 | 三、使用者:最小部署流程 |
| 修改 `.env` 配置 | 四、.env 配置怎么填 |
| 发布 Windows/Linux 客户端版本 | 六、发布新版本 |
| 查看日志、重启、备份、升级 | 七、常用命令 |
## 一、维护者:生成 Docker 离线部署包
在服务端源码所在机器上执行。通常这台机器是 Ubuntu,并且已经安装 Docker。
进入服务端源码目录:
```bash
cd update-server
```
执行打包脚本:
```bash
bash ./scripts/package-offline-server.sh \
--version __VERSION__ \
--output-dir ./dist/SimCAEServerDockerPackage
```
脚本会做这些事情:
```text
1. 构建 simcae-update-server:__VERSION__ 镜像。
2. 拉取 MinIO 和 MinIO Client 镜像。
3. 复制 docker-compose.yml、.env.example、签名密钥和部署说明。
4. 把 API、MinIO、MinIO Client 三个镜像保存到 images/simcae-server-all-images___VERSION__.tar。
5. 生成完整目录 dist/SimCAEServerDockerPackage/。
6. 生成最终交付压缩包 dist/SimCAEServerDockerPackage.tar.gz。
```
打包完成后,重点确认这两个输出:
```text
dist/SimCAEServerDockerPackage/
dist/SimCAEServerDockerPackage.tar.gz
```
通常只需要把下面这个文件发给部署人员或拷贝到目标服务器:
```text
dist/SimCAEServerDockerPackage.tar.gz
```
如果只是重新打包已有镜像,可以使用:
```bash
bash ./scripts/package-offline-server.sh \
--version __VERSION__ \
--output-dir ./dist/SimCAEServerDockerPackage \
--skip-build
```
如果打包机器不能联网,但 MinIO 镜像已经提前存在本机,可以使用:
```bash
bash ./scripts/package-offline-server.sh \
--version __VERSION__ \
--output-dir ./dist/SimCAEServerDockerPackage \
--skip-pull
```
注意:`keys/manifest_private_key.pem` 会被放进部署包,用于服务端发布版本时签名 Manifest。这个私钥必须保护好,不要公开上传。
## 二、这个包里面有什么
解压后目录结构如下:
```text
SimCAEServerDockerPackage/
README.md 当前说明文档
docker-compose.yml 容器编排文件
.env.example 环境变量模板
load-images.sh 导入 Docker 镜像并创建 .env 的脚本
images/
simcae-server-all-images___VERSION__.tar Docker 镜像包,包含 api、minio、minio-init
keys/
manifest_private_key.pem 服务端 Manifest 签名私钥
manifest_public_key.pem 与客户端配套的验签公钥
```
这些文件的关系可以这样理解:
```text
images/*.tar 程序镜像包,相当于安装材料
docker-compose.yml 容器部署图纸,说明启动哪些服务、端口怎么映射、目录怎么挂载
.env 你的实际配置,第一次运行 load-images.sh 时由 .env.example 生成
keys/ 发布版本时用于签名 Manifest,客户端用对应公钥验签
runtime/ 启动后自动生成,保存服务端数据库、上传临时文件、崩溃报告等
minio_data/ 启动后自动生成,保存升级包文件
```
## 三、使用者:最小部署流程
先把 `SimCAEServerDockerPackage.tar.gz` 拷贝到要部署的 Ubuntu 服务器上,然后执行:
```bash
tar -xzf SimCAEServerDockerPackage.tar.gz
cd SimCAEServerDockerPackage
pwd
ls
```
你需要确认当前目录就是解压后的 `SimCAEServerDockerPackage` 目录,并且能看到:
```text
docker-compose.yml
.env.example
load-images.sh
images/
keys/
```
后面所有 `docker compose` 命令都要在这个目录执行,因为 `docker-compose.yml` 和 `.env` 都在这里。
继续在 `SimCAEServerDockerPackage` 目录执行:
```bash
bash ./load-images.sh
```
这个脚本会做两件事:
```text
1. docker load 导入 images/simcae-server-all-images___VERSION__.tar 里的镜像
2. 如果当前目录没有 .env,就从 .env.example 复制一份 .env
3. 自动创建 runtime/、minio_data/ 等运行目录,并尽量修正目录权限
```
然后修改 `.env`
```bash
nano .env
```
最少只需要把 `MINIO_PUBLIC_ENDPOINT` 改成客户端能访问到的服务器地址:
```text
MINIO_PUBLIC_ENDPOINT=http://你的服务器IP:9000
```
例如服务器 IP 是 `192.168.229.128`
```text
MINIO_PUBLIC_ENDPOINT=http://192.168.229.128:9000
```
启动服务:
```bash
docker compose up -d
```
查看状态和后端日志:
```bash
docker compose ps
docker compose logs -f api
```
浏览器访问:
```text
后台/API: http://你的服务器IP:8000/
MinIO 控制台: http://你的服务器IP:9001/
```
如果服务器开启防火墙,至少放行:
```text
8000 后台/API
9000 客户端下载升级文件
9001 MinIO 控制台,可选
```
## 四、.env 配置怎么填
`.env.example` 里已经给了一组能直接试跑的默认值。下面按重要程度说明。
### 必须确认或修改
| 字段 | 是否必填 | 默认能否直接用 | 怎么填 |
| --- | --- | --- | --- |
| `MINIO_PUBLIC_ENDPOINT` | 必填 | 不能直接用于正式环境 | 改成客户端能访问到的 MinIO 地址,格式是 `http://服务器IP:9000`。客户端会用这个地址下载升级文件。 |
| `SERVER_PORT` | 必填 | 可以 | 后台/API 端口,默认 `8000`。如果服务器 8000 被占用,可以改成其他端口。 |
| `PUBLIC_API_BASE_URL` | 可不填 | 可以 | 管理页生成 qrc 服务端配置 `server_config.json` 时使用的后端 API 地址。不填时自动使用当前访问后台的地址;如果经过域名、反向代理或端口映射,建议填成客户端实际能访问的地址,例如 `http://服务器IP:8000`。 |
| `RELEASE_MAIN_EXECUTABLE` | 必填 | SimCAE 默认可以 | 发布包里主程序的相对路径。Windows 示例:`bin/SimCAE.exe`Linux 示例:`bin/SimCAE`。 |
| `CLIENT_API_TOKEN` | 必填 | 可以 | 客户端访问服务端 API 的令牌。必须和客户端 `config/app_config.json` 里的 `client_token` 完全一致。 |
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` | 必填 | 可以 | 管理后台初始用户名和密码。首次启动且数据库里没有管理员用户时,会自动创建这个账号。网页登录时输入这组账号密码。 |
| `ADMIN_JWT_SECRET` | 必填 | 可以 | 管理后台 JWT 签名密钥。正式部署建议改成随机长字符串,并长期保持不变;改掉后旧登录 token 会失效。 |
| `ADMIN_TOKEN` | 必填 | 可以 | 管理后台兼容令牌。新后台默认不用它登录;它仍用于旧脚本兼容、JWT_SECRET 未设置时的默认签名密钥,以及 `CRASH_ADMIN_TOKEN` 为空时的崩溃报告管理兜底令牌。 |
| `LICENSE_KEY_ENCRYPTION_SECRET` | 必填 | 可以 | 后台授权列表显示 License Key 时使用的加密密钥。正式部署建议修改,并且部署后长期保持不变;如果后续改掉它,旧 License 仍可用于客户端校验,但后台无法再显示旧 License Key 原文。 |
| `MINIO_ACCESS_KEY` | 必填 | 可以 | MinIO 用户名。默认可试跑,正式环境建议改。 |
| `MINIO_SECRET_KEY` | 必填 | 可以 | MinIO 密码。默认可试跑,正式环境建议改。 |
### 通常不用改
| 字段 | 作用 | 默认值说明 |
| --- | --- | --- |
| `SIMCAE_UPDATE_SERVER_IMAGE` | API 镜像名称 | 打包脚本会自动写成当前版本,例如 `simcae-update-server:__VERSION__`。 |
| `APP_UID` / `APP_GID` | API 容器写入 `runtime/` 时使用的用户 ID | `load-images.sh` 会尽量自动改成当前服务器用户。遇到权限问题时再检查。 |
| `SERVICE_TITLE` | 管理后台标题 | 默认 `SimCAE Update Service`。 |
| `CORS_ALLOW_ORIGINS` | 跨域来源 | 内网部署保持 `*` 即可。 |
| `TARGET_PLATFORM` / `TARGET_ARCH` | 发布包目标平台 | Windows x64 填 `windows` / `x64`Linux x64 填 `linux` / `x64`。 |
| `SIGNING_KEY_ID` | Manifest 签名密钥编号 | 默认 `manifest-key-v1`。只有更换签名体系时才需要改。 |
| `MINIO_API_PORT` | MinIO 文件下载端口 | 默认 `9000`。客户端下载升级文件要能访问这个端口。 |
| `MINIO_CONSOLE_PORT` | MinIO 控制台端口 | 默认 `9001`。不需要控制台时可以不开放到外部。 |
| `MINIO_BUCKET` | MinIO 存储桶名 | 默认 `updates`。 |
| `SIGN_EXPIRE_MIN` | 升级文件下载链接有效期 | 默认 `60` 分钟。 |
| `MINIO_CONNECT_TIMEOUT_SEC` / `MINIO_READ_TIMEOUT_SEC` / `MINIO_RETRY_TOTAL` / `MINIO_HEALTH_TIMEOUT_SEC` | MinIO 连接超时与重试 | 默认适合内网部署。 |
### 发布保护参数
这些字段用于防止上传超大目录导致服务器磁盘、内存压力过大。默认一般不用改。
| 字段 | 作用 | 默认值 |
| --- | --- | --- |
| `PUBLISH_MAX_REQUEST_MB` | 单次发布请求最大大小,单位 MB | `4096` |
| `PUBLISH_MAX_FILES` | 单次发布最多文件数 | `20000` |
| `PUBLISH_MAX_FIELDS` | 表单字段数上限,通常要大于文件数 | `20100` |
| `UPLOAD_SPACE_RESERVE_MB` | 上传时额外保留的磁盘空间,单位 MB | `256` |
如果正式发布目录超过 4GB,可以在确认服务器磁盘空间足够后调大 `PUBLISH_MAX_REQUEST_MB`。如果页面提示空间不足,优先清理旧版本、扩容磁盘,或把服务端部署到更大的数据盘。
### 崩溃报告接口参数
| 字段 | 是否必填 | 默认能否直接用 | 怎么填 |
| --- | --- | --- | --- |
| `CRASH_SERVICE_VERSION` | 必填 | 可以 | 崩溃报告接口版本,默认 `1.0.0`。 |
| `CRASH_REPORT_TOKEN` | 必填 | 可以 | 客户端上传崩溃报告时使用的令牌。正式环境建议改。 |
| `CRASH_SYMBOL_TOKEN` | 必填 | 可以 | 上传符号文件时使用的令牌。正式环境建议改。 |
| `CRASH_ADMIN_TOKEN` | 可不填 | 可以 | 崩溃报告管理令牌。不填时使用 `ADMIN_TOKEN`。 |
| `CRASH_METADATA_MAX_KB` | 必填 | 可以 | 崩溃报告 metadata 最大大小。 |
| `CRASH_MINIDUMP_MAX_MB` | 必填 | 可以 | minidump 最大大小。 |
| `CRASH_ATTACHMENTS_MAX_MB` | 必填 | 可以 | 附件最大大小。 |
| `CRASH_REQUEST_MAX_MB` | 必填 | 可以 | 崩溃报告完整请求最大大小。 |
| `CRASH_SYMBOLS_MAX_MB` | 必填 | 可以 | 符号文件最大大小。 |
## 五、客户端配置要同步哪些值
推荐直接使用后台页面生成:
```text
1. 登录后台
2. 选择应用和渠道
3. 如需预置授权,先创建或选择一个 License,页面会自动把可查看的 License 填入“客户端配置生成”
4. 打开“客户端配置生成”
5. 点击“生成配套配置”
6. 点击“复制配置”,粘贴到客户端 `bin/config/app_config.json`
7. 点击“复制 qrc 配置”,粘贴到客户端源码 `config/server_config.json`
8. 重新编译 Launcher / Updater / Bootstrap,让 `api_base_url` 通过 qrc 编进程序
```
客户端 `config/app_config.json` 至少要和服务端保持这个值一致:
```json
{
"client_token": "和服务端 CLIENT_API_TOKEN 一样"
}
```
如果服务端 `.env` 里保持默认:
```text
CLIENT_API_TOKEN=SimCAEClientToken2026
```
客户端就填:
```json
"client_token": "SimCAEClientToken2026"
```
`api_base_url` 是后端 API 地址,走 `SERVER_PORT`,不是 MinIO 地址。它现在位于客户端源码 `config/server_config.json`,并通过 qrc 编译进程序,不再写入客户端 `app_config.json` 或注册表。
## 六、发布新版本
进入后台后,“发布新版本”支持两种方式:
```text
1. 选择软件发布根目录,例如 SimCAE 目录。Windows 目录内应包含 bin/SimCAE.exeLinux 目录内应包含 bin/SimCAE
2. 上传压缩发布包,支持 zip、tar.gz、tgz、tar.bz2、tbz2、rar
```
压缩发布包上传到服务器后,服务端会先解压,再校验是否包含 `.env` 中 `RELEASE_MAIN_EXECUTABLE` 指定的主程序路径。Windows 默认是:
```text
RELEASE_MAIN_EXECUTABLE=bin/SimCAE.exe
```
Linux 客户端部署时建议改成:
```text
TARGET_PLATFORM=linux
RELEASE_MAIN_EXECUTABLE=bin/SimCAE
CLIENT_MAIN_EXECUTABLE=SimCAE
CLIENT_LAUNCHER_EXECUTABLE=Launcher
CLIENT_UPDATER_EXECUTABLE=Updater
CLIENT_BOOTSTRAP_EXECUTABLE=Bootstrap
```
`.rar` 需要服务器镜像内有 rar 解压工具。当前 Docker 镜像已内置 `bsdtar`;如果某些 rar 变体仍然解压失败,请改用 zip/tar.gz/tar.bz2,或在服务器镜像中补充 `unrar`/`7z`。
## 七、常用命令
确认你仍然在 `SimCAEServerDockerPackage` 目录:
```bash
pwd
```
启动:
```bash
docker compose up -d
```
查看状态:
```bash
docker compose ps
```
查看后端日志:
```bash
docker compose logs -f api
```
查看所有容器日志:
```bash
docker compose logs -f
```
重启后端:
```bash
docker compose restart api
```
停止服务:
```bash
docker compose down
```
如果提示 `docker: 'compose' is not a docker command`,说明服务器没有安装 Docker Compose plugin,需要先安装它。
## 八、数据、备份和迁移
运行后会生成这些目录或文件:
```text
.env 当前服务器实际配置
runtime/ 服务端数据库、上传临时目录、崩溃报告等
minio_data/ MinIO 对象数据,也就是上传后的升级文件
keys/ Manifest 签名密钥
```
备份或迁移正式环境时,重点备份:
```text
.env
runtime/
minio_data/
keys/
docker-compose.yml
```
不要只备份容器。容器可以由镜像重新创建,真正重要的数据在上面这些挂载目录里。
## 九、常见问题
### 启动后看不到后端日志
`docker compose up -d` 是后台启动,不会持续打印日志。用下面命令看后端日志:
```bash
docker compose logs -f api
```
### API 容器反复重启,提示 Permission denied: '/data/uploads'
这是 `runtime/` 目录权限不对。进入 `SimCAEServerDockerPackage` 目录后执行:
```bash
docker compose down
mkdir -p runtime/uploads runtime/upload_spool runtime/crash_storage
sudo chown -R $(id -u):$(id -g) runtime
docker compose up -d
```
### 网页能打开,但客户端提示令牌校验失败
检查客户端 `config/app_config.json`
```json
"client_token": "..."
```
必须和服务端 `.env`
```text
CLIENT_API_TOKEN=...
```
完全一致。
### 客户端能连 API,但下载升级文件失败
检查 `.env`
```text
MINIO_PUBLIC_ENDPOINT=http://你的服务器IP:9000
```
这个地址必须从客户端能访问。还要确认服务器防火墙放行了 `9000` 端口。
## 十、安全提醒
`keys/manifest_private_key.pem` 是服务端签 Manifest 的私钥,必须保护好。客户端 SDK 里的 `manifest_public_key.pem` 必须和这个私钥配套,否则客户端会 Manifest 验签失败。
默认用户名、默认密码、默认 token 和默认 MinIO 密码可以直接试跑。正式部署建议改掉,避免多个环境共用同一套公开示例值。
EOF_README
python3 - "$PACKAGE_DIR/README.md" "$VERSION" <<'PYREADME'
from pathlib import Path
import sys
path = Path(sys.argv[1])
version = sys.argv[2]
text = path.read_text(encoding='utf-8').replace('__VERSION__', version)
path.write_text(text, encoding='utf-8')
PYREADME
cat > "$PACKAGE_DIR/load-images.sh" <<EOF
#!/usr/bin/env bash
set -euo pipefail
docker load -i ./images/simcae-server-all-images_$VERSION.tar
if [[ ! -f .env ]]; then
cp .env.example .env
sed -i "s/^APP_UID=.*/APP_UID=\$(id -u)/" .env
sed -i "s/^APP_GID=.*/APP_GID=\$(id -g)/" .env
echo "Created .env from .env.example. Edit .env before starting services."
else
echo ".env already exists. Keeping existing file."
fi
APP_UID_VALUE="\$(grep -E '^APP_UID=' .env | tail -n 1 | cut -d= -f2-)"
APP_GID_VALUE="\$(grep -E '^APP_GID=' .env | tail -n 1 | cut -d= -f2-)"
APP_UID_VALUE="\${APP_UID_VALUE:-\$(id -u)}"
APP_GID_VALUE="\${APP_GID_VALUE:-\$(id -g)}"
mkdir -p ./runtime/uploads ./runtime/upload_spool ./runtime/crash_storage ./minio_data
if [[ -w ./runtime ]]; then
chown -R "\$APP_UID_VALUE:\$APP_GID_VALUE" ./runtime 2>/dev/null || true
else
echo "Warning: ./runtime is not writable by the current user."
echo "Run: sudo chown -R \$APP_UID_VALUE:\$APP_GID_VALUE ./runtime"
fi
echo "Next: edit .env, then run: docker compose up -d"
EOF
chmod +x "$PACKAGE_DIR/load-images.sh"
python3 - "$PACKAGE_DIR/.env.example" "$IMAGE_NAME" <<'PYENV'
from pathlib import Path
import sys
path = Path(sys.argv[1])
image = sys.argv[2]
text = path.read_text(encoding='utf-8')
text = text.replace('SIMCAE_UPDATE_SERVER_IMAGE=simcae-update-server:0.1.0', f'SIMCAE_UPDATE_SERVER_IMAGE={image}')
path.write_text(text, encoding='utf-8')
PYENV
docker save -o "$TAR_FILE" "$IMAGE_NAME" "$MINIO_IMAGE" "$MC_IMAGE"
rm -f "$ARCHIVE_FILE"
tar -czf "$ARCHIVE_FILE" -C "$(dirname "$PACKAGE_DIR")" "$(basename "$PACKAGE_DIR")"
echo "Offline package directory: $PACKAGE_DIR"
echo "Offline package archive: $ARCHIVE_FILE"
echo "Images tar: $TAR_FILE"