| @@ -1,202 +1,184 @@ | |||||
| # EMP 隔离测试环境部署说明 | |||||
| # EMP 隔离部署说明 | |||||
| 这套部署方案用于“服务器不放源码,只运行本地打好的 Docker 镜像包”的场景。 | |||||
| 本目录用于在打包/测试服务器构建 Docker 镜像包,上传到腾讯云 COS,然后在甲方服务器通过包 URL 下载并部署。 | |||||
| ## 目录说明 | |||||
| 本地项目中: | |||||
| ```text | |||||
| deploy/isolated/ | |||||
| build-package.ps1 # Windows 本地打包 | |||||
| build-package.sh # Linux / macOS / WSL 本地打包 | |||||
| docker-compose.runtime.yml # 服务器运行用 compose 模板 | |||||
| .env.example # 服务器运行配置模板 | |||||
| install.sh # 服务器安装脚本 | |||||
| dockerfiles/ # 本地构建镜像用 Dockerfile | |||||
| nginx/admin.conf # 前端 nginx 代理配置 | |||||
| ``` | |||||
| 默认环境为 `emp-test`,用于兼容原有流程;UAT 环境使用 `DEPLOY_ENV=emp-uat`。`emp-test` 和 `emp-uat` 都有独立环境变量模板,并共用一份运行用 compose 模板。 | |||||
| 打包后产物在: | |||||
| ## 目录说明 | |||||
| ```text | ```text | ||||
| deploy/isolated/dist/emp-test-runtime-时间戳.tar.gz | |||||
| build-package.sh Linux / WSL 全量打包脚本 | |||||
| build-package.ps1 Windows PowerShell 全量打包脚本 | |||||
| build-update.sh 增量打包脚本 | |||||
| publish-cos.sh 上传 COS 并输出签名 URL | |||||
| deploy-from-url.sh 甲方服务器按 URL 下载并部署 | |||||
| install.sh 甲方服务器全量安装脚本 | |||||
| apply-update.sh 甲方服务器增量更新脚本 | |||||
| profiles/docker-compose.yml emp-test / emp-uat 共用运行模板 | |||||
| profiles/emp-test/.env.example | |||||
| profiles/emp-uat/.env.example | |||||
| docker-compose.runtime.yml 旧默认运行模板,保留兼容 | |||||
| ``` | ``` | ||||
| 服务器解压后只需要: | |||||
| 打包时 compose 选择顺序为: | |||||
| ```text | ```text | ||||
| docker-compose.yml | |||||
| .env | |||||
| install.sh | |||||
| images.tar | |||||
| README.md | |||||
| profiles/<DEPLOY_ENV>/docker-compose.yml | |||||
| -> profiles/docker-compose.yml | |||||
| -> docker-compose.runtime.yml | |||||
| -> test/docker-compose.yml | |||||
| ``` | ``` | ||||
| ## 本地打包 | |||||
| 正常情况下,`emp-test` 和 `emp-uat` 都使用 `profiles/docker-compose.yml`,差异只来自各自的 `.env.example`。 | |||||
| Windows PowerShell: | |||||
| ```powershell | |||||
| cd E:\emp\deploy\isolated | |||||
| .\build-package.ps1 | |||||
| ``` | |||||
| ## COS 配置 | |||||
| Linux / WSL: | |||||
| 打包服务器启用 `COS_UPLOAD=1` 前,需要设置: | |||||
| ```bash | ```bash | ||||
| cd /path/to/emp/deploy/isolated | |||||
| sh build-package.sh | |||||
| export COS_SECRET_ID=change-me | |||||
| export COS_SECRET_KEY=change-me | |||||
| export COS_REGION=ap-chengdu | |||||
| export COS_BUCKET=emp-example-bucket | |||||
| ``` | ``` | ||||
| 如果 `deploy` 是独立仓库,不在 `emp` 根目录下,需要指定业务代码根目录: | |||||
| 可选配置: | |||||
| ```bash | ```bash | ||||
| cd /home/git/emp_test_deploy/isolated | |||||
| EMP_ROOT=/home/git/emp sh build-package.sh | |||||
| export COS_SIGN_EXPIRE=604800 | |||||
| export COS_PREFIX=deploy/emp-uat/runtime/custom | |||||
| export COS_CONFIG_PATH=/path/to/.cos.yaml | |||||
| ``` | ``` | ||||
| `EMP_ROOT` 指向的目录下必须包含: | |||||
| `publish-cos.sh` 使用腾讯云 `coscli cp` 上传包,并用 `coscli signurl` 生成临时下载 URL。打包服务器需要提前安装 `coscli`。 | |||||
| ```text | |||||
| emp_server/ | |||||
| emp_admin/ | |||||
| emp_ws/ | |||||
| ``` | |||||
| 如果本机已经构建好所有业务镜像,只想重新生成安装包: | |||||
| ## UAT 全量打包并上传 | |||||
| ```bash | ```bash | ||||
| SKIP_BUILD=1 sh build-package.sh | |||||
| cd /home/git/emp/deploy/isolated | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| COS_UPLOAD=1 \ | |||||
| EMP_ROOT=/home/git/emp \ | |||||
| ./build-package.sh | |||||
| ``` | ``` | ||||
| 国内 npm 慢时可以指定源: | |||||
| Windows PowerShell: | |||||
| ```bash | |||||
| NPM_REGISTRY=https://registry.npmmirror.com sh build-package.sh | |||||
| ```powershell | |||||
| cd E:\emp\deploy\isolated | |||||
| .\build-package.ps1 -DeployEnv emp-uat -CosUpload | |||||
| ``` | ``` | ||||
| 默认会把 MySQL、Redis、Kafka、TDengine、Nacos 的中间件镜像一起打进 `images.tar`,服务器不需要访问 Docker Hub。 | |||||
| 输出示例: | |||||
| ## 上传服务器 | |||||
| ```bash | |||||
| scp deploy/isolated/dist/emp-test-runtime-*.tar.gz root@服务器IP:/opt/ | |||||
| ```text | |||||
| Package: .../dist/emp-uat-runtime-20260611120000.tar.gz | |||||
| COS Key: deploy/emp-uat/runtime/20260611120000/emp-uat-runtime-20260611120000.tar.gz | |||||
| SHA256: ... | |||||
| URL: https://... | |||||
| ``` | ``` | ||||
| 服务器执行: | |||||
| ## 甲方服务器全量部署 | |||||
| 先将 `deploy-from-url.sh` 放到甲方服务器。之后执行: | |||||
| ```bash | ```bash | ||||
| mkdir -p /opt/emp-test | |||||
| tar -xzf /opt/emp-test-runtime-*.tar.gz -C /opt/emp-test --strip-components=1 | |||||
| cd /opt/emp-test | |||||
| cp .env.example .env | |||||
| vi .env | |||||
| sh install.sh | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-uat \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<打包输出的URL>" | |||||
| ``` | ``` | ||||
| ## 必改配置 | |||||
| `.env` 中至少要改这些: | |||||
| 脚本会将包下载到: | |||||
| ```env | |||||
| PUBLIC_HOST=服务器外网IP或域名 | |||||
| MYSQL_ROOT_PASSWORD=强密码 | |||||
| DB_PWD=同 MYSQL_ROOT_PASSWORD | |||||
| SIMULATOR_DB_PASSWORD=同 MYSQL_ROOT_PASSWORD | |||||
| REDIS_PASSWORD=强密码 | |||||
| PDF_FRONTEND_BASE_URL=http://服务器外网IP:4081 | |||||
| ```text | |||||
| /home/admin-x99/emp-uat/packages/<时间戳>/ | |||||
| ``` | ``` | ||||
| 如果端口被宿主机其他项目占用,改这些端口: | |||||
| 然后把运行文件复制到: | |||||
| ```env | |||||
| ADMIN_HOST_PORT=4081 | |||||
| GATEWAY_HOST_PORT=9000 | |||||
| MYSQL_HOST_PORT=13306 | |||||
| KAFKA_HOST_PORT=19094 | |||||
| TDENGINE_REST_HOST_PORT=6041 | |||||
| ```text | |||||
| /home/admin-x99/emp-uat/runtime/ | |||||
| ``` | ``` | ||||
| ## 对外连接 | |||||
| 并执行 `install.sh`。 | |||||
| MySQL 8.0: | |||||
| 如果目标目录下还没有 `.env`,`install.sh` 会先从 `.env.example` 生成 `.env` 并停止。修改密码、`PUBLIC_HOST`、端口和第三方配置后,再执行: | |||||
| ```text | |||||
| host: 服务器外网IP | |||||
| port: MYSQL_HOST_PORT,默认 13306 | |||||
| user: root | |||||
| password: MYSQL_ROOT_PASSWORD | |||||
| database: emp | |||||
| ```bash | |||||
| cd /home/admin-x99/emp-uat/runtime | |||||
| DEPLOY_ENV=emp-uat bash install.sh | |||||
| ``` | ``` | ||||
| Kafka: | |||||
| ## 同一台服务器部署 test 和 uat | |||||
| ```text | |||||
| bootstrap.servers=PUBLIC_HOST:KAFKA_HOST_PORT | |||||
| 默认端口:19094 | |||||
| ``` | |||||
| 同一台甲方服务器可以同时部署两套环境。两套环境使用不同 `DEPLOY_ENV`、`DEPLOY_HOME`、compose project 和宿主机端口。 | |||||
| TDengine REST: | |||||
| `profiles/docker-compose.yml` 中网关、PDF、Nacos、Redis 默认只在 Docker 内网访问;前端 Nginx 容器会在 Docker 内网代理 `/api/` 和 `/socket.io/`。 | |||||
| ```text | |||||
| http://PUBLIC_HOST:TDENGINE_REST_HOST_PORT | |||||
| 默认端口:6041 | |||||
| ``` | |||||
| | 环境 | 部署目录 | Compose 项目名 | 前端 | WS | MySQL | 本地 Kafka 可选 | TDengine REST | | |||||
| | --- | --- | --- | --- | --- | --- | --- | --- | | |||||
| | emp-test | `/home/admin-x99/emp-test` | `emp-test` | 4750 | 4751 | 4752 | 4753 | 4754 | | |||||
| | emp-uat | `/home/admin-x99/emp-uat` | `emp-uat` | 4755 | 4756 | 4757 | 4758 | 4759 | | |||||
| Nacos: | |||||
| `4760` 预留备用。当前公共 compose 模板不对外暴露 Gateway、PDF、Nacos、Redis、TDengine RPC;如需额外暴露,再使用 `4760` 或向甲方申请新端口。 | |||||
| ```text | |||||
| http://PUBLIC_HOST:NACOS_HOST_PORT/nacos | |||||
| 默认账号:nacos | |||||
| 默认密码:nacos | |||||
| ``` | |||||
| 甲方服务器系统重装后,`emp-test` 和 `emp-uat` 都按全量部署重新执行一次;不要只打增量包。 | |||||
| 前端: | |||||
| Kafka 当前配置: | |||||
| ```text | |||||
| http://PUBLIC_HOST:ADMIN_HOST_PORT | |||||
| ``` | |||||
| - `emp-test` 模拟器推送:`ip-cld.cn:29362` / `test-vehicle-real-data`。 | |||||
| - `emp-test` 后端消费:`ip-cld.cn:29362` / `YuanJing-test-vehicle-mock-data`。 | |||||
| - `emp-uat` 模拟器推送:`ip-cld.cn:29362` / `uat-vehicle-real-data`。 | |||||
| - `emp-uat` 后端消费:`ip-cld.cn:29362` / `YuanJing-uat-vehicle-mock-data`。 | |||||
| ## 数据导入 | |||||
| 内部 Kafka 镜像仍会打进离线包,但 `profiles/docker-compose.yml` 默认不启动 Kafka。需要本地联调内部 Kafka 时,再显式启用 compose profile:`COMPOSE_PROFILES=local-kafka`。 | |||||
| MySQL 导入示例: | |||||
| 部署 test: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec -T mysql \ | |||||
| mysql -uroot -p"$MYSQL_ROOT_PASSWORD" emp < emp.sql | |||||
| DEPLOY_ENV=emp-test \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-test \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<emp-test包URL>" | |||||
| ``` | ``` | ||||
| TDengine 导入建议仍使用 `taosdump`: | |||||
| 部署 uat: | |||||
| ```bash | ```bash | ||||
| docker cp td_dump_dir emp-test-tdengine-1:/tmp/td_dump | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec tdengine \ | |||||
| taosdump -u root -p"$TDENGINE_PWD" -i /tmp/td_dump | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-uat \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<emp-uat包URL>" | |||||
| ``` | ``` | ||||
| ## 常用命令 | |||||
| Docker compose 会按项目名隔离容器、网络和数据卷,所以只要 `PROJECT_NAME` 不同,`emp-test` 和 `emp-uat` 的数据卷不会互相覆盖。 | |||||
| 查看状态: | |||||
| ## 甲方服务器常用命令 | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps | |||||
| ``` | |||||
| 看日志: | |||||
| cd /home/admin-x99/emp-uat/runtime | |||||
| ```bash | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs -f emp-gateway | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-uat ps | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-uat logs -f emp-gateway | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-uat down | |||||
| ``` | ``` | ||||
| 停止: | |||||
| 查看 test 时把目录和项目名改为 `emp-test`: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test down | |||||
| cd /home/admin-x99/emp-test/runtime | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps | |||||
| ``` | ``` | ||||
| 停止并删除数据卷: | |||||
| ## 手工兜底部署 | |||||
| 如果 COS 不可用,仍然可以通过其他方式把 `dist/` 下的包传到甲方服务器。全量包手工部署示例: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test down -v | |||||
| mkdir -p /home/admin-x99/emp-uat/runtime | |||||
| tar -xzf emp-uat-runtime-*.tar.gz -C /home/admin-x99/emp-uat/runtime --strip-components=1 | |||||
| cd /home/admin-x99/emp-uat/runtime | |||||
| DEPLOY_ENV=emp-uat bash install.sh | |||||
| ``` | ``` | ||||
| @@ -5,12 +5,14 @@ set -Eeuo pipefail | |||||
| # Usage: | # Usage: | ||||
| # bash apply-update.sh | # bash apply-update.sh | ||||
| # bash apply-update.sh emp-admin emp-monitor | # bash apply-update.sh emp-admin emp-monitor | ||||
| # PROJECT_NAME=emp-test ENV_FILE=.env COMPOSE_FILE=docker-compose.yml bash apply-update.sh | |||||
| # DEPLOY_ENV=emp-uat ENV_FILE=.env COMPOSE_FILE=docker-compose.yml bash apply-update.sh | |||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||
| cd "$SCRIPT_DIR" | cd "$SCRIPT_DIR" | ||||
| PROJECT_NAME="${PROJECT_NAME:-emp-test}" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| PROJECT_NAME="${PROJECT_NAME:-$DEPLOY_ENV}" | |||||
| DEPLOY_HOME="${DEPLOY_HOME:-/home/admin-x99/$DEPLOY_ENV}" | |||||
| ENV_FILE="${ENV_FILE:-.env}" | ENV_FILE="${ENV_FILE:-.env}" | ||||
| COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | ||||
| IMAGE_TAR="${IMAGE_TAR:-images.tar}" | IMAGE_TAR="${IMAGE_TAR:-images.tar}" | ||||
| @@ -66,7 +68,9 @@ resolve_runtime_file() { | |||||
| local candidates=( | local candidates=( | ||||
| "../runtime/$default_name" | "../runtime/$default_name" | ||||
| "/home/admin-x99/emp-test/runtime/$default_name" | |||||
| "$DEPLOY_HOME/runtime/$default_name" | |||||
| "$DEPLOY_HOME/$default_name" | |||||
| "/opt/$DEPLOY_ENV/$default_name" | |||||
| ) | ) | ||||
| local candidate | local candidate | ||||
| for candidate in "${candidates[@]}"; do | for candidate in "${candidates[@]}"; do | ||||
| @@ -1,10 +1,13 @@ | |||||
| param( | param( | ||||
| [string]$DeployEnv = $(if ($env:DEPLOY_ENV) { $env:DEPLOY_ENV } else { "emp-test" }), | |||||
| [string]$ImageNamespace = $(if ($env:IMAGE_NAMESPACE) { $env:IMAGE_NAMESPACE } else { "emp-test" }), | [string]$ImageNamespace = $(if ($env:IMAGE_NAMESPACE) { $env:IMAGE_NAMESPACE } else { "emp-test" }), | ||||
| [string]$ImageTag = $(if ($env:IMAGE_TAG) { $env:IMAGE_TAG } else { Get-Date -Format "yyyyMMddHHmmss" }), | [string]$ImageTag = $(if ($env:IMAGE_TAG) { $env:IMAGE_TAG } else { Get-Date -Format "yyyyMMddHHmmss" }), | ||||
| [string]$NpmRegistry = $(if ($env:NPM_REGISTRY) { $env:NPM_REGISTRY } else { "https://registry.npmjs.org" }), | [string]$NpmRegistry = $(if ($env:NPM_REGISTRY) { $env:NPM_REGISTRY } else { "https://registry.npmjs.org" }), | ||||
| [string]$EmpRoot = $(if ($env:EMP_ROOT) { $env:EMP_ROOT } else { "" }), | [string]$EmpRoot = $(if ($env:EMP_ROOT) { $env:EMP_ROOT } else { "" }), | ||||
| [string]$ProfileDir = $(if ($env:PROFILE_DIR) { $env:PROFILE_DIR } else { "" }), | |||||
| [switch]$SkipBuild, | [switch]$SkipBuild, | ||||
| [switch]$NoMiddlewareImages | |||||
| [switch]$NoMiddlewareImages, | |||||
| [switch]$CosUpload | |||||
| ) | ) | ||||
| $ErrorActionPreference = "Stop" | $ErrorActionPreference = "Stop" | ||||
| @@ -14,7 +17,10 @@ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path | |||||
| $RootDir = $null | $RootDir = $null | ||||
| $DistDir = Join-Path $ScriptDir "dist" | $DistDir = Join-Path $ScriptDir "dist" | ||||
| $BuildContextDir = Join-Path $ScriptDir ".build-context" | $BuildContextDir = Join-Path $ScriptDir ".build-context" | ||||
| $PackageName = "emp-test-runtime-$ImageTag" | |||||
| if (-not $env:IMAGE_NAMESPACE -and $ImageNamespace -eq "emp-test" -and $DeployEnv -ne "emp-test") { | |||||
| $ImageNamespace = $DeployEnv | |||||
| } | |||||
| $PackageName = $(if ($env:PACKAGE_NAME) { $env:PACKAGE_NAME } else { "$DeployEnv-runtime-$ImageTag" }) | |||||
| $PackageDir = Join-Path $DistDir $PackageName | $PackageDir = Join-Path $DistDir $PackageName | ||||
| $PackageArchive = Join-Path $DistDir "$PackageName.tar.gz" | $PackageArchive = Join-Path $DistDir "$PackageName.tar.gz" | ||||
| @@ -202,18 +208,34 @@ function Prepare-Package { | |||||
| $ComposeSource = Join-Path $ScriptDir "docker-compose.runtime.yml" | $ComposeSource = Join-Path $ScriptDir "docker-compose.runtime.yml" | ||||
| $EnvSource = Join-Path $ScriptDir ".env.example" | $EnvSource = Join-Path $ScriptDir ".env.example" | ||||
| $ResolvedProfileDir = $(if ($ProfileDir) { $ProfileDir } else { Join-Path $ScriptDir "profiles\$DeployEnv" }) | |||||
| $CommonProfileComposeSource = Join-Path $ScriptDir "profiles\docker-compose.yml" | |||||
| $ProfileComposeSource = Join-Path $ResolvedProfileDir "docker-compose.yml" | |||||
| $ProfileEnvSource = Join-Path $ResolvedProfileDir ".env.example" | |||||
| $ProfileEnvTxtSource = Join-Path $ResolvedProfileDir "env.txt" | |||||
| $TestComposeSource = Join-Path $ScriptDir "test\docker-compose.yml" | $TestComposeSource = Join-Path $ScriptDir "test\docker-compose.yml" | ||||
| $TestEnvSource = Join-Path $ScriptDir "test\env.txt" | $TestEnvSource = Join-Path $ScriptDir "test\env.txt" | ||||
| if (Test-Path $TestComposeSource) { | |||||
| if (Test-Path $ProfileComposeSource) { | |||||
| $ComposeSource = $ProfileComposeSource | |||||
| } elseif (Test-Path $CommonProfileComposeSource) { | |||||
| $ComposeSource = $CommonProfileComposeSource | |||||
| } elseif ($DeployEnv -eq "emp-test" -and (Test-Path $TestComposeSource)) { | |||||
| $ComposeSource = $TestComposeSource | $ComposeSource = $TestComposeSource | ||||
| } | } | ||||
| if (Test-Path $TestEnvSource) { | |||||
| if (Test-Path $ProfileEnvSource) { | |||||
| $EnvSource = $ProfileEnvSource | |||||
| } elseif (Test-Path $ProfileEnvTxtSource) { | |||||
| $EnvSource = $ProfileEnvTxtSource | |||||
| } elseif ($DeployEnv -eq "emp-test" -and (Test-Path $TestEnvSource)) { | |||||
| $EnvSource = $TestEnvSource | $EnvSource = $TestEnvSource | ||||
| } | } | ||||
| Copy-Item $ComposeSource (Join-Path $PackageDir "docker-compose.yml") -Force | Copy-Item $ComposeSource (Join-Path $PackageDir "docker-compose.yml") -Force | ||||
| Copy-Item $EnvSource (Join-Path $PackageDir ".env.example") -Force | Copy-Item $EnvSource (Join-Path $PackageDir ".env.example") -Force | ||||
| Copy-Item (Join-Path $ScriptDir "install.sh") (Join-Path $PackageDir "install.sh") -Force | Copy-Item (Join-Path $ScriptDir "install.sh") (Join-Path $PackageDir "install.sh") -Force | ||||
| Copy-Item (Join-Path $ScriptDir "deploy-from-url.sh") (Join-Path $PackageDir "deploy-from-url.sh") -Force | |||||
| Copy-Item (Join-Path $ScriptDir "README.md") (Join-Path $PackageDir "README.md") -Force | Copy-Item (Join-Path $ScriptDir "README.md") (Join-Path $PackageDir "README.md") -Force | ||||
| } | } | ||||
| @@ -244,6 +266,69 @@ function Archive-Package { | |||||
| Write-Log "Package created: $PackageArchive" | Write-Log "Package created: $PackageArchive" | ||||
| } | } | ||||
| function Publish-Package { | |||||
| if (-not $CosUpload -and $env:COS_UPLOAD -ne "1") { | |||||
| return | |||||
| } | |||||
| $CoscliBin = $(if ($env:COSCLI_BIN) { $env:COSCLI_BIN } else { "coscli" }) | |||||
| Assert-Command $CoscliBin | |||||
| if (-not $env:COS_BUCKET) { | |||||
| throw "Missing COS_BUCKET" | |||||
| } | |||||
| $CoscliOptions = @() | |||||
| if ($env:COS_CONFIG_PATH) { | |||||
| $CoscliOptions += @("-c", $env:COS_CONFIG_PATH) | |||||
| } else { | |||||
| if (-not $env:COS_SECRET_ID) { throw "Missing COS_SECRET_ID or COS_CONFIG_PATH" } | |||||
| if (-not $env:COS_SECRET_KEY) { throw "Missing COS_SECRET_KEY or COS_CONFIG_PATH" } | |||||
| if (-not $env:COS_REGION) { throw "Missing COS_REGION" } | |||||
| $CoscliOptions += @( | |||||
| "--init-skip=true", | |||||
| "-i", $env:COS_SECRET_ID, | |||||
| "-k", $env:COS_SECRET_KEY, | |||||
| "-e", "cos.$($env:COS_REGION).myqcloud.com" | |||||
| ) | |||||
| if ($env:COS_TOKEN) { | |||||
| $CoscliOptions += @("--token", $env:COS_TOKEN) | |||||
| } | |||||
| } | |||||
| $RunId = Get-Date -Format "yyyyMMddHHmmss" | |||||
| $CosSignExpire = $(if ($env:COS_SIGN_EXPIRE) { $env:COS_SIGN_EXPIRE } else { "604800" }) | |||||
| $DefaultPrefix = "deploy/$DeployEnv/runtime/$RunId" | |||||
| $CosPrefix = $(if ($env:COS_PREFIX) { $env:COS_PREFIX } else { $DefaultPrefix }).Trim("/") | |||||
| $PackageBase = Split-Path -Leaf $PackageArchive | |||||
| $CosKey = $(if ($env:COS_KEY) { $env:COS_KEY } else { "$CosPrefix/$PackageBase" }).TrimStart("/") | |||||
| $CosUri = "cos://$($env:COS_BUCKET)/$CosKey" | |||||
| $Sha256 = (Get-FileHash $PackageArchive -Algorithm SHA256).Hash.ToLowerInvariant() | |||||
| Write-Log "Upload package to COS: $CosUri" | |||||
| & $CoscliBin @(@("cp", $PackageArchive, $CosUri) + $CoscliOptions) | |||||
| if ($LASTEXITCODE -ne 0) { | |||||
| throw "coscli cp failed, exit code: $LASTEXITCODE" | |||||
| } | |||||
| Write-Log "Generate signed URL, expire seconds: $CosSignExpire" | |||||
| $SignedOutput = & $CoscliBin @(@("signurl", $CosUri, "--time", $CosSignExpire, "--simple-output") + $CoscliOptions) | |||||
| if ($LASTEXITCODE -ne 0) { | |||||
| throw "coscli signurl failed, exit code: $LASTEXITCODE" | |||||
| } | |||||
| $SignedUrl = ($SignedOutput | Select-Object -Last 1).ToString() | |||||
| Write-Host "" | |||||
| Write-Host "Package: $PackageArchive" | |||||
| Write-Host "COS Key: $CosKey" | |||||
| Write-Host "SHA256: $Sha256" | |||||
| Write-Host "URL: $SignedUrl" | |||||
| Write-Host "" | |||||
| Write-Host "Target deploy command:" | |||||
| Write-Host " DEPLOY_ENV=$DeployEnv PACKAGE_SHA256=$Sha256 bash deploy-from-url.sh `"$SignedUrl`"" | |||||
| Write-Host "" | |||||
| } | |||||
| Add-KnownToolPaths | Add-KnownToolPaths | ||||
| Assert-Command "docker" | Assert-Command "docker" | ||||
| Assert-DockerDaemon | Assert-DockerDaemon | ||||
| @@ -252,6 +337,7 @@ Assert-Command "tar" | |||||
| $RootDir = Resolve-EmpRoot | $RootDir = Resolve-EmpRoot | ||||
| Write-Log "EMP root: $RootDir" | Write-Log "EMP root: $RootDir" | ||||
| Write-Log "Deploy root: $ScriptDir" | Write-Log "Deploy root: $ScriptDir" | ||||
| Write-Log "Deploy env: $DeployEnv" | |||||
| if (-not $SkipBuild) { | if (-not $SkipBuild) { | ||||
| Assert-Command "mvn" | Assert-Command "mvn" | ||||
| @@ -266,3 +352,4 @@ if (-not $SkipBuild) { | |||||
| Prepare-Package | Prepare-Package | ||||
| Save-Images | Save-Images | ||||
| Archive-Package | Archive-Package | ||||
| Publish-Package | |||||
| @@ -10,14 +10,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||||
| DIST_DIR="$SCRIPT_DIR/dist" | DIST_DIR="$SCRIPT_DIR/dist" | ||||
| BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context" | BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context" | ||||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-emp-test}" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-$DEPLOY_ENV}" | |||||
| IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | ||||
| PACKAGE_NAME="${PACKAGE_NAME:-emp-test-runtime-${IMAGE_TAG}}" | |||||
| PACKAGE_NAME="${PACKAGE_NAME:-${DEPLOY_ENV}-runtime-${IMAGE_TAG}}" | |||||
| PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | ||||
| PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | ||||
| NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | ||||
| SKIP_BUILD="${SKIP_BUILD:-0}" | SKIP_BUILD="${SKIP_BUILD:-0}" | ||||
| INCLUDE_MIDDLEWARE_IMAGES="${INCLUDE_MIDDLEWARE_IMAGES:-1}" | INCLUDE_MIDDLEWARE_IMAGES="${INCLUDE_MIDDLEWARE_IMAGES:-1}" | ||||
| COS_UPLOAD="${COS_UPLOAD:-0}" | |||||
| JAVA_MODULES=(emp_gateway emp_auth emp_monitor emp_data) | JAVA_MODULES=(emp_gateway emp_auth emp_monitor emp_data) | ||||
| APP_IMAGES=( | APP_IMAGES=( | ||||
| @@ -150,21 +152,35 @@ prepare_package() { | |||||
| rm -rf "$PACKAGE_DIR" | rm -rf "$PACKAGE_DIR" | ||||
| mkdir -p "$PACKAGE_DIR" | mkdir -p "$PACKAGE_DIR" | ||||
| local common_profile_compose="$SCRIPT_DIR/profiles/docker-compose.yml" | |||||
| local compose_src="$SCRIPT_DIR/docker-compose.runtime.yml" | local compose_src="$SCRIPT_DIR/docker-compose.runtime.yml" | ||||
| local env_src="$SCRIPT_DIR/.env.example" | local env_src="$SCRIPT_DIR/.env.example" | ||||
| if [[ -f "$SCRIPT_DIR/test/docker-compose.yml" ]]; then | |||||
| local profile_dir="${PROFILE_DIR:-$SCRIPT_DIR/profiles/$DEPLOY_ENV}" | |||||
| if [[ -f "$profile_dir/docker-compose.yml" ]]; then | |||||
| compose_src="$profile_dir/docker-compose.yml" | |||||
| elif [[ -f "$common_profile_compose" ]]; then | |||||
| compose_src="$common_profile_compose" | |||||
| elif [[ "$DEPLOY_ENV" == "emp-test" && -f "$SCRIPT_DIR/test/docker-compose.yml" ]]; then | |||||
| compose_src="$SCRIPT_DIR/test/docker-compose.yml" | compose_src="$SCRIPT_DIR/test/docker-compose.yml" | ||||
| fi | fi | ||||
| if [[ -f "$SCRIPT_DIR/test/env.txt" ]]; then | |||||
| if [[ -f "$profile_dir/.env.example" ]]; then | |||||
| env_src="$profile_dir/.env.example" | |||||
| elif [[ -f "$profile_dir/env.txt" ]]; then | |||||
| env_src="$profile_dir/env.txt" | |||||
| elif [[ "$DEPLOY_ENV" == "emp-test" && -f "$SCRIPT_DIR/test/env.txt" ]]; then | |||||
| env_src="$SCRIPT_DIR/test/env.txt" | env_src="$SCRIPT_DIR/test/env.txt" | ||||
| fi | fi | ||||
| cp "$compose_src" "$PACKAGE_DIR/docker-compose.yml" | cp "$compose_src" "$PACKAGE_DIR/docker-compose.yml" | ||||
| cp "$env_src" "$PACKAGE_DIR/.env.example" | cp "$env_src" "$PACKAGE_DIR/.env.example" | ||||
| cp "$SCRIPT_DIR/install.sh" "$PACKAGE_DIR/install.sh" | cp "$SCRIPT_DIR/install.sh" "$PACKAGE_DIR/install.sh" | ||||
| cp "$SCRIPT_DIR/deploy-from-url.sh" "$PACKAGE_DIR/deploy-from-url.sh" | |||||
| cp "$SCRIPT_DIR/README.md" "$PACKAGE_DIR/README.md" | cp "$SCRIPT_DIR/README.md" "$PACKAGE_DIR/README.md" | ||||
| chmod +x "$PACKAGE_DIR/install.sh" | chmod +x "$PACKAGE_DIR/install.sh" | ||||
| chmod +x "$PACKAGE_DIR/deploy-from-url.sh" | |||||
| } | } | ||||
| save_images() { | save_images() { | ||||
| @@ -189,11 +205,21 @@ archive_package() { | |||||
| log "Package created: $PACKAGE_ARCHIVE" | log "Package created: $PACKAGE_ARCHIVE" | ||||
| } | } | ||||
| publish_package() { | |||||
| if [[ "$COS_UPLOAD" != "1" ]]; then | |||||
| return | |||||
| fi | |||||
| log "Upload package to COS" | |||||
| DEPLOY_ENV="$DEPLOY_ENV" PACKAGE_KIND=runtime bash "$SCRIPT_DIR/publish-cos.sh" "$PACKAGE_ARCHIVE" | |||||
| } | |||||
| need_cmd docker | need_cmd docker | ||||
| need_cmd tar | need_cmd tar | ||||
| log "EMP root: $ROOT_DIR" | log "EMP root: $ROOT_DIR" | ||||
| log "Deploy root: $SCRIPT_DIR" | log "Deploy root: $SCRIPT_DIR" | ||||
| log "Deploy env: $DEPLOY_ENV" | |||||
| if [[ "$SKIP_BUILD" != "1" ]]; then | if [[ "$SKIP_BUILD" != "1" ]]; then | ||||
| need_cmd mvn | need_cmd mvn | ||||
| @@ -208,3 +234,4 @@ fi | |||||
| prepare_package | prepare_package | ||||
| save_images | save_images | ||||
| archive_package | archive_package | ||||
| publish_package | |||||
| @@ -8,16 +8,18 @@ set -Eeuo pipefail | |||||
| # EMP_ROOT=/path/to/emp IMAGE_TAG=20260602153000 ./build-update.sh emp-admin emp-monitor | # EMP_ROOT=/path/to/emp IMAGE_TAG=20260602153000 ./build-update.sh emp-admin emp-monitor | ||||
| # | # | ||||
| # Output: | # Output: | ||||
| # dist/emp-test-update-${IMAGE_TAG}-${services}.tar.gz | |||||
| # dist/${DEPLOY_ENV}-update-${IMAGE_TAG}-${services}.tar.gz | |||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||
| DIST_DIR="${DIST_DIR:-$SCRIPT_DIR/dist}" | DIST_DIR="${DIST_DIR:-$SCRIPT_DIR/dist}" | ||||
| BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context" | BUILD_CONTEXT_DIR="$SCRIPT_DIR/.build-context" | ||||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-emp-test}" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| IMAGE_NAMESPACE="${IMAGE_NAMESPACE:-$DEPLOY_ENV}" | |||||
| IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | IMAGE_TAG="${IMAGE_TAG:-$(date '+%Y%m%d%H%M%S')}" | ||||
| NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmjs.org}" | ||||
| SKIP_BUILD="${SKIP_BUILD:-0}" | SKIP_BUILD="${SKIP_BUILD:-0}" | ||||
| COS_UPLOAD="${COS_UPLOAD:-0}" | |||||
| log() { | log() { | ||||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | ||||
| @@ -212,7 +214,7 @@ build_admin_image() { | |||||
| prepare_package() { | prepare_package() { | ||||
| local services_slug="$1" | local services_slug="$1" | ||||
| PACKAGE_NAME="${PACKAGE_NAME:-emp-test-update-${IMAGE_TAG}-${services_slug}}" | |||||
| PACKAGE_NAME="${PACKAGE_NAME:-${DEPLOY_ENV}-update-${IMAGE_TAG}-${services_slug}}" | |||||
| PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | PACKAGE_DIR="$DIST_DIR/$PACKAGE_NAME" | ||||
| PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | PACKAGE_ARCHIVE="$DIST_DIR/${PACKAGE_NAME}.tar.gz" | ||||
| @@ -228,6 +230,7 @@ prepare_package() { | |||||
| done > "$PACKAGE_DIR/services.txt" | done > "$PACKAGE_DIR/services.txt" | ||||
| { | { | ||||
| echo "deploy_env=$DEPLOY_ENV" | |||||
| echo "image_namespace=$IMAGE_NAMESPACE" | echo "image_namespace=$IMAGE_NAMESPACE" | ||||
| echo "image_tag=$IMAGE_TAG" | echo "image_tag=$IMAGE_TAG" | ||||
| echo "services=${REQUESTED_SERVICES[*]}" | echo "services=${REQUESTED_SERVICES[*]}" | ||||
| @@ -255,6 +258,15 @@ archive_package() { | |||||
| log "Update package created: $PACKAGE_ARCHIVE" | log "Update package created: $PACKAGE_ARCHIVE" | ||||
| } | } | ||||
| publish_package() { | |||||
| if [[ "$COS_UPLOAD" != "1" ]]; then | |||||
| return | |||||
| fi | |||||
| log "Upload update package to COS" | |||||
| DEPLOY_ENV="$DEPLOY_ENV" PACKAGE_KIND=update bash "$SCRIPT_DIR/publish-cos.sh" "$PACKAGE_ARCHIVE" | |||||
| } | |||||
| need_cmd docker | need_cmd docker | ||||
| need_cmd tar | need_cmd tar | ||||
| @@ -266,6 +278,7 @@ SERVICES_SLUG="$(IFS=-; echo "${REQUESTED_SERVICES[*]}")" | |||||
| log "EMP root: $ROOT_DIR" | log "EMP root: $ROOT_DIR" | ||||
| log "Deploy root: $SCRIPT_DIR" | log "Deploy root: $SCRIPT_DIR" | ||||
| log "Deploy env: $DEPLOY_ENV" | |||||
| log "Update services: ${REQUESTED_SERVICES[*]}" | log "Update services: ${REQUESTED_SERVICES[*]}" | ||||
| log "Image tag: $IMAGE_TAG" | log "Image tag: $IMAGE_TAG" | ||||
| @@ -304,10 +317,14 @@ fi | |||||
| prepare_package "$SERVICES_SLUG" | prepare_package "$SERVICES_SLUG" | ||||
| save_images | save_images | ||||
| archive_package | archive_package | ||||
| publish_package | |||||
| cat <<EOF | cat <<EOF | ||||
| Next steps on target server: | Next steps on target server: | ||||
| DEPLOY_ENV=$DEPLOY_ENV bash deploy-from-url.sh "<COS signed URL>" | |||||
| Manual fallback: | |||||
| mkdir -p /tmp/emp-update | mkdir -p /tmp/emp-update | ||||
| tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-update --strip-components=1 | tar -xzf $(basename "$PACKAGE_ARCHIVE") -C /tmp/emp-update --strip-components=1 | ||||
| cd /tmp/emp-update | cd /tmp/emp-update | ||||
| @@ -0,0 +1,142 @@ | |||||
| #!/usr/bin/env bash | |||||
| set -Eeuo pipefail | |||||
| # Download a package URL and deploy it on the target server. | |||||
| # Usage: | |||||
| # DEPLOY_ENV=emp-uat bash deploy-from-url.sh "<signed package URL>" | |||||
| # DEPLOY_ENV=emp-uat PACKAGE_SHA256=<sha256> bash deploy-from-url.sh "<signed package URL>" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| PROJECT_NAME="${PROJECT_NAME:-$DEPLOY_ENV}" | |||||
| DEPLOY_HOME="${DEPLOY_HOME:-/home/admin-x99/$DEPLOY_ENV}" | |||||
| PACKAGE_URL="${1:-}" | |||||
| EXPECTED_SHA256="${PACKAGE_SHA256:-${2:-}}" | |||||
| RUN_ID="${RUN_ID:-$(date '+%Y%m%d%H%M%S')}" | |||||
| PACKAGE_ROOT="${PACKAGE_ROOT:-$DEPLOY_HOME/packages}" | |||||
| RUNTIME_DIR="${RUNTIME_DIR:-$DEPLOY_HOME/runtime}" | |||||
| WORK_DIR="$PACKAGE_ROOT/$RUN_ID" | |||||
| DOWNLOAD_FILE="$WORK_DIR/package.tar.gz" | |||||
| EXTRACT_DIR="$WORK_DIR/extracted" | |||||
| log() { | |||||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||||
| } | |||||
| die() { | |||||
| echo "ERROR: $*" >&2 | |||||
| exit 1 | |||||
| } | |||||
| need_cmd() { | |||||
| command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" | |||||
| } | |||||
| download_package() { | |||||
| local url="$1" | |||||
| local output="$2" | |||||
| if command -v curl >/dev/null 2>&1; then | |||||
| curl -fL --retry 3 --connect-timeout 20 -o "$output" "$url" | |||||
| elif command -v wget >/dev/null 2>&1; then | |||||
| wget -O "$output" "$url" | |||||
| else | |||||
| die "Missing curl or wget" | |||||
| fi | |||||
| } | |||||
| calc_sha256() { | |||||
| local file="$1" | |||||
| if command -v sha256sum >/dev/null 2>&1; then | |||||
| sha256sum "$file" | awk '{print $1}' | |||||
| elif command -v shasum >/dev/null 2>&1; then | |||||
| shasum -a 256 "$file" | awk '{print $1}' | |||||
| else | |||||
| die "Missing sha256sum or shasum for checksum verification" | |||||
| fi | |||||
| } | |||||
| verify_checksum() { | |||||
| local file="$1" | |||||
| local expected="$2" | |||||
| [[ -n "$expected" ]] || return | |||||
| local actual | |||||
| actual="$(calc_sha256 "$file")" | |||||
| if [[ "$actual" != "$expected" ]]; then | |||||
| die "SHA256 mismatch. expected=$expected actual=$actual" | |||||
| fi | |||||
| log "SHA256 verified: $actual" | |||||
| } | |||||
| copy_runtime_package() { | |||||
| mkdir -p "$RUNTIME_DIR" | |||||
| cp "$EXTRACT_DIR/docker-compose.yml" "$RUNTIME_DIR/docker-compose.yml" | |||||
| cp "$EXTRACT_DIR/install.sh" "$RUNTIME_DIR/install.sh" | |||||
| cp "$EXTRACT_DIR/images.tar" "$RUNTIME_DIR/images.tar" | |||||
| if [[ -f "$EXTRACT_DIR/deploy-from-url.sh" ]]; then | |||||
| cp "$EXTRACT_DIR/deploy-from-url.sh" "$RUNTIME_DIR/deploy-from-url.sh" | |||||
| fi | |||||
| if [[ -f "$EXTRACT_DIR/.env.example" ]]; then | |||||
| cp "$EXTRACT_DIR/.env.example" "$RUNTIME_DIR/.env.example" | |||||
| fi | |||||
| if [[ -f "$EXTRACT_DIR/README.md" ]]; then | |||||
| cp "$EXTRACT_DIR/README.md" "$RUNTIME_DIR/README.md" | |||||
| fi | |||||
| } | |||||
| deploy_runtime_package() { | |||||
| log "Deploy runtime package to $RUNTIME_DIR" | |||||
| copy_runtime_package | |||||
| ( | |||||
| cd "$RUNTIME_DIR" | |||||
| DEPLOY_ENV="$DEPLOY_ENV" \ | |||||
| PROJECT_NAME="$PROJECT_NAME" \ | |||||
| ENV_FILE=.env \ | |||||
| COMPOSE_FILE=docker-compose.yml \ | |||||
| IMAGE_TAR=images.tar \ | |||||
| bash install.sh | |||||
| ) | |||||
| } | |||||
| deploy_update_package() { | |||||
| [[ -f "$RUNTIME_DIR/.env" ]] || die "Missing runtime env file: $RUNTIME_DIR/.env" | |||||
| [[ -f "$RUNTIME_DIR/docker-compose.yml" ]] || die "Missing runtime compose file: $RUNTIME_DIR/docker-compose.yml" | |||||
| log "Apply update package with runtime dir: $RUNTIME_DIR" | |||||
| ( | |||||
| cd "$EXTRACT_DIR" | |||||
| DEPLOY_ENV="$DEPLOY_ENV" \ | |||||
| DEPLOY_HOME="$DEPLOY_HOME" \ | |||||
| PROJECT_NAME="$PROJECT_NAME" \ | |||||
| ENV_FILE="$RUNTIME_DIR/.env" \ | |||||
| COMPOSE_FILE="$RUNTIME_DIR/docker-compose.yml" \ | |||||
| IMAGE_TAR=images.tar \ | |||||
| bash apply-update.sh | |||||
| ) | |||||
| } | |||||
| [[ -n "$PACKAGE_URL" ]] || die "Usage: DEPLOY_ENV=emp-uat bash deploy-from-url.sh <package-url>" | |||||
| need_cmd tar | |||||
| mkdir -p "$WORK_DIR" "$EXTRACT_DIR" | |||||
| log "Deploy env: $DEPLOY_ENV" | |||||
| log "Deploy home: $DEPLOY_HOME" | |||||
| log "Download package" | |||||
| download_package "$PACKAGE_URL" "$DOWNLOAD_FILE" | |||||
| verify_checksum "$DOWNLOAD_FILE" "$EXPECTED_SHA256" | |||||
| log "Extract package" | |||||
| tar -xzf "$DOWNLOAD_FILE" -C "$EXTRACT_DIR" --strip-components=1 | |||||
| if [[ -f "$EXTRACT_DIR/install.sh" && -f "$EXTRACT_DIR/docker-compose.yml" ]]; then | |||||
| deploy_runtime_package | |||||
| elif [[ -f "$EXTRACT_DIR/apply-update.sh" && -f "$EXTRACT_DIR/images.tar" ]]; then | |||||
| deploy_update_package | |||||
| else | |||||
| die "Unknown package structure: $EXTRACT_DIR" | |||||
| fi | |||||
| log "Done. Package workspace: $WORK_DIR" | |||||
| @@ -1,11 +1,14 @@ | |||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||
| set -Eeuo pipefail | set -Eeuo pipefail | ||||
| # 服务器运行脚本:只加载本地镜像包并启动 compose,不做源码构建。 | |||||
| # Target server runtime installer. It only loads packaged Docker images and | |||||
| # starts docker compose; it does not build source code. | |||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||||
| cd "$SCRIPT_DIR" | cd "$SCRIPT_DIR" | ||||
| PROJECT_NAME="${PROJECT_NAME:-emp-test}" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| PROJECT_NAME="${PROJECT_NAME:-$DEPLOY_ENV}" | |||||
| ENV_FILE="${ENV_FILE:-.env}" | ENV_FILE="${ENV_FILE:-.env}" | ||||
| COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" | ||||
| IMAGE_TAR="${IMAGE_TAR:-images.tar}" | IMAGE_TAR="${IMAGE_TAR:-images.tar}" | ||||
| @@ -20,7 +23,7 @@ die() { | |||||
| } | } | ||||
| need_cmd() { | need_cmd() { | ||||
| command -v "$1" >/dev/null 2>&1 || die "缺少命令:$1" | |||||
| command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" | |||||
| } | } | ||||
| need_cmd docker | need_cmd docker | ||||
| @@ -30,38 +33,37 @@ if docker compose version >/dev/null 2>&1; then | |||||
| elif command -v docker-compose >/dev/null 2>&1; then | elif command -v docker-compose >/dev/null 2>&1; then | ||||
| DC=(docker-compose) | DC=(docker-compose) | ||||
| else | else | ||||
| die "未安装 docker compose" | |||||
| die "Missing docker compose" | |||||
| fi | fi | ||||
| if [[ ! -f "$ENV_FILE" ]]; then | if [[ ! -f "$ENV_FILE" ]]; then | ||||
| cp .env.example "$ENV_FILE" | cp .env.example "$ENV_FILE" | ||||
| die "已生成 $ENV_FILE,请先修改密码、PUBLIC_HOST 和端口后重新执行:sh install.sh" | |||||
| die "Generated $ENV_FILE. Update passwords, PUBLIC_HOST and ports, then rerun: bash install.sh" | |||||
| fi | fi | ||||
| if [[ ! -f "$COMPOSE_FILE" ]]; then | if [[ ! -f "$COMPOSE_FILE" ]]; then | ||||
| die "未找到 $COMPOSE_FILE" | |||||
| die "Missing compose file: $COMPOSE_FILE" | |||||
| fi | fi | ||||
| if [[ -f "$IMAGE_TAR" ]]; then | if [[ -f "$IMAGE_TAR" ]]; then | ||||
| log "加载镜像包:$IMAGE_TAR" | |||||
| log "Load images: $IMAGE_TAR" | |||||
| docker load -i "$IMAGE_TAR" | docker load -i "$IMAGE_TAR" | ||||
| else | else | ||||
| log "未找到 $IMAGE_TAR,将尝试使用本机已有镜像或在线拉取镜像" | |||||
| log "Image tar not found: $IMAGE_TAR. Compose will use local or remote images." | |||||
| fi | fi | ||||
| log "启动隔离测试环境:$PROJECT_NAME" | |||||
| log "Start deploy env: $DEPLOY_ENV, project: $PROJECT_NAME" | |||||
| "${DC[@]}" \ | "${DC[@]}" \ | ||||
| --env-file "$ENV_FILE" \ | --env-file "$ENV_FILE" \ | ||||
| -f "$COMPOSE_FILE" \ | -f "$COMPOSE_FILE" \ | ||||
| -p "$PROJECT_NAME" \ | -p "$PROJECT_NAME" \ | ||||
| up -d | up -d | ||||
| log "当前容器状态" | |||||
| log "Current service status" | |||||
| "${DC[@]}" \ | "${DC[@]}" \ | ||||
| --env-file "$ENV_FILE" \ | --env-file "$ENV_FILE" \ | ||||
| -f "$COMPOSE_FILE" \ | -f "$COMPOSE_FILE" \ | ||||
| -p "$PROJECT_NAME" \ | -p "$PROJECT_NAME" \ | ||||
| ps | ps | ||||
| log "完成。前端地址请访问 .env 中的 PUBLIC_HOST:ADMIN_HOST_PORT。" | |||||
| log "Done. Visit http://PUBLIC_HOST:ADMIN_HOST_PORT from the configured .env values." | |||||
| @@ -0,0 +1,238 @@ | |||||
| x-app-env: &app-env | |||||
| env_file: | |||||
| - .env | |||||
| restart: unless-stopped | |||||
| networks: | |||||
| - emp-net | |||||
| x-java-depends: &java-depends | |||||
| nacos: | |||||
| condition: service_healthy | |||||
| mysql: | |||||
| condition: service_healthy | |||||
| redis: | |||||
| condition: service_healthy | |||||
| services: | |||||
| mysql: | |||||
| image: ${MYSQL_IMAGE:-mysql:8.0} | |||||
| restart: unless-stopped | |||||
| ports: | |||||
| - "0.0.0.0:${MYSQL_HOST_PORT:-23306}:3306" | |||||
| environment: | |||||
| TZ: Asia/Shanghai | |||||
| MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} | |||||
| MYSQL_DATABASE: ${MYSQL_DATABASE:-emp} | |||||
| command: | |||||
| - --character-set-server=utf8mb4 | |||||
| - --collation-server=utf8mb4_0900_ai_ci | |||||
| - --default-time-zone=+08:00 | |||||
| - --max-connections=1000 | |||||
| volumes: | |||||
| - mysql_data:/var/lib/mysql | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$${MYSQL_ROOT_PASSWORD}\" --silent"] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| networks: | |||||
| - emp-net | |||||
| redis: | |||||
| image: ${REDIS_IMAGE:-redis:7-alpine} | |||||
| restart: unless-stopped | |||||
| environment: | |||||
| REDIS_PASSWORD: ${REDIS_PASSWORD} | |||||
| command: ["sh", "-c", "redis-server --appendonly yes --requirepass \"$${REDIS_PASSWORD}\""] | |||||
| volumes: | |||||
| - redis_data:/data | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD}\" ping | grep -q PONG"] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| networks: | |||||
| - emp-net | |||||
| kafka: | |||||
| image: ${KAFKA_IMAGE:-bitnami/kafka:3.7.0} | |||||
| profiles: | |||||
| - local-kafka | |||||
| restart: unless-stopped | |||||
| ports: | |||||
| - "0.0.0.0:${KAFKA_HOST_PORT:-29362}:9094" | |||||
| environment: | |||||
| ALLOW_PLAINTEXT_LISTENER: "yes" | |||||
| KAFKA_CFG_NODE_ID: 1 | |||||
| KAFKA_CFG_PROCESS_ROLES: controller,broker | |||||
| KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093 | |||||
| KAFKA_CFG_LISTENERS: INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:9094 | |||||
| KAFKA_CFG_ADVERTISED_LISTENERS: INTERNAL://kafka:9092,EXTERNAL://${PUBLIC_HOST}:${KAFKA_HOST_PORT:-29362} | |||||
| KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT | |||||
| KAFKA_CFG_INTER_BROKER_LISTENER_NAME: INTERNAL | |||||
| KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER | |||||
| KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true" | |||||
| KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 | |||||
| KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 | |||||
| KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 1 | |||||
| volumes: | |||||
| - kafka_data:/bitnami/kafka | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "/opt/bitnami/kafka/bin/kafka-topics.sh --bootstrap-server 127.0.0.1:9092 --list >/dev/null 2>&1"] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| networks: | |||||
| - emp-net | |||||
| kafka-init: | |||||
| image: ${KAFKA_IMAGE:-bitnami/kafka:3.7.0} | |||||
| profiles: | |||||
| - local-kafka | |||||
| restart: "no" | |||||
| depends_on: | |||||
| kafka: | |||||
| condition: service_healthy | |||||
| entrypoint: ["/bin/bash", "-ec"] | |||||
| environment: | |||||
| KAFKA_TOPIC: ${KAFKA_TOPIC:-vehicle-data} | |||||
| command: | | |||||
| echo "create kafka topic: $${KAFKA_TOPIC}" | |||||
| /opt/bitnami/kafka/bin/kafka-topics.sh \ | |||||
| --bootstrap-server kafka:9092 \ | |||||
| --create \ | |||||
| --if-not-exists \ | |||||
| --topic "$${KAFKA_TOPIC}" \ | |||||
| --partitions 3 \ | |||||
| --replication-factor 1 | |||||
| /opt/bitnami/kafka/bin/kafka-topics.sh \ | |||||
| --bootstrap-server kafka:9092 \ | |||||
| --describe \ | |||||
| --topic "$${KAFKA_TOPIC}" | |||||
| networks: | |||||
| - emp-net | |||||
| tdengine: | |||||
| image: ${TDENGINE_IMAGE:-tdengine/tdengine:3.3.6.0} | |||||
| hostname: tdengine | |||||
| privileged: true | |||||
| restart: unless-stopped | |||||
| ports: | |||||
| - "0.0.0.0:${TDENGINE_REST_HOST_PORT:-37363}:6041" | |||||
| environment: | |||||
| TZ: Asia/Shanghai | |||||
| TAOS_FQDN: tdengine | |||||
| TDENGINE_DATABASE: ${TDENGINE_DATABASE:-emp} | |||||
| volumes: | |||||
| - tdengine_data:/var/lib/taos | |||||
| - tdengine_log:/var/log/taos | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "taos -s \"create database if not exists $${TDENGINE_DATABASE}; show databases;\" >/dev/null 2>&1"] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| networks: | |||||
| - emp-net | |||||
| nacos: | |||||
| image: ${NACOS_IMAGE:-nacos/nacos-server:v2.3.2-slim} | |||||
| restart: unless-stopped | |||||
| environment: | |||||
| MODE: standalone | |||||
| JVM_XMS: 256m | |||||
| JVM_XMX: 512m | |||||
| SPRING_DATASOURCE_PLATFORM: "" | |||||
| NACOS_AUTH_ENABLE: ${NACOS_AUTH_ENABLE:-true} | |||||
| NACOS_AUTH_IDENTITY_KEY: ${NACOS_AUTH_IDENTITY_KEY:-emp} | |||||
| NACOS_AUTH_IDENTITY_VALUE: ${NACOS_AUTH_IDENTITY_VALUE:-emp2026} | |||||
| NACOS_AUTH_TOKEN: ${NACOS_AUTH_TOKEN} | |||||
| volumes: | |||||
| - nacos_data:/home/nacos/data | |||||
| - nacos_logs:/home/nacos/logs | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8848/nacos/actuator/health || exit 1"] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| networks: | |||||
| - emp-net | |||||
| emp-gateway: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-gateway:${IMAGE_TAG:-latest} | |||||
| depends_on: | |||||
| <<: *java-depends | |||||
| emp-auth: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-auth:${IMAGE_TAG:-latest} | |||||
| depends_on: | |||||
| <<: *java-depends | |||||
| emp-monitor: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-monitor:${IMAGE_TAG:-latest} | |||||
| depends_on: | |||||
| <<: *java-depends | |||||
| tdengine: | |||||
| condition: service_healthy | |||||
| emp-pdf: | |||||
| condition: service_healthy | |||||
| emp-data: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-data:${IMAGE_TAG:-latest} | |||||
| depends_on: | |||||
| <<: *java-depends | |||||
| tdengine: | |||||
| condition: service_healthy | |||||
| emp-ws: | |||||
| condition: service_started | |||||
| emp-pdf: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-pdf:${IMAGE_TAG:-latest} | |||||
| environment: | |||||
| PORT: 3100 | |||||
| healthcheck: | |||||
| test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:3100/pdf', r => { r.resume(); process.exit(r.statusCode < 500 ? 0 : 1) }).on('error', () => process.exit(1))\""] | |||||
| interval: 10s | |||||
| timeout: 5s | |||||
| retries: 30 | |||||
| emp-ws: | |||||
| <<: *app-env | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-ws:${IMAGE_TAG:-latest} | |||||
| ports: | |||||
| - "0.0.0.0:${WS_HOST_PORT:-37362}:3000" | |||||
| depends_on: | |||||
| mysql: | |||||
| condition: service_healthy | |||||
| redis: | |||||
| condition: service_healthy | |||||
| emp-admin: | |||||
| image: ${IMAGE_NAMESPACE:-emp-test}/emp-admin:${IMAGE_TAG:-latest} | |||||
| restart: unless-stopped | |||||
| ports: | |||||
| - "0.0.0.0:${ADMIN_HOST_PORT:-37361}:80" | |||||
| depends_on: | |||||
| emp-gateway: | |||||
| condition: service_started | |||||
| emp-ws: | |||||
| condition: service_started | |||||
| networks: | |||||
| - emp-net | |||||
| networks: | |||||
| emp-net: | |||||
| driver: bridge | |||||
| volumes: | |||||
| mysql_data: | |||||
| redis_data: | |||||
| kafka_data: | |||||
| tdengine_data: | |||||
| tdengine_log: | |||||
| nacos_data: | |||||
| nacos_logs: | |||||
| @@ -0,0 +1,108 @@ | |||||
| # EMP test runtime variables. | |||||
| # Copy to .env on the target server and update passwords, PUBLIC_HOST and ports. | |||||
| COMPOSE_PROJECT_NAME=emp-test | |||||
| CONTAINER_PREFIX=emp-test | |||||
| IMAGE_NAMESPACE=emp-test | |||||
| IMAGE_TAG=latest | |||||
| PUBLIC_HOST=127.0.0.1 | |||||
| ADMIN_HOST_PORT=4750 | |||||
| WS_HOST_PORT=4751 | |||||
| MYSQL_HOST_PORT=4752 | |||||
| KAFKA_HOST_PORT=4753 | |||||
| TDENGINE_REST_HOST_PORT=4754 | |||||
| REDIS_BIND_HOST=127.0.0.1 | |||||
| # profiles/docker-compose.yml 不对外暴露 Gateway/PDF/Nacos/Redis/TDengine RPC。 | |||||
| # 4760 预留备用,确需额外暴露服务时再单独分配。 | |||||
| MYSQL_IMAGE=mysql:8.0 | |||||
| REDIS_IMAGE=redis:7-alpine | |||||
| KAFKA_IMAGE=bitnami/kafka:3.7.0 | |||||
| TDENGINE_IMAGE=tdengine/tdengine:3.3.6.0 | |||||
| NACOS_IMAGE=nacos/nacos-server:v2.3.2-slim | |||||
| MYSQL_DATABASE=emp | |||||
| MYSQL_ROOT_PASSWORD=change-me-mysql-root | |||||
| DB_URL=jdbc:mysql://mysql:3306/emp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai | |||||
| DB_USER=root | |||||
| DB_PWD=change-me-mysql-root | |||||
| # DB_READ_URL=jdbc:mysql://mysql-read:3306/emp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai | |||||
| # DB_READ_USER=root | |||||
| # DB_READ_PWD=change-me-mysql-root | |||||
| SIMULATOR_DB_HOST=mysql | |||||
| SIMULATOR_DB_PORT=3306 | |||||
| SIMULATOR_DB_USER=root | |||||
| SIMULATOR_DB_PASSWORD=change-me-mysql-root | |||||
| SIMULATOR_DB_DATABASE=emp | |||||
| SIMULATOR_DB_LIMIT=0 | |||||
| REDIS_HOST=redis | |||||
| REDIS_PORT=6379 | |||||
| REDIS_PASSWORD=change-me-redis | |||||
| REDIS_DB=0 | |||||
| KAFKA_BROKERS=ip-cld.cn:29362 | |||||
| KAFKA_GROUP_ID=emp-test-data-group | |||||
| KAFKA_TOPIC=YuanJing-test-vehicle-mock-data | |||||
| KAFKA_USER= | |||||
| KAFKA_PWD= | |||||
| SIMULATOR_KAFKA_BROKERS=ip-cld.cn:29362 | |||||
| SIMULATOR_KAFKA_TOPIC=test-vehicle-real-data | |||||
| SIMULATOR_KAFKA_USER= | |||||
| SIMULATOR_KAFKA_PASSWORD= | |||||
| SIMULATOR_KAFKA_CLIENT_ID=emp-test-simulator | |||||
| SIMULATOR_KAFKA_BATCH_SIZE=500 | |||||
| TDENGINE_DATABASE=emp | |||||
| TDENGINE_USER=root | |||||
| TDENGINE_PWD=taosdata | |||||
| TDENGINE_URL=jdbc:TAOS-RS://tdengine:6041/emp | |||||
| NACOS_ADDR=nacos:8848 | |||||
| NACOS_USER=nacos | |||||
| NACOS_PWD=nacos | |||||
| NACOS_AUTH_ENABLE=true | |||||
| NACOS_AUTH_IDENTITY_KEY=emp | |||||
| NACOS_AUTH_IDENTITY_VALUE=emp2026 | |||||
| NACOS_AUTH_TOKEN=ZW1wLXBsYXRmb3JtLW5hY29zLXNlY3JldC1rZXktMjAyNg== | |||||
| SPRING_PROFILES_ACTIVE=prod | |||||
| JWT_SECRET=change-me-jwt-secret | |||||
| JWT_EXPIRATION=86400000 | |||||
| SCHEDULER_ENABLED=true | |||||
| EMP_WS_ENV=production | |||||
| NODE_ENV=production | |||||
| PORT=3000 | |||||
| WS_INSTANCES=1 | |||||
| WS_HOST=emp-ws | |||||
| SERVER_API_BASE_URL=http://emp-gateway:9000/api | |||||
| SIMULATOR_ADMIN_USERNAME=admin | |||||
| SIMULATOR_LOGIN_AUTH=change-me-login-auth | |||||
| SIMULATOR_JWT_SECRET=change-me-jwt-secret | |||||
| PDF_SERVICE_URL=http://emp-pdf:3100 | |||||
| PDF_FRONTEND_BASE_URL=http://127.0.0.1:4750 | |||||
| AMAP_KEY= | |||||
| COS_SECRET_ID=change-me | |||||
| COS_SECRET_KEY=change-me | |||||
| COS_REGION=ap-chengdu | |||||
| COS_BUCKET=emp-example-bucket | |||||
| COS_PUBLIC_BASE_URL= | |||||
| SYNC_BASE_URL=https://example.com | |||||
| SYNC_TK=change-me | |||||
| SYNC_TENANT_ID=change-me | |||||
| SYNC_REPORT_CRON=0 30 2 * * ? | |||||
| SYNC_REPORT_SYNC_ENABLED=false | |||||
| SYNC_REPORT_SYNC_CONCURRENCY=3 | |||||
| SYNC_REPORT_SYNC_GROUP_NAMES= | |||||
| SYNC_REPORT_CACHE_MISS_FETCH_ENABLED=false | |||||
| GROUP_REPORT_CRON=0 30 4 ? * THU,SUN | |||||
| @@ -0,0 +1,108 @@ | |||||
| # EMP UAT runtime variables. | |||||
| # Copy to .env on the target server and update passwords, PUBLIC_HOST and ports. | |||||
| COMPOSE_PROJECT_NAME=emp-uat | |||||
| CONTAINER_PREFIX=emp-uat | |||||
| IMAGE_NAMESPACE=emp-uat | |||||
| IMAGE_TAG=latest | |||||
| PUBLIC_HOST=127.0.0.1 | |||||
| ADMIN_HOST_PORT=4755 | |||||
| WS_HOST_PORT=4756 | |||||
| MYSQL_HOST_PORT=4757 | |||||
| KAFKA_HOST_PORT=4758 | |||||
| TDENGINE_REST_HOST_PORT=4759 | |||||
| REDIS_BIND_HOST=127.0.0.1 | |||||
| # profiles/docker-compose.yml 不对外暴露 Gateway/PDF/Nacos/Redis/TDengine RPC。 | |||||
| # 4760 预留备用,确需额外暴露服务时再单独分配。 | |||||
| MYSQL_IMAGE=mysql:8.0 | |||||
| REDIS_IMAGE=redis:7-alpine | |||||
| KAFKA_IMAGE=bitnami/kafka:3.7.0 | |||||
| TDENGINE_IMAGE=tdengine/tdengine:3.3.6.0 | |||||
| NACOS_IMAGE=nacos/nacos-server:v2.3.2-slim | |||||
| MYSQL_DATABASE=emp | |||||
| MYSQL_ROOT_PASSWORD=change-me-mysql-root | |||||
| DB_URL=jdbc:mysql://mysql:3306/emp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai | |||||
| DB_USER=root | |||||
| DB_PWD=change-me-mysql-root | |||||
| # DB_READ_URL=jdbc:mysql://mysql-read:3306/emp?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai | |||||
| # DB_READ_USER=root | |||||
| # DB_READ_PWD=change-me-mysql-root | |||||
| SIMULATOR_DB_HOST=mysql | |||||
| SIMULATOR_DB_PORT=3306 | |||||
| SIMULATOR_DB_USER=root | |||||
| SIMULATOR_DB_PASSWORD=change-me-mysql-root | |||||
| SIMULATOR_DB_DATABASE=emp | |||||
| SIMULATOR_DB_LIMIT=0 | |||||
| REDIS_HOST=redis | |||||
| REDIS_PORT=6379 | |||||
| REDIS_PASSWORD=change-me-redis | |||||
| REDIS_DB=0 | |||||
| KAFKA_BROKERS=ip-cld.cn:29362 | |||||
| KAFKA_GROUP_ID=emp-uat-data-group | |||||
| KAFKA_TOPIC=YuanJing-uat-vehicle-mock-data | |||||
| KAFKA_USER= | |||||
| KAFKA_PWD= | |||||
| SIMULATOR_KAFKA_BROKERS=ip-cld.cn:29362 | |||||
| SIMULATOR_KAFKA_TOPIC=uat-vehicle-real-data | |||||
| SIMULATOR_KAFKA_USER= | |||||
| SIMULATOR_KAFKA_PASSWORD= | |||||
| SIMULATOR_KAFKA_CLIENT_ID=emp-uat-simulator | |||||
| SIMULATOR_KAFKA_BATCH_SIZE=500 | |||||
| TDENGINE_DATABASE=emp | |||||
| TDENGINE_USER=root | |||||
| TDENGINE_PWD=taosdata | |||||
| TDENGINE_URL=jdbc:TAOS-RS://tdengine:6041/emp | |||||
| NACOS_ADDR=nacos:8848 | |||||
| NACOS_USER=nacos | |||||
| NACOS_PWD=nacos | |||||
| NACOS_AUTH_ENABLE=true | |||||
| NACOS_AUTH_IDENTITY_KEY=emp | |||||
| NACOS_AUTH_IDENTITY_VALUE=emp2026 | |||||
| NACOS_AUTH_TOKEN=ZW1wLXBsYXRmb3JtLW5hY29zLXNlY3JldC1rZXktMjAyNg== | |||||
| SPRING_PROFILES_ACTIVE=prod | |||||
| JWT_SECRET=change-me-jwt-secret | |||||
| JWT_EXPIRATION=86400000 | |||||
| SCHEDULER_ENABLED=true | |||||
| EMP_WS_ENV=production | |||||
| NODE_ENV=production | |||||
| PORT=3000 | |||||
| WS_INSTANCES=1 | |||||
| WS_HOST=emp-ws | |||||
| SERVER_API_BASE_URL=http://emp-gateway:9000/api | |||||
| SIMULATOR_ADMIN_USERNAME=admin | |||||
| SIMULATOR_LOGIN_AUTH=change-me-login-auth | |||||
| SIMULATOR_JWT_SECRET=change-me-jwt-secret | |||||
| PDF_SERVICE_URL=http://emp-pdf:3100 | |||||
| PDF_FRONTEND_BASE_URL=http://127.0.0.1:4755 | |||||
| AMAP_KEY= | |||||
| COS_SECRET_ID=change-me | |||||
| COS_SECRET_KEY=change-me | |||||
| COS_REGION=ap-chengdu | |||||
| COS_BUCKET=emp-example-bucket | |||||
| COS_PUBLIC_BASE_URL= | |||||
| SYNC_BASE_URL=https://example.com | |||||
| SYNC_TK=change-me | |||||
| SYNC_TENANT_ID=change-me | |||||
| SYNC_REPORT_CRON=0 30 2 * * ? | |||||
| SYNC_REPORT_SYNC_ENABLED=false | |||||
| SYNC_REPORT_SYNC_CONCURRENCY=3 | |||||
| SYNC_REPORT_SYNC_GROUP_NAMES= | |||||
| SYNC_REPORT_CACHE_MISS_FETCH_ENABLED=false | |||||
| GROUP_REPORT_CRON=0 30 4 ? * THU,SUN | |||||
| @@ -0,0 +1,120 @@ | |||||
| #!/usr/bin/env bash | |||||
| set -Eeuo pipefail | |||||
| # Upload a runtime/update package to Tencent COS and print a deployable URL. | |||||
| # Required when COSCLI is not preconfigured: | |||||
| # COS_SECRET_ID, COS_SECRET_KEY, COS_REGION, COS_BUCKET | |||||
| # | |||||
| # Optional: | |||||
| # DEPLOY_ENV=emp-uat | |||||
| # PACKAGE_KIND=runtime|update | |||||
| # COS_PREFIX=deploy/emp-uat/update/20260611120000 | |||||
| # COS_KEY=deploy/emp-uat/update/package.tar.gz | |||||
| # COS_SIGN_EXPIRE=604800 | |||||
| # COS_CONFIG_PATH=/path/to/.cos.yaml | |||||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |||||
| DEPLOY_ENV="${DEPLOY_ENV:-emp-test}" | |||||
| PACKAGE_KIND="${PACKAGE_KIND:-}" | |||||
| COSCLI_BIN="${COSCLI_BIN:-coscli}" | |||||
| COS_SIGN_EXPIRE="${COS_SIGN_EXPIRE:-604800}" | |||||
| log() { | |||||
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |||||
| } | |||||
| die() { | |||||
| echo "ERROR: $*" >&2 | |||||
| exit 1 | |||||
| } | |||||
| need_cmd() { | |||||
| command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" | |||||
| } | |||||
| calc_sha256() { | |||||
| local file="$1" | |||||
| if command -v sha256sum >/dev/null 2>&1; then | |||||
| sha256sum "$file" | awk '{print $1}' | |||||
| elif command -v shasum >/dev/null 2>&1; then | |||||
| shasum -a 256 "$file" | awk '{print $1}' | |||||
| else | |||||
| echo "" | |||||
| fi | |||||
| } | |||||
| infer_package_kind() { | |||||
| local name="$1" | |||||
| if [[ -n "$PACKAGE_KIND" ]]; then | |||||
| echo "$PACKAGE_KIND" | |||||
| elif [[ "$name" == *"-update-"* ]]; then | |||||
| echo "update" | |||||
| else | |||||
| echo "runtime" | |||||
| fi | |||||
| } | |||||
| build_coscli_opts() { | |||||
| COSCLI_OPTS=() | |||||
| if [[ -n "${COS_CONFIG_PATH:-}" ]]; then | |||||
| COSCLI_OPTS+=("-c" "$COS_CONFIG_PATH") | |||||
| return | |||||
| fi | |||||
| [[ -n "${COS_SECRET_ID:-}" ]] || die "Missing COS_SECRET_ID or COS_CONFIG_PATH" | |||||
| [[ -n "${COS_SECRET_KEY:-}" ]] || die "Missing COS_SECRET_KEY or COS_CONFIG_PATH" | |||||
| [[ -n "${COS_REGION:-}" ]] || die "Missing COS_REGION" | |||||
| COSCLI_OPTS+=( | |||||
| "--init-skip=true" | |||||
| "-i" "$COS_SECRET_ID" | |||||
| "-k" "$COS_SECRET_KEY" | |||||
| "-e" "cos.$COS_REGION.myqcloud.com" | |||||
| ) | |||||
| if [[ -n "${COS_TOKEN:-}" ]]; then | |||||
| COSCLI_OPTS+=("--token" "$COS_TOKEN") | |||||
| fi | |||||
| } | |||||
| [[ "$#" -eq 1 ]] || die "Usage: bash $SCRIPT_DIR/publish-cos.sh <package.tar.gz>" | |||||
| PACKAGE_FILE="$1" | |||||
| [[ -f "$PACKAGE_FILE" ]] || die "Package not found: $PACKAGE_FILE" | |||||
| [[ -n "${COS_BUCKET:-}" ]] || die "Missing COS_BUCKET" | |||||
| need_cmd "$COSCLI_BIN" | |||||
| build_coscli_opts | |||||
| PACKAGE_FILE="$(cd "$(dirname "$PACKAGE_FILE")" && pwd)/$(basename "$PACKAGE_FILE")" | |||||
| PACKAGE_BASE="$(basename "$PACKAGE_FILE")" | |||||
| PACKAGE_KIND="$(infer_package_kind "$PACKAGE_BASE")" | |||||
| RUN_ID="$(date '+%Y%m%d%H%M%S')" | |||||
| DEFAULT_PREFIX="deploy/$DEPLOY_ENV/$PACKAGE_KIND/$RUN_ID" | |||||
| COS_PREFIX="${COS_PREFIX:-$DEFAULT_PREFIX}" | |||||
| COS_PREFIX="${COS_PREFIX#/}" | |||||
| COS_PREFIX="${COS_PREFIX%/}" | |||||
| COS_KEY="${COS_KEY:-$COS_PREFIX/$PACKAGE_BASE}" | |||||
| COS_KEY="${COS_KEY#/}" | |||||
| COS_URI="cos://$COS_BUCKET/$COS_KEY" | |||||
| SHA256="$(calc_sha256 "$PACKAGE_FILE")" | |||||
| log "Upload package: $PACKAGE_FILE" | |||||
| log "COS object: $COS_URI" | |||||
| "$COSCLI_BIN" cp "$PACKAGE_FILE" "$COS_URI" "${COSCLI_OPTS[@]}" | |||||
| log "Generate signed URL, expire seconds: $COS_SIGN_EXPIRE" | |||||
| SIGNED_URL="$("$COSCLI_BIN" signurl "$COS_URI" --time "$COS_SIGN_EXPIRE" --simple-output "${COSCLI_OPTS[@]}")" | |||||
| cat <<EOF | |||||
| Package: $PACKAGE_FILE | |||||
| COS Key: $COS_KEY | |||||
| SHA256: $SHA256 | |||||
| URL: $SIGNED_URL | |||||
| Target deploy command: | |||||
| DEPLOY_ENV=$DEPLOY_ENV PACKAGE_SHA256=$SHA256 bash deploy-from-url.sh "$SIGNED_URL" | |||||
| EOF | |||||
| @@ -1,207 +1,155 @@ | |||||
| # 隔离测试环境增量更新说明 | |||||
| # EMP 隔离环境增量更新说明 | |||||
| 本文档用于后续只更新部分 EMP 服务镜像,不再每次全量打包中间件和所有服务。 | |||||
| 增量包用于只更新指定应用服务镜像,不包含 MySQL、Redis、Kafka、TDengine、Nacos 等中间件镜像。 | |||||
| ## 一、适用场景 | |||||
| 默认环境为 `emp-test`;UAT 环境使用 `DEPLOY_ENV=emp-uat`。 | |||||
| 适用于只更新以下应用服务中的一个或多个: | |||||
| 甲方服务器系统重装后,`emp-test` 和 `emp-uat` 都需要先重新执行全量部署;全量部署完成并确认 `runtime/.env`、`runtime/docker-compose.yml` 存在后,后续版本再使用增量更新。 | |||||
| | 简写 | Compose 服务 | 镜像 | | |||||
| | --- | --- | --- | | |||||
| | gateway | emp-gateway | emp-test/emp-gateway | | |||||
| | auth | emp-auth | emp-test/emp-auth | | |||||
| | monitor | emp-monitor | emp-test/emp-monitor | | |||||
| | data | emp-data | emp-test/emp-data | | |||||
| | pdf | emp-pdf | emp-test/emp-pdf | | |||||
| | ws | emp-ws | emp-test/emp-ws | | |||||
| | admin | emp-admin | emp-test/emp-admin | | |||||
| Kafka 配置应保持为: | |||||
| 中间件 MySQL、Redis、Kafka、TDengine、Nacos 不走增量更新包。 | |||||
| - `emp-test` 模拟器推送 `ip-cld.cn:29362` / `test-vehicle-real-data`,后端消费 `ip-cld.cn:29362` / `YuanJing-test-vehicle-mock-data`。 | |||||
| - `emp-uat` 模拟器推送 `ip-cld.cn:29362` / `uat-vehicle-real-data`,后端消费 `ip-cld.cn:29362` / `YuanJing-uat-vehicle-mock-data`。 | |||||
| ## 二、构建机生成增量包 | |||||
| 内部 Kafka 镜像仍会打进离线包,但默认不启动、不参与业务链路。 | |||||
| 进入部署脚本目录: | |||||
| ## 支持更新的服务 | |||||
| ```bash | |||||
| cd /home/git/emp_test_deploy/isolated | |||||
| ``` | |||||
| | 简写 | Compose 服务名 | 镜像 | | |||||
| | --- | --- | --- | | |||||
| | gateway | emp-gateway | `${IMAGE_NAMESPACE}/emp-gateway` | | |||||
| | auth | emp-auth | `${IMAGE_NAMESPACE}/emp-auth` | | |||||
| | monitor | emp-monitor | `${IMAGE_NAMESPACE}/emp-monitor` | | |||||
| | data | emp-data | `${IMAGE_NAMESPACE}/emp-data` | | |||||
| | pdf | emp-pdf | `${IMAGE_NAMESPACE}/emp-pdf` | | |||||
| | ws | emp-ws | `${IMAGE_NAMESPACE}/emp-ws` | | |||||
| | admin | emp-admin | `${IMAGE_NAMESPACE}/emp-admin` | | |||||
| 只更新 `admin` 和 `monitor`: | |||||
| ## 构建并上传 UAT 增量包 | |||||
| ```bash | |||||
| EMP_ROOT=/home/git/emp \ | |||||
| IMAGE_NAMESPACE=emp-test \ | |||||
| ./build-update.sh admin monitor | |||||
| ``` | |||||
| 生成文件在: | |||||
| 打包服务器先设置 COS 配置: | |||||
| ```bash | ```bash | ||||
| dist/emp-test-update-<时间戳>-admin-monitor.tar.gz | |||||
| export COS_SECRET_ID=change-me | |||||
| export COS_SECRET_KEY=change-me | |||||
| export COS_REGION=ap-chengdu | |||||
| export COS_BUCKET=emp-example-bucket | |||||
| ``` | ``` | ||||
| 也可以更新其他服务: | |||||
| 构建指定服务,上传 COS,并输出签名 URL: | |||||
| ```bash | ```bash | ||||
| # 只更新前端 | |||||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh admin | |||||
| # 只更新数据服务 | |||||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh data | |||||
| cd /home/git/emp/deploy/isolated | |||||
| # 更新模拟器 / WebSocket | |||||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh ws | |||||
| # 更新 PDF 服务 | |||||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh pdf | |||||
| ``` | |||||
| 如果镜像已经在本机构建好,只想重新打包已有镜像: | |||||
| ```bash | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| COS_UPLOAD=1 \ | |||||
| EMP_ROOT=/home/git/emp \ | EMP_ROOT=/home/git/emp \ | ||||
| IMAGE_NAMESPACE=emp-test \ | |||||
| SKIP_BUILD=1 \ | |||||
| ./build-update.sh admin monitor | ./build-update.sh admin monitor | ||||
| ``` | ``` | ||||
| ## 三、传输到甲方服务器 | |||||
| 将增量包传到甲方服务器任意目录,例如: | |||||
| ```bash | |||||
| /home/admin-x99/emp-test/update/emp-test-update-20260602153000-admin-monitor.tar.gz | |||||
| ``` | |||||
| ## 四、甲方服务器应用增量包 | |||||
| 进入服务器: | |||||
| ```bash | |||||
| cd /home/admin-x99/emp-test | |||||
| mkdir -p update-runtime | |||||
| tar -xzf update/emp-test-update-20260602153000-admin-monitor.tar.gz \ | |||||
| -C update-runtime \ | |||||
| --strip-components=1 | |||||
| cd update-runtime | |||||
| ``` | |||||
| 执行增量更新: | |||||
| 输出示例: | |||||
| ```bash | |||||
| bash apply-update.sh | |||||
| ```text | |||||
| Package: .../dist/emp-uat-update-20260611123000-admin-monitor.tar.gz | |||||
| COS Key: deploy/emp-uat/update/20260611123000/emp-uat-update-20260611123000-admin-monitor.tar.gz | |||||
| SHA256: ... | |||||
| URL: https://... | |||||
| ``` | ``` | ||||
| 脚本会优先使用当前目录下的 `.env`、`docker-compose.yml`;如果当前目录没有,会自动查找 `../runtime/` 和 `/home/admin-x99/emp-test/runtime/` 下的运行配置。 | |||||
| 脚本会自动执行: | |||||
| 如果本机镜像已经构建好,只需要重新打包并上传: | |||||
| ```bash | ```bash | ||||
| docker load -i images.tar | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test \ | |||||
| up -d --no-deps --force-recreate emp-admin emp-monitor | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| COS_UPLOAD=1 \ | |||||
| SKIP_BUILD=1 \ | |||||
| EMP_ROOT=/home/git/emp \ | |||||
| ./build-update.sh admin monitor | |||||
| ``` | ``` | ||||
| 如果当前目录没有 `.env` 和 `docker-compose.yml`,可以显式指定运行环境目录中的文件: | |||||
| ```bash | |||||
| PROJECT_NAME=emp-test \ | |||||
| ENV_FILE=/home/admin-x99/emp-test/runtime/.env \ | |||||
| COMPOSE_FILE=/home/admin-x99/emp-test/runtime/docker-compose.yml \ | |||||
| bash apply-update.sh | |||||
| ``` | |||||
| ## 甲方服务器按 URL 应用增量包 | |||||
| 也可以手动指定更新服务,覆盖包内 `services.txt`: | |||||
| 先确认目标环境已完成全量部署,并存在: | |||||
| ```bash | |||||
| PROJECT_NAME=emp-test \ | |||||
| ENV_FILE=/home/admin-x99/emp-test/runtime/.env \ | |||||
| COMPOSE_FILE=/home/admin-x99/emp-test/runtime/docker-compose.yml \ | |||||
| bash apply-update.sh emp-admin emp-monitor | |||||
| ```text | |||||
| /home/admin-x99/emp-uat/runtime/.env | |||||
| /home/admin-x99/emp-uat/runtime/docker-compose.yml | |||||
| ``` | ``` | ||||
| ## 五、验证 | |||||
| 查看服务状态: | |||||
| 执行: | |||||
| ```bash | ```bash | ||||
| cd /home/admin-x99/emp-test/runtime | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps emp-admin emp-monitor | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-uat \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<打包输出的URL>" | |||||
| ``` | ``` | ||||
| 查看后端日志: | |||||
| 同一台服务器上更新不同环境时,必须使用对应环境参数: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs --tail=100 emp-monitor | |||||
| ``` | |||||
| 验证前端: | |||||
| # 更新 test | |||||
| DEPLOY_ENV=emp-test \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-test \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<emp-test增量包URL>" | |||||
| ```bash | |||||
| curl -I http://127.0.0.1:37361 | |||||
| # 更新 uat | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-uat \ | |||||
| PACKAGE_SHA256=<打包输出的SHA256> \ | |||||
| bash deploy-from-url.sh "<emp-uat增量包URL>" | |||||
| ``` | ``` | ||||
| ## 六、注意事项 | |||||
| 1. 增量更新默认同时打 `latest` 和时间戳 tag。 | |||||
| 2. 当前测试环境 `.env` 建议继续使用 `IMAGE_TAG=latest`,不要为了增量包修改成时间戳。 | |||||
| 3. 如果把 `.env` 的 `IMAGE_TAG` 改成某个新时间戳,但只传了部分服务镜像,其他服务会因为缺少该 tag 而无法重建。 | |||||
| 4. 如果修改了 `docker-compose.yml`、`.env.example`、中间件初始化逻辑,建议重新全量打包或单独同步配置文件。 | |||||
| 5. 数据库结构变更不包含在镜像增量包中,需要单独执行 SQL 迁移。 | |||||
| ## 七、PDF 导出故障排查 | |||||
| 报错: | |||||
| `deploy-from-url.sh` 会将包下载到: | |||||
| ```text | ```text | ||||
| PDF导出失败: I/O error on GET request for "http://emp-pdf:3100/pdf": Connection refused | |||||
| $DEPLOY_HOME/packages/<时间戳>/ | |||||
| ``` | ``` | ||||
| 含义:`emp-monitor` 已经访问到 Docker 内网地址 `emp-pdf:3100`,但 PDF 服务端口没有进程监听,常见原因是 `emp-pdf` 容器未启动、启动后退出、正在重启,或 Node 服务未正常监听 3100。 | |||||
| 然后调用包内的 `apply-update.sh`,并自动使用运行目录中的 `.env` 和 `docker-compose.yml`。 | |||||
| 新版 `docker-compose.yml` 已给 `emp-pdf` 增加健康检查,并让 `emp-monitor` 等待 `emp-pdf` 可访问后再启动。健康检查访问 `/pdf`,400 也算通过,因为不带 url 参数时返回 400 代表服务已正常监听。若服务器仍使用旧 compose,需要先同步新的 `docker-compose.yml` 或按下面命令手动重启 PDF 服务。 | |||||
| ## 手工应用增量包 | |||||
| 先看容器状态: | |||||
| 如果已经手工解压增量包,可以在解压目录执行: | |||||
| ```bash | ```bash | ||||
| cd /home/admin-x99/emp-test/runtime | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps emp-pdf emp-monitor | |||||
| ``` | |||||
| 查看 PDF 服务日志: | |||||
| cd /tmp/emp-update | |||||
| ```bash | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test logs --tail=200 emp-pdf | |||||
| DEPLOY_ENV=emp-uat \ | |||||
| DEPLOY_HOME=/home/admin-x99/emp-uat \ | |||||
| ENV_FILE=/home/admin-x99/emp-uat/runtime/.env \ | |||||
| COMPOSE_FILE=/home/admin-x99/emp-uat/runtime/docker-compose.yml \ | |||||
| bash apply-update.sh emp-admin emp-monitor | |||||
| ``` | ``` | ||||
| 在 PDF 容器内检查 PDF 服务是否监听: | |||||
| 如果不传服务名,`apply-update.sh` 会读取增量包内的 `services.txt`。 | |||||
| ```bash | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec emp-pdf \ | |||||
| node -e "require('http').get('http://127.0.0.1:3100/pdf', r => { console.log(r.statusCode); r.resume(); process.exit(r.statusCode < 500 ? 0 : 1) }).on('error', e => { console.error(e.message); process.exit(1) })" | |||||
| ``` | |||||
| ## 验证 | |||||
| 在同一个 Docker 网络里检查 `emp-monitor` 到 `emp-pdf` 的访问: | |||||
| 查看 UAT 服务状态: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test exec emp-monitor \ | |||||
| sh -lc "curl -i http://emp-pdf:3100/pdf || wget -S -O- http://emp-pdf:3100/pdf" | |||||
| cd /home/admin-x99/emp-uat/runtime | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-uat ps emp-admin emp-monitor | |||||
| ``` | ``` | ||||
| 如果 `emp-pdf` 未运行或健康检查失败,先重启 PDF 服务: | |||||
| 查看日志: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test up -d --no-deps --force-recreate emp-pdf | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-uat logs --tail=100 emp-monitor | |||||
| ``` | ``` | ||||
| 再重启 monitor,使其重新调用可用的 PDF 服务: | |||||
| 查看 test 时把目录和项目名改为 `emp-test`: | |||||
| ```bash | ```bash | ||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test restart emp-monitor | |||||
| cd /home/admin-x99/emp-test/runtime | |||||
| docker compose --env-file .env -f docker-compose.yml -p emp-test ps emp-admin emp-monitor | |||||
| ``` | ``` | ||||
| 如果日志中出现 Chromium/Puppeteer 相关错误,重新构建并增量更新 `pdf`: | |||||
| ## 注意事项 | |||||
| ```bash | |||||
| EMP_ROOT=/home/git/emp IMAGE_NAMESPACE=emp-test ./build-update.sh pdf | |||||
| ``` | |||||
| 1. 增量包默认同时包含 `latest` 和时间戳两个镜像 tag。 | |||||
| 2. 目标服务器 `.env` 建议保持 `IMAGE_TAG=latest`,除非确定所有服务都已经按同一个时间戳 tag 部署。 | |||||
| 3. 如果修改了 `docker-compose.yml`、`.env.example` 或中间件初始化逻辑,建议重新打全量包,或单独同步运行配置。 | |||||
| 4. 数据库结构变更不包含在镜像增量包中,需要单独执行 SQL 迁移。 | |||||
| 5. 同一台服务器同时部署 `emp-test` 和 `emp-uat` 时,更新命令里的 `DEPLOY_ENV`、`DEPLOY_HOME`、包 URL 必须匹配同一个环境。 | |||||