Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa4517c263 | ||
|
|
2c73ad7eb0 |
@@ -65,12 +65,17 @@ jobs:
|
|||||||
./scripts/test-install-fixture.sh
|
./scripts/test-install-fixture.sh
|
||||||
./scripts/test-git-install-fixture.sh
|
./scripts/test-git-install-fixture.sh
|
||||||
./scripts/test-update-fixture.sh
|
./scripts/test-update-fixture.sh
|
||||||
|
./scripts/test-baota-start-fixture.sh
|
||||||
|
./scripts/test-purge-fixture.sh
|
||||||
./scripts/test-gitea-publish-fixture.sh
|
./scripts/test-gitea-publish-fixture.sh
|
||||||
shellcheck deploy/install.sh deploy/install-from-git.sh deploy/update.sh scripts/check-semver.sh \
|
shellcheck deploy/install.sh deploy/install-from-git.sh deploy/update.sh deploy/baota-start.sh \
|
||||||
|
deploy/purge.sh \
|
||||||
|
scripts/check-semver.sh \
|
||||||
scripts/package-release.sh scripts/publish-gitea-release.sh \
|
scripts/package-release.sh scripts/publish-gitea-release.sh \
|
||||||
scripts/generate-release-key.sh scripts/test-install-fixture.sh \
|
scripts/generate-release-key.sh scripts/test-install-fixture.sh \
|
||||||
scripts/test-git-install-fixture.sh \
|
scripts/test-git-install-fixture.sh \
|
||||||
scripts/test-update-fixture.sh scripts/test-gitea-publish-fixture.sh \
|
scripts/test-update-fixture.sh scripts/test-gitea-publish-fixture.sh \
|
||||||
|
scripts/test-baota-start-fixture.sh scripts/test-purge-fixture.sh \
|
||||||
scripts/verify-release.sh
|
scripts/verify-release.sh
|
||||||
|
|
||||||
- name: Build and sign release assets
|
- name: Build and sign release assets
|
||||||
|
|||||||
@@ -56,17 +56,37 @@ PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview 发
|
|||||||
外部数据库,不需要服务器安装 MySQL CLI。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、
|
外部数据库,不需要服务器安装 MySQL CLI。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、
|
||||||
`/var/lib/kaidi-update` 和 `/etc/kaidi`。安装器不安装、不启动也不修改 Nginx、Caddy、宝塔或其他反向代理。
|
`/var/lib/kaidi-update` 和 `/etc/kaidi`。安装器不安装、不启动也不修改 Nginx、Caddy、宝塔或其他反向代理。
|
||||||
|
|
||||||
|
### 彻底清理旧安装
|
||||||
|
|
||||||
|
重新安装前先在宝塔面板停止并删除名为 `kaidi-finance` 的 Java 项目,避免面板守护进程重新拉起旧 Java。
|
||||||
|
随后执行一键清理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/purge.sh \
|
||||||
|
-o /tmp/kaidi-purge.sh \
|
||||||
|
&& printf '%s %s\n' c49c5460260d2c67d88aced99463e3d98a67f8ef0309f5c8d5d8a8f5e4addf36 /tmp/kaidi-purge.sh \
|
||||||
|
| sha256sum -c - \
|
||||||
|
&& sudo env KAIDI_PURGE_CONFIRM=DELETE_LOCAL_KAIDI_INSTALLATION bash /tmp/kaidi-purge.sh \
|
||||||
|
&& rm -f /tmp/kaidi-purge.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
清理脚本会先停止 systemd 和遗留 Kaidi 进程,将旧配置、文件存储、安装码以及已有数据库备份归档到
|
||||||
|
`/root/kaidi-reinstall-backups/`,再删除 `/opt/kaidi`、`/www/wwwroot/kaidi`、`/etc/kaidi`、
|
||||||
|
`/var/lib/kaidi`、`/var/lib/kaidi-update`、`/var/log/kaidi` 和三个 systemd 单元。恢复包权限固定为
|
||||||
|
`0600`,确认新安装及数据无误后再由 root 删除。脚本不删除外部 MySQL、系统 Java、Nginx 或宝塔站点配置;
|
||||||
|
新安装必须连接一个新的空 MySQL 数据库,旧数据库保留用于回滚。
|
||||||
|
|
||||||
### 直接 curl 安装
|
### 直接 curl 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh | sudo bash
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh | sudo bash
|
||||||
```
|
```
|
||||||
|
|
||||||
这条命令会提示填写 Java 应用端口,直接回车使用 `18080`;随后安装最新签名 Release,并默认进入 `/setup`
|
这条命令会提示填写 Java 应用端口,直接回车使用 `18080`;随后安装最新签名 Release,并默认进入 `/setup`
|
||||||
安装向导。Java 默认只监听 `127.0.0.1:所选端口`,前端页面、API 和健康检查均由同一端口提供。无人值守安装可直接指定:
|
安装向导。Java 默认只监听 `127.0.0.1:所选端口`,前端页面、API 和健康检查均由同一端口提供。无人值守安装可直接指定:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh \
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh \
|
||||||
| sudo env KAIDI_APP_PORT=19090 bash
|
| sudo env KAIDI_APP_PORT=19090 bash
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -74,8 +94,8 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe
|
|||||||
时才设置 `KAIDI_SERVER_ADDRESS=0.0.0.0`,通常应保持默认回环绑定并由本机反向代理访问。需要在执行前独立校验安装脚本时使用:
|
时才设置 `KAIDI_SERVER_ADDRESS=0.0.0.0`,通常应保持默认回环绑定并由本机反向代理访问。需要在执行前独立校验安装脚本时使用:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh -o /tmp/kaidi-install.sh
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh -o /tmp/kaidi-install.sh
|
||||||
printf '%s %s\n' bc9001197af843dc323907a98023064ccc4f184e000543d2086b8a6f8161dbc7 /tmp/kaidi-install.sh | sha256sum -c -
|
printf '%s %s\n' 6256b05025aa314d4c02e7c0f5d2ca530f28ec5d10f833504b34c09ada6e02a5 /tmp/kaidi-install.sh | sha256sum -c -
|
||||||
sudo bash /tmp/kaidi-install.sh
|
sudo bash /tmp/kaidi-install.sh
|
||||||
rm -f /tmp/kaidi-install.sh
|
rm -f /tmp/kaidi-install.sh
|
||||||
```
|
```
|
||||||
@@ -86,7 +106,7 @@ rm -f /tmp/kaidi-install.sh
|
|||||||
包装器会在 `sudo` 前校验 `deploy/install.sh` 的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。
|
包装器会在 `sudo` 前校验 `deploy/install.sh` 的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --branch v1.0.0-preview.19 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview
|
git clone --branch v1.0.0-preview.24 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview
|
||||||
cd kaidi-preview
|
cd kaidi-preview
|
||||||
./deploy/install-from-git.sh
|
./deploy/install-from-git.sh
|
||||||
```
|
```
|
||||||
@@ -106,6 +126,21 @@ cd kaidi-preview
|
|||||||
- 向导只初始化一个由操作者填写的 `SYSTEM_ADMIN` 管理员,不创建项目、财务、资料或演示账号。
|
- 向导只初始化一个由操作者填写的 `SYSTEM_ADMIN` 管理员,不创建项目、财务、资料或演示账号。
|
||||||
- 安装器把一次性安装码写入仅 root 可读的 `/root/kaidi-first-login.txt`;完成向导后写入锁定标记并切换正式应用。
|
- 安装器把一次性安装码写入仅 root 可读的 `/root/kaidi-first-login.txt`;完成向导后写入锁定标记并切换正式应用。
|
||||||
|
|
||||||
|
### 环境变量与密钥安全
|
||||||
|
|
||||||
|
首次安装不需要在命令行或宝塔项目中填写数据库环境变量。数据库地址、账号和密码只在 `/setup` 向导中提交,
|
||||||
|
完成后写入 `/var/lib/kaidi/setup/application.env`;字段加密密钥由安装器生成并写入
|
||||||
|
`/etc/kaidi/kaidi.env`。两个文件不会把秘密放进 Java 启动参数或 `ps` 输出,并由 Linux 文件权限限制为
|
||||||
|
root 或专用 `kaidi` 服务账号读取。运行中的 Java 仍必须持有这些值,因此 root 和该服务账号始终属于受信边界。
|
||||||
|
|
||||||
|
不要通过公网 HTTP 提交数据库密码。应先为域名配置有效 HTTPS,或通过 SSH 端口转发访问本机回环地址完成向导:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -L 18080:127.0.0.1:18080 root@SERVER_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在本机浏览器打开 `http://127.0.0.1:18080/setup`。向导完成后自动锁定,安装码不能再次使用。
|
||||||
|
|
||||||
安装完成后先执行 `sudo cat /root/kaidi-first-login.txt`,其中会显示准确的反向代理目标和一次性安装码。配置反向代理后,
|
安装完成后先执行 `sudo cat /root/kaidi-first-login.txt`,其中会显示准确的反向代理目标和一次性安装码。配置反向代理后,
|
||||||
通过域名的 `/setup` 完成数据库和管理员配置,再访问域名根路径登录。Preview 使用 HTTP 时安装器默认设置
|
通过域名的 `/setup` 完成数据库和管理员配置,再访问域名根路径登录。Preview 使用 HTTP 时安装器默认设置
|
||||||
`SESSION_COOKIE_SECURE=false`;配置 HTTPS 反向代理后,应在 `/etc/kaidi/kaidi.env` 改为
|
`SESSION_COOKIE_SECURE=false`;配置 HTTPS 反向代理后,应在 `/etc/kaidi/kaidi.env` 改为
|
||||||
@@ -141,7 +176,7 @@ sudo cat /root/kaidi-first-login.txt
|
|||||||
32 位服务端镜像,因此 32 位主机需要预先连接一台 MySQL 8.4 数据库,之后仍然只执行一个安装命令:
|
32 位服务端镜像,因此 32 位主机需要预先连接一台 MySQL 8.4 数据库,之后仍然只执行一个安装命令:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.19/install.sh | sudo bash
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh | sudo bash
|
||||||
```
|
```
|
||||||
|
|
||||||
无论主机架构如何,安装器都不会安装 MySQL、数据库客户端或创建数据库容器。在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的
|
无论主机架构如何,安装器都不会安装 MySQL、数据库客户端或创建数据库容器。在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的
|
||||||
@@ -163,7 +198,7 @@ GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP';
|
|||||||
管理员数据和运维人员新增的环境变量:
|
管理员数据和运维人员新增的环境变量:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.22/install.sh \
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh \
|
||||||
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash
|
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -174,10 +209,68 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe
|
|||||||
如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
|
如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.22/install.sh \
|
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/install.sh \
|
||||||
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=true bash
|
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=true bash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 宝塔面板手动部署
|
||||||
|
|
||||||
|
宝塔使用 **Spring Boot** 项目类型。发布物是包含嵌入式 Tomcat 的可执行 JAR,不使用外置 Tomcat 项目。
|
||||||
|
先下载并解压签名 Release;项目目录必须指向同时包含 `app.jar`、`VERSION`、`public/` 和 `ops/` 的目录。
|
||||||
|
推荐采用版本目录加 `current` 软链接:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VERSION=1.0.0-preview.24
|
||||||
|
RELEASE_ROOT=/www/wwwroot/kaidi/releases/$VERSION
|
||||||
|
sudo install -d -o root -g kaidi -m 0750 "$RELEASE_ROOT"
|
||||||
|
curl -fL "https://git.awaioi.com/ERP-Team/kaidi/releases/download/v$VERSION/kaidi-finance-$VERSION.tar.gz" \
|
||||||
|
-o "/tmp/kaidi-finance-$VERSION.tar.gz"
|
||||||
|
sudo tar -xzf "/tmp/kaidi-finance-$VERSION.tar.gz" -C "$RELEASE_ROOT"
|
||||||
|
sudo chown -R root:kaidi "$RELEASE_ROOT"
|
||||||
|
sudo find "$RELEASE_ROOT" -type d -exec chmod 0750 {} +
|
||||||
|
sudo find "$RELEASE_ROOT" -type f -exec chmod 0640 {} +
|
||||||
|
sudo chmod 0750 "$RELEASE_ROOT/ops/baota-start.sh"
|
||||||
|
sudo ln -sfn "$RELEASE_ROOT" /www/wwwroot/kaidi/current.next
|
||||||
|
sudo mv -Tf /www/wwwroot/kaidi/current.next /www/wwwroot/kaidi/current
|
||||||
|
```
|
||||||
|
|
||||||
|
宝塔 Java 项目字段:
|
||||||
|
|
||||||
|
| 字段 | 值 |
|
||||||
|
| --- | --- |
|
||||||
|
| 项目类型 | Spring Boot |
|
||||||
|
| 项目路径 | `/www/wwwroot/kaidi/current` |
|
||||||
|
| 项目名称 | `kaidi-finance` |
|
||||||
|
| 项目 JDK | JDK 17 |
|
||||||
|
| 项目启动命令 | `/www/wwwroot/kaidi/current/ops/baota-start.sh` |
|
||||||
|
| 启动用户 | `kaidi`,禁止使用 root |
|
||||||
|
| 项目端口 | `18080` |
|
||||||
|
| 绑定域名 | 实际业务域名,例如 `fi.awaioi.com` |
|
||||||
|
| 后端路由 | `/` |
|
||||||
|
| 前端资源 | 留空,由 Spring Boot 提供 `public/` |
|
||||||
|
| 进程守护 | 开启 |
|
||||||
|
| 项目意外重启 | 勾选 |
|
||||||
|
|
||||||
|
环境变量按 `ops/baota.env.example` 填入宝塔。已完成安装的服务器必须沿用
|
||||||
|
`/var/lib/kaidi/setup/application.env` 中的 `DB_URL`、`DB_USERNAME`、`DB_PASSWORD`,以及
|
||||||
|
`/etc/kaidi/kaidi.env` 中的 `FIELD_ENCRYPTION_KEY`;不要生成新的字段加密密钥。HTTP Preview 使用
|
||||||
|
`SESSION_COOKIE_SECURE=false`,切换到 HTTPS 后改为 `true`。宝塔模式固定设置
|
||||||
|
`FINANCE_UPDATE_ENABLED=false`,版本更新通过上传新目录、切换 `current`、重启项目完成。
|
||||||
|
|
||||||
|
启用宝塔守护前停用原 systemd 进程,避免两个进程管理器反复争用端口或互相重启:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl disable --now kaidi-update.path kaidi-finance.service
|
||||||
|
sudo systemctl stop kaidi-update.service
|
||||||
|
```
|
||||||
|
|
||||||
|
启动后在服务器验证 Java 上游,再配置域名反向代理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS http://127.0.0.1:18080/actuator/health
|
||||||
|
curl -fsS http://127.0.0.1:18080/ | head
|
||||||
|
```
|
||||||
|
|
||||||
### 停用并移除程序
|
### 停用并移除程序
|
||||||
|
|
||||||
以下命令移除应用程序和服务,但保留 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/etc/kaidi` 以及数据库,
|
以下命令移除应用程序和服务,但保留 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/etc/kaidi` 以及数据库,
|
||||||
@@ -205,13 +298,13 @@ base64 < release-signing-private.pem | tr -d '\n'
|
|||||||
Release;生产服务器从公共 Release 下载,不保存 Token。私钥不得提交到 Git。`.gitea/workflows/release.yml` 要求
|
Release;生产服务器从公共 Release 下载,不保存 Token。私钥不得提交到 Git。`.gitea/workflows/release.yml` 要求
|
||||||
act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁。
|
act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁。
|
||||||
|
|
||||||
工作流先建立不可见草稿,再显式上传并核对 9 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的
|
工作流先建立不可见草稿,再显式上传并核对 10 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的
|
||||||
`latest` 会排除 `prerelease=true`,因此即使 tag 名含 `preview`,发布记录的 `prerelease` 也固定为 `false`;
|
`latest` 会排除 `prerelease=true`,因此即使 tag 名含 `preview`,发布记录的 `prerelease` 也固定为 `false`;
|
||||||
Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布:
|
Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag v1.0.0-preview.19
|
git tag v1.0.0-preview.24
|
||||||
git push origin v1.0.0-preview.19
|
git push origin v1.0.0-preview.24
|
||||||
```
|
```
|
||||||
|
|
||||||
在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。
|
在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。
|
||||||
@@ -225,12 +318,12 @@ URL、Token、脚本或命令。更新流程固定为:
|
|||||||
4. 安装请求持久领取到 `/var/lib/kaidi-update/processing`;进程或主机中断后由 systemd 恢复未完成事务。
|
4. 安装请求持久领取到 `/var/lib/kaidi-update/processing`;进程或主机中断后由 systemd 恢复未完成事务。
|
||||||
5. root 更新器重新验签和验哈希,确认 `mysqldump` 成功并生成权限为 `0600` 的备份,默认保留最近 5 份。安装器不安装
|
5. root 更新器重新验签和验哈希,确认 `mysqldump` 成功并生成权限为 `0600` 的备份,默认保留最近 5 份。安装器不安装
|
||||||
`mysqldump`;启用在线更新前,运维人员须自行在应用服务器预装与外部 MySQL 兼容的 `mysqldump`。
|
`mysqldump`;启用在线更新前,运维人员须自行在应用服务器预装与外部 MySQL 兼容的 `mysqldump`。
|
||||||
6. 校验更新脚本和 systemd 单元后,原子切换 updater、systemd 和应用版本,不修改反向代理。
|
6. 校验更新脚本和 systemd 单元后,在旧 Java 仍运行时原子切换版本链接,再由 systemd 执行一次 `restart`,不修改反向代理。
|
||||||
7. 同时检查 Java 健康端点、更新 path unit 和 Java 托管的静态首页。全部通过后页面显示 10 秒倒计时并自动
|
7. 同时检查 Java 健康端点和 Java 托管的静态首页。全部通过后页面显示 10 秒倒计时并自动
|
||||||
刷新;刷新或短暂断线发生在安装中时,页面会恢复 3 秒轮询。任一检查失败则恢复并验证上一版本。
|
刷新;刷新或短暂断线发生在安装中时,页面会恢复 3 秒轮询。任一检查失败则恢复并验证上一版本。
|
||||||
|
|
||||||
忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新,
|
忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新,
|
||||||
不会延迟服务端切换。systemd 在 300 秒内连续失败 3 次后停止自动重试,避免失败任务空跑。
|
不会延迟服务端切换。失败请求会被归档且不会由 systemd 自动重试,避免同一事务反复停止业务服务。
|
||||||
|
|
||||||
安装器会把公共 API 地址写入 root-only 的 `/etc/kaidi/kaidi.env` 与 `/etc/kaidi/update.env`,两者权限均为
|
安装器会把公共 API 地址写入 root-only 的 `/etc/kaidi/kaidi.env` 与 `/etc/kaidi/update.env`,两者权限均为
|
||||||
`0600`:前者供 Java 后端“检查更新”读取,后者供 root 更新器下载资产。默认 Token 为空;如改接私有镜像,
|
`0600`:前者供 Java 后端“检查更新”读取,后者供 root 更新器下载资产。默认 Token 为空;如改接私有镜像,
|
||||||
@@ -244,9 +337,9 @@ sudo journalctl -u kaidi-update.service -n 100 --no-pager
|
|||||||
cat /var/lib/kaidi-update/status.json
|
cat /var/lib/kaidi-update/status.json
|
||||||
```
|
```
|
||||||
|
|
||||||
如果 `status.json` 显示 `FAILED` 且日志提示回滚未完成,不要删除
|
如果 `status.json` 显示 `FAILED` 且日志提示回滚未完成,失败请求会归档到
|
||||||
`/var/lib/kaidi-update/processing/request.json` 或活动事务目录。systemd 在 300 秒内连续失败 3 次后会停止自动重试,
|
`/var/lib/kaidi-update/failed`,活动事务保留在 `transactions/active`,系统不会自动再次执行。修复日志所示的
|
||||||
修复日志所示的磁盘、权限或旧版本健康问题后执行:
|
磁盘、权限或旧版本健康问题后,才明确执行一次:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl reset-failed kaidi-update.service kaidi-update.path
|
sudo systemctl reset-failed kaidi-update.service kaidi-update.path
|
||||||
@@ -263,13 +356,13 @@ cat /var/lib/kaidi-update/status.json
|
|||||||
```bash
|
```bash
|
||||||
KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \
|
KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \
|
||||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
||||||
./scripts/package-release.sh 1.0.0-preview.19
|
./scripts/package-release.sh 1.0.0-preview.24
|
||||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
||||||
./scripts/verify-release.sh dist/release
|
./scripts/verify-release.sh dist/release
|
||||||
```
|
```
|
||||||
|
|
||||||
输出位于 `dist/release/`,包含安装脚本、签名清单、公钥、SHA-256 清单、前后端 CycloneDX SBOM、
|
输出位于 `dist/release/`,包含安装脚本、彻底清理脚本、签名清单、公钥、SHA-256 清单、前后端 CycloneDX
|
||||||
首次安装指纹文件和完整应用压缩包。
|
SBOM、首次安装指纹文件和完整应用压缩包。
|
||||||
打包脚本会把 Maven `revision` 与 npm 包版本临时绑定到 Release SemVer,构建结束后恢复工作区源文件;
|
打包脚本会把 Maven `revision` 与 npm 包版本临时绑定到 Release SemVer,构建结束后恢复工作区源文件;
|
||||||
JAR、两个 SBOM、签名清单或 tag 的版本只要有一项不一致,发布即失败。
|
JAR、两个 SBOM、签名清单或 tag 的版本只要有一项不一致,发布即失败。
|
||||||
签名清单同时绑定应用包、两份 SBOM、安装器、公钥、bootstrap 指纹和 Git 源码修订;本地脏工作区会明确记录
|
签名清单同时绑定应用包、两份 SBOM、安装器、公钥、bootstrap 指纹和 Git 源码修订;本地脏工作区会明确记录
|
||||||
|
|||||||
Executable
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf '[kaidi-baota] ERROR: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
RELEASE_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||||
|
APP_JAR="$RELEASE_ROOT/app.jar"
|
||||||
|
PUBLIC_INDEX="$RELEASE_ROOT/public/index.html"
|
||||||
|
VERSION_FILE="$RELEASE_ROOT/VERSION"
|
||||||
|
|
||||||
|
[ -s "$APP_JAR" ] || die "app.jar is missing from $RELEASE_ROOT"
|
||||||
|
[ -s "$PUBLIC_INDEX" ] || die "public/index.html is missing from $RELEASE_ROOT"
|
||||||
|
[ -s "$VERSION_FILE" ] || die "VERSION is missing from $RELEASE_ROOT"
|
||||||
|
|
||||||
|
JAVA_BIN=${KAIDI_JAVA_BIN:-}
|
||||||
|
if [ -z "$JAVA_BIN" ] && [ -n "${JAVA_HOME:-}" ]; then
|
||||||
|
JAVA_BIN="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ -z "$JAVA_BIN" ]; then
|
||||||
|
JAVA_BIN=$(command -v java 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
[ -n "$JAVA_BIN" ] || die "Java 17 or newer was not found"
|
||||||
|
if [[ "$JAVA_BIN" == */* ]]; then
|
||||||
|
[ -x "$JAVA_BIN" ] || die "Java executable is not available at $JAVA_BIN"
|
||||||
|
else
|
||||||
|
command -v "$JAVA_BIN" >/dev/null 2>&1 || die "Java executable $JAVA_BIN is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-production}
|
||||||
|
export SERVER_ADDRESS=${SERVER_ADDRESS:-127.0.0.1}
|
||||||
|
export SERVER_PORT=${SERVER_PORT:-18080}
|
||||||
|
export FINANCE_SETUP_ENABLED=${FINANCE_SETUP_ENABLED:-false}
|
||||||
|
export FINANCE_BOOTSTRAP_ENABLED=${FINANCE_BOOTSTRAP_ENABLED:-false}
|
||||||
|
export FINANCE_UPDATE_ENABLED=${FINANCE_UPDATE_ENABLED:-false}
|
||||||
|
export FILE_SCANNER_ENABLED=${FILE_SCANNER_ENABLED:-false}
|
||||||
|
export FINANCE_STATIC_LOCATIONS=${FINANCE_STATIC_LOCATIONS:-file:$RELEASE_ROOT/public/}
|
||||||
|
export UPDATE_CURRENT_VERSION_FILE=${UPDATE_CURRENT_VERSION_FILE:-$VERSION_FILE}
|
||||||
|
export APP_VERSION=${APP_VERSION:-$(tr -d '\r\n' < "$VERSION_FILE")}
|
||||||
|
|
||||||
|
if [ "$FINANCE_SETUP_ENABLED" != true ]; then
|
||||||
|
for name in DB_URL DB_USERNAME DB_PASSWORD FIELD_ENCRYPTION_KEY FILE_STORAGE_ROOT FILE_STORAGE_TEMP; do
|
||||||
|
[ -n "${!name:-}" ] || die "$name must be configured in the Baota project environment"
|
||||||
|
done
|
||||||
|
for directory in "$FILE_STORAGE_ROOT" "$FILE_STORAGE_TEMP"; do
|
||||||
|
[ -d "$directory" ] || die "Storage directory $directory does not exist"
|
||||||
|
[ -w "$directory" ] || die "Startup user cannot write storage directory $directory"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$RELEASE_ROOT"
|
||||||
|
exec "$JAVA_BIN" -XX:MaxRAMPercentage=70 -Dfile.encoding=UTF-8 -jar "$APP_JAR"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
SPRING_PROFILES_ACTIVE=production
|
||||||
|
SERVER_ADDRESS=127.0.0.1
|
||||||
|
SERVER_PORT=18080
|
||||||
|
SESSION_COOKIE_SECURE=false
|
||||||
|
DB_URL=jdbc:mysql://MYSQL_HOST:3306/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC
|
||||||
|
DB_USERNAME=MYSQL_USERNAME
|
||||||
|
DB_PASSWORD=MYSQL_PASSWORD
|
||||||
|
FIELD_ENCRYPTION_KEY=EXISTING_FIELD_ENCRYPTION_KEY
|
||||||
|
FILE_STORAGE_ROOT=/var/lib/kaidi/files
|
||||||
|
FILE_STORAGE_TEMP=/var/lib/kaidi/tmp
|
||||||
|
FILE_SCANNER_ENABLED=false
|
||||||
|
FINANCE_BOOTSTRAP_ENABLED=false
|
||||||
|
FINANCE_SETUP_ENABLED=false
|
||||||
|
FINANCE_UPDATE_ENABLED=false
|
||||||
@@ -5,7 +5,7 @@ umask 077
|
|||||||
|
|
||||||
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
INSTALLER="$ROOT/deploy/install.sh"
|
INSTALLER="$ROOT/deploy/install.sh"
|
||||||
INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-bc9001197af843dc323907a98023064ccc4f184e000543d2086b8a6f8161dbc7}
|
INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-6256b05025aa314d4c02e7c0f5d2ca530f28ec5d10f833504b34c09ada6e02a5}
|
||||||
RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest}
|
RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest}
|
||||||
PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}
|
PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}
|
||||||
TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-}
|
TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-}
|
||||||
|
|||||||
+3
-1
@@ -344,7 +344,7 @@ secure_release_tree() {
|
|||||||
chown -R root:"$SERVICE_GROUP" "$release_dir"
|
chown -R root:"$SERVICE_GROUP" "$release_dir"
|
||||||
find "$release_dir" -type d -exec chmod 0750 {} +
|
find "$release_dir" -type d -exec chmod 0750 {} +
|
||||||
find "$release_dir" -type f -exec chmod 0640 {} +
|
find "$release_dir" -type f -exec chmod 0640 {} +
|
||||||
chmod 0750 "$release_dir/ops/update.sh"
|
chmod 0750 "$release_dir/ops/update.sh" "$release_dir/ops/baota-start.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_security_contexts() {
|
restore_security_contexts() {
|
||||||
@@ -848,6 +848,8 @@ find "$WORK_DIR/extracted" -type l -print -quit | grep -q . \
|
|||||||
[ -s "$WORK_DIR/extracted/public/index.html" ] || die "Release frontend is missing"
|
[ -s "$WORK_DIR/extracted/public/index.html" ] || die "Release frontend is missing"
|
||||||
[ "$(cat "$WORK_DIR/extracted/VERSION")" = "$VERSION" ] || die "Release version mismatch"
|
[ "$(cat "$WORK_DIR/extracted/VERSION")" = "$VERSION" ] || die "Release version mismatch"
|
||||||
[ -x "$WORK_DIR/extracted/ops/update.sh" ] || die "Release updater is missing"
|
[ -x "$WORK_DIR/extracted/ops/update.sh" ] || die "Release updater is missing"
|
||||||
|
[ -x "$WORK_DIR/extracted/ops/baota-start.sh" ] || die "Baota Spring Boot launcher is missing"
|
||||||
|
bash -n "$WORK_DIR/extracted/ops/baota-start.sh" || die "Baota Spring Boot launcher is invalid"
|
||||||
|
|
||||||
configure_database
|
configure_database
|
||||||
preflight_database
|
preflight_database
|
||||||
|
|||||||
Executable
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
APP_ROOT=/opt/kaidi
|
||||||
|
BAOTA_ROOT=/www/wwwroot/kaidi
|
||||||
|
CONFIG_ROOT=/etc/kaidi
|
||||||
|
STATE_ROOT=/var/lib/kaidi
|
||||||
|
UPDATE_STATE_ROOT=/var/lib/kaidi-update
|
||||||
|
LOG_ROOT=/var/log/kaidi
|
||||||
|
FIRST_LOGIN_FILE=/root/kaidi-first-login.txt
|
||||||
|
BACKUP_ROOT=${KAIDI_PURGE_BACKUP_ROOT:-/root/kaidi-reinstall-backups}
|
||||||
|
CONFIRMATION=${KAIDI_PURGE_CONFIRM:-}
|
||||||
|
REQUIRED_CONFIRMATION=DELETE_LOCAL_KAIDI_INSTALLATION
|
||||||
|
SERVICE_USER=kaidi
|
||||||
|
SERVICE_GROUP=kaidi
|
||||||
|
BACKUP_ARCHIVE=
|
||||||
|
|
||||||
|
log() { printf '[kaidi-purge] %s\n' "$*"; }
|
||||||
|
die() { printf '[kaidi-purge] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
systemd_available() {
|
||||||
|
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
[ "$(id -u)" -eq 0 ] || die "Run with sudo or as root"
|
||||||
|
[ "$(uname -s)" = Linux ] || die "The purge script only supports Linux"
|
||||||
|
[ "$CONFIRMATION" = "$REQUIRED_CONFIRMATION" ] \
|
||||||
|
|| die "Set KAIDI_PURGE_CONFIRM=$REQUIRED_CONFIRMATION to confirm local removal"
|
||||||
|
case "$BACKUP_ROOT" in
|
||||||
|
/*) ;;
|
||||||
|
*) die "KAIDI_PURGE_BACKUP_ROOT must be an absolute path" ;;
|
||||||
|
esac
|
||||||
|
case "$BACKUP_ROOT/" in
|
||||||
|
"$APP_ROOT/"*|"$BAOTA_ROOT/"*|"$CONFIG_ROOT/"*|"$STATE_ROOT/"*|\
|
||||||
|
"$UPDATE_STATE_ROOT/"*|"$LOG_ROOT/"*)
|
||||||
|
die "The recovery backup must be outside every managed Kaidi directory"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
[ ! -L "$BACKUP_ROOT" ] || die "The recovery backup root must not be a symbolic link"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_systemd_units() {
|
||||||
|
local unit
|
||||||
|
systemd_available || return 0
|
||||||
|
for unit in kaidi-update.path kaidi-update.service kaidi-finance.service; do
|
||||||
|
if systemctl cat "$unit" >/dev/null 2>&1; then
|
||||||
|
systemctl stop "$unit" >/dev/null 2>&1 \
|
||||||
|
|| die "Failed to stop $unit"
|
||||||
|
systemctl disable "$unit" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
related_pids() {
|
||||||
|
local proc pid command_line
|
||||||
|
for proc in /proc/[0-9]*; do
|
||||||
|
[ -r "$proc/cmdline" ] || continue
|
||||||
|
pid=${proc##*/}
|
||||||
|
[ "$pid" != "$$" ] && [ "$pid" != "$PPID" ] || continue
|
||||||
|
command_line=$(tr '\000' ' ' < "$proc/cmdline" 2>/dev/null || true)
|
||||||
|
case "$command_line" in
|
||||||
|
*"$APP_ROOT/"*|*"$BAOTA_ROOT/"*) printf '%s\n' "$pid" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_managed_processes() {
|
||||||
|
local deadline
|
||||||
|
local -a pids=()
|
||||||
|
mapfile -t pids < <(related_pids)
|
||||||
|
if [ "${#pids[@]}" -gt 0 ]; then
|
||||||
|
log "Stopping remaining Kaidi processes: ${pids[*]}"
|
||||||
|
kill -TERM "${pids[@]}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
deadline=$((SECONDS + 5))
|
||||||
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
||||||
|
mapfile -t pids < <(related_pids)
|
||||||
|
[ "${#pids[@]}" -gt 0 ] || return 0
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
kill -KILL "${pids[@]}" 2>/dev/null || true
|
||||||
|
sleep 1
|
||||||
|
mapfile -t pids < <(related_pids)
|
||||||
|
[ "${#pids[@]}" -eq 0 ] \
|
||||||
|
|| die "A process manager is restarting Kaidi; remove the Kaidi project from Baota and run this command again"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_recovery_backup() {
|
||||||
|
local path relative temporary timestamp
|
||||||
|
local -a paths=()
|
||||||
|
for path in \
|
||||||
|
"$CONFIG_ROOT" \
|
||||||
|
"$STATE_ROOT" \
|
||||||
|
"$UPDATE_STATE_ROOT/backups" \
|
||||||
|
"$UPDATE_STATE_ROOT/failed" \
|
||||||
|
"$UPDATE_STATE_ROOT/transactions" \
|
||||||
|
"$FIRST_LOGIN_FILE"; do
|
||||||
|
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||||
|
relative=${path#/}
|
||||||
|
paths+=("$relative")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "${#paths[@]}" -eq 0 ]; then
|
||||||
|
log "No local configuration or data needs a recovery backup"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
install -d -o root -g root -m 0700 "$BACKUP_ROOT"
|
||||||
|
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
BACKUP_ARCHIVE="$BACKUP_ROOT/kaidi-local-state-$timestamp.tar.gz"
|
||||||
|
temporary="$BACKUP_ARCHIVE.next.$$"
|
||||||
|
log "Creating a root-only recovery backup at $BACKUP_ARCHIVE"
|
||||||
|
tar -czf "$temporary" -C / -- "${paths[@]}"
|
||||||
|
tar -tzf "$temporary" >/dev/null
|
||||||
|
chmod 0600 "$temporary"
|
||||||
|
mv -f "$temporary" "$BACKUP_ARCHIVE"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_systemd_units() {
|
||||||
|
local unit
|
||||||
|
rm -f \
|
||||||
|
/etc/systemd/system/kaidi-finance.service \
|
||||||
|
/etc/systemd/system/kaidi-update.service \
|
||||||
|
/etc/systemd/system/kaidi-update.path \
|
||||||
|
/etc/systemd/system/multi-user.target.wants/kaidi-finance.service \
|
||||||
|
/etc/systemd/system/multi-user.target.wants/kaidi-update.path
|
||||||
|
systemd_available || return 0
|
||||||
|
systemctl daemon-reload
|
||||||
|
for unit in kaidi-finance.service kaidi-update.service kaidi-update.path; do
|
||||||
|
systemctl reset-failed "$unit" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_managed_paths() {
|
||||||
|
rm -rf -- \
|
||||||
|
"$APP_ROOT" \
|
||||||
|
"$BAOTA_ROOT" \
|
||||||
|
"$CONFIG_ROOT" \
|
||||||
|
"$STATE_ROOT" \
|
||||||
|
"$UPDATE_STATE_ROOT" \
|
||||||
|
"$LOG_ROOT"
|
||||||
|
rm -f -- "$FIRST_LOGIN_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_download_archives() {
|
||||||
|
rm -f -- /tmp/kaidi-finance-*.tar.gz
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_service_identity() {
|
||||||
|
local entry home shell group_entry gid members primary_users
|
||||||
|
entry=$(getent passwd "$SERVICE_USER" 2>/dev/null || true)
|
||||||
|
if [ -n "$entry" ]; then
|
||||||
|
home=$(printf '%s\n' "$entry" | awk -F: '{print $6}')
|
||||||
|
shell=$(printf '%s\n' "$entry" | awk -F: '{print $7}')
|
||||||
|
case "$home:$shell" in
|
||||||
|
"$STATE_ROOT:"*/nologin|"$STATE_ROOT:"*/false)
|
||||||
|
userdel "$SERVICE_USER" \
|
||||||
|
|| die "Failed to remove the dedicated $SERVICE_USER service account"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log "Keeping pre-existing user $SERVICE_USER because its home or shell is not Kaidi-managed"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
group_entry=$(getent group "$SERVICE_GROUP" 2>/dev/null || true)
|
||||||
|
[ -n "$group_entry" ] || return 0
|
||||||
|
gid=$(printf '%s\n' "$group_entry" | awk -F: '{print $3}')
|
||||||
|
members=$(printf '%s\n' "$group_entry" | awk -F: '{print $4}')
|
||||||
|
primary_users=$(getent passwd | awk -F: -v gid="$gid" '$4 == gid { print $1 }')
|
||||||
|
if [ -z "$members" ] && [ -z "$primary_users" ]; then
|
||||||
|
groupdel "$SERVICE_GROUP" \
|
||||||
|
|| die "Failed to remove the dedicated $SERVICE_GROUP service group"
|
||||||
|
else
|
||||||
|
log "Keeping group $SERVICE_GROUP because another account still uses it"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
validate_inputs
|
||||||
|
log "External MySQL data and reverse-proxy configuration will not be modified"
|
||||||
|
stop_systemd_units
|
||||||
|
stop_managed_processes
|
||||||
|
create_recovery_backup
|
||||||
|
remove_systemd_units
|
||||||
|
remove_managed_paths
|
||||||
|
remove_download_archives
|
||||||
|
remove_service_identity
|
||||||
|
stop_managed_processes
|
||||||
|
log "Local Kaidi installation state has been removed"
|
||||||
|
if [ -n "$BACKUP_ARCHIVE" ]; then
|
||||||
|
log "Recovery backup: $BACKUP_ARCHIVE"
|
||||||
|
fi
|
||||||
|
log "Use a new empty MySQL database for the next installation"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -4,8 +4,6 @@ Description=Watch for Kaidi Finance update requests
|
|||||||
[Path]
|
[Path]
|
||||||
PathChanged=/var/lib/kaidi-update/inbox
|
PathChanged=/var/lib/kaidi-update/inbox
|
||||||
PathExists=/var/lib/kaidi-update/inbox/request.json
|
PathExists=/var/lib/kaidi-update/inbox/request.json
|
||||||
PathChanged=/var/lib/kaidi-update/processing
|
|
||||||
PathExists=/var/lib/kaidi-update/processing/request.json
|
|
||||||
Unit=kaidi-update.service
|
Unit=kaidi-update.service
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ Group=root
|
|||||||
EnvironmentFile=/etc/kaidi/update.env
|
EnvironmentFile=/etc/kaidi/update.env
|
||||||
EnvironmentFile=-/var/lib/kaidi/setup/application.env
|
EnvironmentFile=-/var/lib/kaidi/setup/application.env
|
||||||
ExecStart=/opt/kaidi/bin/update.sh
|
ExecStart=/opt/kaidi/bin/update.sh
|
||||||
Restart=on-failure
|
Restart=no
|
||||||
RestartSec=15
|
|
||||||
Nice=10
|
Nice=10
|
||||||
IOSchedulingClass=best-effort
|
IOSchedulingClass=best-effort
|
||||||
IOSchedulingPriority=6
|
IOSchedulingPriority=6
|
||||||
|
|||||||
+9
-7
@@ -102,7 +102,7 @@ secure_release_tree() {
|
|||||||
chown -R root:"$SERVICE_GROUP" "$release_dir" || return 1
|
chown -R root:"$SERVICE_GROUP" "$release_dir" || return 1
|
||||||
find "$release_dir" -type d -exec chmod 0750 {} + || return 1
|
find "$release_dir" -type d -exec chmod 0750 {} + || return 1
|
||||||
find "$release_dir" -type f -exec chmod 0640 {} + || return 1
|
find "$release_dir" -type f -exec chmod 0640 {} + || return 1
|
||||||
chmod 0750 "$release_dir/ops/update.sh"
|
chmod 0750 "$release_dir/ops/update.sh" "$release_dir/ops/baota-start.sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_release_access() {
|
verify_release_access() {
|
||||||
@@ -379,7 +379,6 @@ rollback_active_transaction() {
|
|||||||
link_restored=true
|
link_restored=true
|
||||||
operations_restored=true
|
operations_restored=true
|
||||||
|
|
||||||
systemctl stop "$SERVICE_NAME" || true
|
|
||||||
if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
|
if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
|
||||||
if ! ln -sfn "$previous_target" "$APP_ROOT/current.next" \
|
if ! ln -sfn "$previous_target" "$APP_ROOT/current.next" \
|
||||||
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
||||||
@@ -390,7 +389,7 @@ rollback_active_transaction() {
|
|||||||
fi
|
fi
|
||||||
restore_operations || operations_restored=false
|
restore_operations || operations_restored=false
|
||||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||||
systemctl start "$SERVICE_NAME" || true
|
systemctl restart "$SERVICE_NAME" || systemctl start "$SERVICE_NAME" || true
|
||||||
if [ "$link_restored" = true ] && verify_app_surface; then
|
if [ "$link_restored" = true ] && verify_app_surface; then
|
||||||
remove_failed_release "$failed_release"
|
remove_failed_release "$failed_release"
|
||||||
rm -rf "$ACTIVE_TRANSACTION"
|
rm -rf "$ACTIVE_TRANSACTION"
|
||||||
@@ -400,8 +399,11 @@ rollback_active_transaction() {
|
|||||||
fail "$reason; previous release is running, but operations restoration requires manual review"
|
fail "$reason; previous release is running, but operations restoration requires manual review"
|
||||||
fi
|
fi
|
||||||
TERMINAL_STATUS_WRITTEN=true
|
TERMINAL_STATUS_WRITTEN=true
|
||||||
status FAILED "$reason; rollback is incomplete and will be retried" "${TARGET_VERSION:-}"
|
status FAILED "$reason; rollback is incomplete and manual recovery is required" "${TARGET_VERSION:-}"
|
||||||
printf '%s\n' "$reason; rollback is incomplete and will be retried" >&2
|
if ! archive_processing_request; then
|
||||||
|
printf '%s\n' "Update request could not be archived after an incomplete rollback" >&2
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$reason; rollback is incomplete and manual recovery is required" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,6 +429,7 @@ recover_interrupted_transaction() {
|
|||||||
|
|
||||||
validate_release_operations() {
|
validate_release_operations() {
|
||||||
sh -n "$WORK_DIR/extracted/ops/update.sh" || return 1
|
sh -n "$WORK_DIR/extracted/ops/update.sh" || return 1
|
||||||
|
bash -n "$WORK_DIR/extracted/ops/baota-start.sh" || return 1
|
||||||
command -v systemd-analyze >/dev/null 2>&1 || return 1
|
command -v systemd-analyze >/dev/null 2>&1 || return 1
|
||||||
systemd-analyze verify \
|
systemd-analyze verify \
|
||||||
"$WORK_DIR/extracted/ops/kaidi-finance.service" \
|
"$WORK_DIR/extracted/ops/kaidi-finance.service" \
|
||||||
@@ -613,7 +616,6 @@ apply_operations || rollback_active_transaction "Release operations files could
|
|||||||
write_transaction_value phase OPS_APPLIED
|
write_transaction_value phase OPS_APPLIED
|
||||||
|
|
||||||
write_transaction_value phase APP_SWITCHING
|
write_transaction_value phase APP_SWITCHING
|
||||||
systemctl stop "$SERVICE_NAME" || rollback_active_transaction "Application service could not be stopped"
|
|
||||||
if ! ln -sfn "$RELEASE_DIR" "$APP_ROOT/current.next" \
|
if ! ln -sfn "$RELEASE_DIR" "$APP_ROOT/current.next" \
|
||||||
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
|| ! mv -Tf "$APP_ROOT/current.next" "$APP_ROOT/current"; then
|
||||||
rollback_active_transaction "Release application link could not be activated"
|
rollback_active_transaction "Release application link could not be activated"
|
||||||
@@ -623,7 +625,7 @@ write_transaction_value phase APP_SWITCHED
|
|||||||
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
||||||
write_transaction_value phase HEALTH_CHECKING
|
write_transaction_value phase HEALTH_CHECKING
|
||||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||||
systemctl start "$SERVICE_NAME" || true
|
systemctl restart "$SERVICE_NAME" || systemctl start "$SERVICE_NAME" || true
|
||||||
if verify_app_surface; then
|
if verify_app_surface; then
|
||||||
success_message="Release $TARGET_VERSION is running"
|
success_message="Release $TARGET_VERSION is running"
|
||||||
if ! systemctl start "$UPDATE_PATH_NAME" >/dev/null 2>&1; then
|
if ! systemctl start "$UPDATE_PATH_NAME" >/dev/null 2>&1; then
|
||||||
|
|||||||
@@ -91,12 +91,14 @@ cp "$JAR" "$STAGE/app.jar"
|
|||||||
cp -R "$ROOT/frontend/dist/." "$STAGE/public/"
|
cp -R "$ROOT/frontend/dist/." "$STAGE/public/"
|
||||||
printf '%s\n' "$VERSION" > "$STAGE/VERSION"
|
printf '%s\n' "$VERSION" > "$STAGE/VERSION"
|
||||||
cp "$ROOT/deploy/update.sh" "$STAGE/ops/update.sh"
|
cp "$ROOT/deploy/update.sh" "$STAGE/ops/update.sh"
|
||||||
|
cp "$ROOT/deploy/baota-start.sh" "$STAGE/ops/baota-start.sh"
|
||||||
|
cp "$ROOT/deploy/baota.env.example" "$STAGE/ops/baota.env.example"
|
||||||
# Kept in the archive so Preview.9's updater can complete the one-time transition.
|
# Kept in the archive so Preview.9's updater can complete the one-time transition.
|
||||||
cp "$ROOT/deploy/nginx/kaidi-finance.conf" "$STAGE/ops/kaidi-finance.conf"
|
cp "$ROOT/deploy/nginx/kaidi-finance.conf" "$STAGE/ops/kaidi-finance.conf"
|
||||||
cp "$ROOT/deploy/systemd/kaidi-finance.service" "$STAGE/ops/kaidi-finance.service"
|
cp "$ROOT/deploy/systemd/kaidi-finance.service" "$STAGE/ops/kaidi-finance.service"
|
||||||
cp "$ROOT/deploy/systemd/kaidi-update.service" "$STAGE/ops/kaidi-update.service"
|
cp "$ROOT/deploy/systemd/kaidi-update.service" "$STAGE/ops/kaidi-update.service"
|
||||||
cp "$ROOT/deploy/systemd/kaidi-update.path" "$STAGE/ops/kaidi-update.path"
|
cp "$ROOT/deploy/systemd/kaidi-update.path" "$STAGE/ops/kaidi-update.path"
|
||||||
chmod 0755 "$STAGE/ops/update.sh"
|
chmod 0755 "$STAGE/ops/update.sh" "$STAGE/ops/baota-start.sh"
|
||||||
|
|
||||||
ARTIFACT="kaidi-finance-$VERSION.tar.gz"
|
ARTIFACT="kaidi-finance-$VERSION.tar.gz"
|
||||||
COPYFILE_DISABLE=1 tar --format=ustar -czf "$OUTPUT_DIR/$ARTIFACT" -C "$STAGE" .
|
COPYFILE_DISABLE=1 tar --format=ustar -czf "$OUTPUT_DIR/$ARTIFACT" -C "$STAGE" .
|
||||||
@@ -141,8 +143,10 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
install -m 0755 "$ROOT/deploy/install.sh" "$OUTPUT_DIR/install.sh"
|
install -m 0755 "$ROOT/deploy/install.sh" "$OUTPUT_DIR/install.sh"
|
||||||
|
install -m 0755 "$ROOT/deploy/purge.sh" "$OUTPUT_DIR/purge.sh"
|
||||||
PUBLIC_KEY_SHA256=$(sha256_file "$OUTPUT_DIR/release-public.pem")
|
PUBLIC_KEY_SHA256=$(sha256_file "$OUTPUT_DIR/release-public.pem")
|
||||||
INSTALLER_SHA256=$(sha256_file "$OUTPUT_DIR/install.sh")
|
INSTALLER_SHA256=$(sha256_file "$OUTPUT_DIR/install.sh")
|
||||||
|
PURGER_SHA256=$(sha256_file "$OUTPUT_DIR/purge.sh")
|
||||||
if [ -n "$TRUSTED_PUBLIC_KEY_SHA256" ]; then
|
if [ -n "$TRUSTED_PUBLIC_KEY_SHA256" ]; then
|
||||||
NORMALIZED_TRUSTED_PUBLIC_KEY_SHA256=$(printf '%s' "$TRUSTED_PUBLIC_KEY_SHA256" | tr '[:upper:]' '[:lower:]')
|
NORMALIZED_TRUSTED_PUBLIC_KEY_SHA256=$(printf '%s' "$TRUSTED_PUBLIC_KEY_SHA256" | tr '[:upper:]' '[:lower:]')
|
||||||
[[ "$NORMALIZED_TRUSTED_PUBLIC_KEY_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
[[ "$NORMALIZED_TRUSTED_PUBLIC_KEY_SHA256" =~ ^[0-9a-f]{64}$ ]] \
|
||||||
@@ -156,6 +160,7 @@ fi
|
|||||||
cat > "$OUTPUT_DIR/bootstrap-checksums.txt" <<EOF
|
cat > "$OUTPUT_DIR/bootstrap-checksums.txt" <<EOF
|
||||||
KAIDI_RELEASE_PUBLIC_KEY_SHA256=$PUBLIC_KEY_SHA256
|
KAIDI_RELEASE_PUBLIC_KEY_SHA256=$PUBLIC_KEY_SHA256
|
||||||
KAIDI_INSTALLER_SHA256=$INSTALLER_SHA256
|
KAIDI_INSTALLER_SHA256=$INSTALLER_SHA256
|
||||||
|
KAIDI_PURGER_SHA256=$PURGER_SHA256
|
||||||
EOF
|
EOF
|
||||||
BOOTSTRAP_SHA256=$(sha256_file "$OUTPUT_DIR/bootstrap-checksums.txt")
|
BOOTSTRAP_SHA256=$(sha256_file "$OUTPUT_DIR/bootstrap-checksums.txt")
|
||||||
|
|
||||||
@@ -163,7 +168,8 @@ RELEASE_NOTES_VALUE=${KAIDI_RELEASE_NOTES:-"Kaidi Finance Preview $VERSION"}
|
|||||||
VERSION="$VERSION" ARTIFACT="$ARTIFACT" SHA256="$SHA256" RELEASE_NOTES="$RELEASE_NOTES_VALUE" \
|
VERSION="$VERSION" ARTIFACT="$ARTIFACT" SHA256="$SHA256" RELEASE_NOTES="$RELEASE_NOTES_VALUE" \
|
||||||
BACKEND_SBOM_SHA256="$BACKEND_SBOM_SHA256" FRONTEND_SBOM_SHA256="$FRONTEND_SBOM_SHA256" \
|
BACKEND_SBOM_SHA256="$BACKEND_SBOM_SHA256" FRONTEND_SBOM_SHA256="$FRONTEND_SBOM_SHA256" \
|
||||||
BACKEND_BUILD_VERSION="$BACKEND_BUILD_VERSION" FRONTEND_BUILD_VERSION="$FRONTEND_BUILD_VERSION" \
|
BACKEND_BUILD_VERSION="$BACKEND_BUILD_VERSION" FRONTEND_BUILD_VERSION="$FRONTEND_BUILD_VERSION" \
|
||||||
INSTALLER_SHA256="$INSTALLER_SHA256" PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
|
INSTALLER_SHA256="$INSTALLER_SHA256" PURGER_SHA256="$PURGER_SHA256" \
|
||||||
|
PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
|
||||||
BOOTSTRAP_SHA256="$BOOTSTRAP_SHA256" SOURCE_REVISION="$SOURCE_REVISION" SOURCE_REF="$SOURCE_REF" \
|
BOOTSTRAP_SHA256="$BOOTSTRAP_SHA256" SOURCE_REVISION="$SOURCE_REVISION" SOURCE_REF="$SOURCE_REF" \
|
||||||
SOURCE_DIRTY="$SOURCE_DIRTY" \
|
SOURCE_DIRTY="$SOURCE_DIRTY" \
|
||||||
node <<'NODE' > "$OUTPUT_DIR/release-manifest.json"
|
node <<'NODE' > "$OUTPUT_DIR/release-manifest.json"
|
||||||
@@ -190,6 +196,8 @@ const manifest = {
|
|||||||
bootstrap: {
|
bootstrap: {
|
||||||
installer: 'install.sh',
|
installer: 'install.sh',
|
||||||
installerSha256: process.env.INSTALLER_SHA256,
|
installerSha256: process.env.INSTALLER_SHA256,
|
||||||
|
purger: 'purge.sh',
|
||||||
|
purgerSha256: process.env.PURGER_SHA256,
|
||||||
publicKey: 'release-public.pem',
|
publicKey: 'release-public.pem',
|
||||||
publicKeySha256: process.env.PUBLIC_KEY_SHA256,
|
publicKeySha256: process.env.PUBLIC_KEY_SHA256,
|
||||||
checksums: 'bootstrap-checksums.txt',
|
checksums: 'bootstrap-checksums.txt',
|
||||||
@@ -209,14 +217,15 @@ fi
|
|||||||
cd "$OUTPUT_DIR"
|
cd "$OUTPUT_DIR"
|
||||||
if command -v sha256sum >/dev/null 2>&1; then
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
sha256sum "$ARTIFACT" backend-sbom.cdx.json frontend-sbom.cdx.json \
|
sha256sum "$ARTIFACT" backend-sbom.cdx.json frontend-sbom.cdx.json \
|
||||||
release-manifest.json release-manifest.sig release-public.pem install.sh \
|
release-manifest.json release-manifest.sig release-public.pem install.sh purge.sh \
|
||||||
bootstrap-checksums.txt > SHA256SUMS
|
bootstrap-checksums.txt > SHA256SUMS
|
||||||
else
|
else
|
||||||
shasum -a 256 "$ARTIFACT" backend-sbom.cdx.json frontend-sbom.cdx.json \
|
shasum -a 256 "$ARTIFACT" backend-sbom.cdx.json frontend-sbom.cdx.json \
|
||||||
release-manifest.json release-manifest.sig release-public.pem install.sh \
|
release-manifest.json release-manifest.sig release-public.pem install.sh purge.sh \
|
||||||
bootstrap-checksums.txt > SHA256SUMS
|
bootstrap-checksums.txt > SHA256SUMS
|
||||||
fi
|
fi
|
||||||
)
|
)
|
||||||
printf 'Release assets created in %s\n' "$OUTPUT_DIR"
|
printf 'Release assets created in %s\n' "$OUTPUT_DIR"
|
||||||
printf 'Trusted release public-key SHA-256: %s\n' "$PUBLIC_KEY_SHA256"
|
printf 'Trusted release public-key SHA-256: %s\n' "$PUBLIC_KEY_SHA256"
|
||||||
printf 'Installer SHA-256: %s\n' "$INSTALLER_SHA256"
|
printf 'Installer SHA-256: %s\n' "$INSTALLER_SHA256"
|
||||||
|
printf 'Purger SHA-256: %s\n' "$PURGER_SHA256"
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ ASSETS=(
|
|||||||
release-manifest.sig
|
release-manifest.sig
|
||||||
release-public.pem
|
release-public.pem
|
||||||
install.sh
|
install.sh
|
||||||
|
purge.sh
|
||||||
bootstrap-checksums.txt
|
bootstrap-checksums.txt
|
||||||
SHA256SUMS
|
SHA256SUMS
|
||||||
)
|
)
|
||||||
@@ -124,7 +125,7 @@ jq -n \
|
|||||||
> "$WORK/publish.json"
|
> "$WORK/publish.json"
|
||||||
publish_status=$(request PATCH "$API/releases/$RELEASE_ID" "$WORK/published.json" "$WORK/publish.json")
|
publish_status=$(request PATCH "$API/releases/$RELEASE_ID" "$WORK/published.json" "$WORK/publish.json")
|
||||||
[ "$publish_status" = 200 ] || fail "release publication returned HTTP $publish_status"
|
[ "$publish_status" = 200 ] || fail "release publication returned HTTP $publish_status"
|
||||||
jq -e '.draft == false and .prerelease == false and (.assets | length) == 9' \
|
jq -e '.draft == false and .prerelease == false and (.assets | length) == 10' \
|
||||||
"$WORK/published.json" >/dev/null || fail 'published release state is invalid'
|
"$WORK/published.json" >/dev/null || fail 'published release state is invalid'
|
||||||
|
|
||||||
printf 'Published Gitea release %s with nine verified assets\n' "$TAG"
|
printf 'Published Gitea release %s with ten verified assets\n' "$TAG"
|
||||||
|
|||||||
Executable
+67
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
WORK=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'Baota start fixture failed: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
RELEASE="$WORK/release"
|
||||||
|
mkdir -p "$RELEASE/ops" "$RELEASE/public" "$WORK/files" "$WORK/tmp"
|
||||||
|
printf 'fixture jar\n' > "$RELEASE/app.jar"
|
||||||
|
printf '<!doctype html>\n' > "$RELEASE/public/index.html"
|
||||||
|
printf '1.0.0-preview.24\n' > "$RELEASE/VERSION"
|
||||||
|
cp "$ROOT/deploy/baota-start.sh" "$RELEASE/ops/baota-start.sh"
|
||||||
|
chmod 0755 "$RELEASE/ops/baota-start.sh"
|
||||||
|
|
||||||
|
cat > "$WORK/java" <<'SH'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
{
|
||||||
|
printf 'cwd=%s\n' "$PWD"
|
||||||
|
printf 'args=%s\n' "$*"
|
||||||
|
printf 'version=%s\n' "$APP_VERSION"
|
||||||
|
printf 'address=%s\n' "$SERVER_ADDRESS"
|
||||||
|
printf 'port=%s\n' "$SERVER_PORT"
|
||||||
|
printf 'update=%s\n' "$FINANCE_UPDATE_ENABLED"
|
||||||
|
printf 'static=%s\n' "$FINANCE_STATIC_LOCATIONS"
|
||||||
|
} > "$MOCK_JAVA_LOG"
|
||||||
|
SH
|
||||||
|
chmod 0755 "$WORK/java"
|
||||||
|
|
||||||
|
MOCK_JAVA_LOG="$WORK/java.log" \
|
||||||
|
KAIDI_JAVA_BIN="$WORK/java" \
|
||||||
|
DB_URL='jdbc:mysql://db.fixture/kaidi_finance' \
|
||||||
|
DB_USERNAME=fixture \
|
||||||
|
DB_PASSWORD='fixture-password' \
|
||||||
|
FIELD_ENCRYPTION_KEY='fixture-field-key' \
|
||||||
|
FILE_STORAGE_ROOT="$WORK/files" \
|
||||||
|
FILE_STORAGE_TEMP="$WORK/tmp" \
|
||||||
|
"$RELEASE/ops/baota-start.sh"
|
||||||
|
|
||||||
|
grep -Fqx "cwd=$RELEASE" "$WORK/java.log" || fail 'launcher did not use the release working directory'
|
||||||
|
grep -Fqx "args=-XX:MaxRAMPercentage=70 -Dfile.encoding=UTF-8 -jar $RELEASE/app.jar" "$WORK/java.log" \
|
||||||
|
|| fail 'launcher did not execute the packaged Spring Boot jar'
|
||||||
|
grep -Fqx 'version=1.0.0-preview.24' "$WORK/java.log" || fail 'launcher did not read VERSION'
|
||||||
|
grep -Fqx 'address=127.0.0.1' "$WORK/java.log" || fail 'launcher did not default to loopback'
|
||||||
|
grep -Fqx 'port=18080' "$WORK/java.log" || fail 'launcher did not default to port 18080'
|
||||||
|
grep -Fqx 'update=false' "$WORK/java.log" || fail 'launcher did not disable the systemd updater'
|
||||||
|
grep -Fqx "static=file:$RELEASE/public/" "$WORK/java.log" \
|
||||||
|
|| fail 'launcher did not bind static resources to the release directory'
|
||||||
|
|
||||||
|
if KAIDI_JAVA_BIN="$WORK/java" \
|
||||||
|
DB_USERNAME=fixture DB_PASSWORD=fixture FIELD_ENCRYPTION_KEY=fixture \
|
||||||
|
FILE_STORAGE_ROOT="$WORK/files" FILE_STORAGE_TEMP="$WORK/tmp" \
|
||||||
|
MOCK_JAVA_LOG="$WORK/missing-db.log" \
|
||||||
|
"$RELEASE/ops/baota-start.sh" > "$WORK/missing-db.out" 2>&1; then
|
||||||
|
fail 'launcher accepted a production configuration without DB_URL'
|
||||||
|
fi
|
||||||
|
grep -Fq 'DB_URL must be configured' "$WORK/missing-db.out" \
|
||||||
|
|| fail 'launcher did not report the missing database URL'
|
||||||
|
[ ! -e "$WORK/missing-db.log" ] || fail 'launcher started Java after configuration validation failed'
|
||||||
|
|
||||||
|
printf 'Baota Spring Boot launcher fixture passed\n'
|
||||||
@@ -24,6 +24,7 @@ for name in \
|
|||||||
release-manifest.sig \
|
release-manifest.sig \
|
||||||
release-public.pem \
|
release-public.pem \
|
||||||
install.sh \
|
install.sh \
|
||||||
|
purge.sh \
|
||||||
bootstrap-checksums.txt \
|
bootstrap-checksums.txt \
|
||||||
SHA256SUMS; do
|
SHA256SUMS; do
|
||||||
printf 'fixture asset %s\n' "$name" > "$RELEASE_DIR/$name"
|
printf 'fixture asset %s\n' "$name" > "$RELEASE_DIR/$name"
|
||||||
@@ -102,11 +103,11 @@ GITEA_TOKEN="$TOKEN" \
|
|||||||
KAIDI_RELEASE_DIR="$RELEASE_DIR" \
|
KAIDI_RELEASE_DIR="$RELEASE_DIR" \
|
||||||
"$ROOT/scripts/publish-gitea-release.sh" > "$WORK/publish.log"
|
"$ROOT/scripts/publish-gitea-release.sh" > "$WORK/publish.log"
|
||||||
|
|
||||||
grep -Fqx "Published Gitea release $TAG with nine verified assets" "$WORK/publish.log" \
|
grep -Fqx "Published Gitea release $TAG with ten verified assets" "$WORK/publish.log" \
|
||||||
|| fail 'publish script did not report success'
|
|| fail 'publish script did not report success'
|
||||||
[ "$(wc -l < "$WORK/assets.jsonl" | tr -d '[:space:]')" -eq 9 ] \
|
[ "$(wc -l < "$WORK/assets.jsonl" | tr -d '[:space:]')" -eq 10 ] \
|
||||||
|| fail 'publish script did not upload exactly nine assets'
|
|| fail 'publish script did not upload exactly ten assets'
|
||||||
! grep -Fq "$TOKEN" "$WORK/curl.log" \
|
! grep -Fq "$TOKEN" "$WORK/curl.log" \
|
||||||
|| fail 'Gitea write token leaked into curl process arguments'
|
|| fail 'Gitea write token leaked into curl process arguments'
|
||||||
|
|
||||||
printf 'Gitea draft, nine-asset verification, and publication fixture passed\n'
|
printf 'Gitea draft, ten-asset verification, and publication fixture passed\n'
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ mkdir -p "$release_permissions/public" "$release_permissions/ops"
|
|||||||
printf 'jar\n' > "$release_permissions/app.jar"
|
printf 'jar\n' > "$release_permissions/app.jar"
|
||||||
printf 'html\n' > "$release_permissions/public/index.html"
|
printf 'html\n' > "$release_permissions/public/index.html"
|
||||||
printf '#!/bin/sh\n' > "$release_permissions/ops/update.sh"
|
printf '#!/bin/sh\n' > "$release_permissions/ops/update.sh"
|
||||||
|
printf '#!/usr/bin/env bash\n' > "$release_permissions/ops/baota-start.sh"
|
||||||
chmod -R 0777 "$release_permissions"
|
chmod -R 0777 "$release_permissions"
|
||||||
(
|
(
|
||||||
# shellcheck disable=SC2329 # Invoked indirectly by the sourced installer helper.
|
# shellcheck disable=SC2329 # Invoked indirectly by the sourced installer helper.
|
||||||
@@ -316,6 +317,10 @@ grep -Fq 'systemctl reset-failed kaidi-finance.service' "$ROOT/deploy/install.sh
|
|||||||
|| fail 'installer can execute before the complete curl stream is parsed'
|
|| fail 'installer can execute before the complete curl stream is parsed'
|
||||||
grep -Fqx 'StartLimitBurst=3' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
grep -Fqx 'StartLimitBurst=3' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
||||||
|| fail 'application service no longer has a bounded restart burst'
|
|| fail 'application service no longer has a bounded restart burst'
|
||||||
|
grep -Fqx 'Restart=no' "$ROOT/deploy/systemd/kaidi-update.service" \
|
||||||
|
|| fail 'update service can automatically repeat a failed switching transaction'
|
||||||
|
! grep -Fq '/var/lib/kaidi-update/processing' "$ROOT/deploy/systemd/kaidi-update.path" \
|
||||||
|
|| fail 'update path can automatically repeat a claimed switching transaction'
|
||||||
# shellcheck disable=SC2016 # Match literal installer source.
|
# shellcheck disable=SC2016 # Match literal installer source.
|
||||||
grep -Fq '[ "$actual_sha256" = "$java_sha256" ]' "$ROOT/deploy/install.sh" \
|
grep -Fq '[ "$actual_sha256" = "$java_sha256" ]' "$ROOT/deploy/install.sh" \
|
||||||
|| fail 'installer no longer verifies the Java runtime SHA-256'
|
|| fail 'installer no longer verifies the Java runtime SHA-256'
|
||||||
|
|||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
WORK=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'Purge fixture failed: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
sed '$d' "$ROOT/deploy/purge.sh" > "$WORK/purge-functions.sh"
|
||||||
|
# shellcheck disable=SC1090,SC1091
|
||||||
|
source "$WORK/purge-functions.sh"
|
||||||
|
|
||||||
|
install() {
|
||||||
|
local -a arguments=()
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-o|-g) shift 2 ;;
|
||||||
|
*) arguments+=("$1"); shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
command install "${arguments[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_ROOT="$WORK/opt/kaidi"
|
||||||
|
BAOTA_ROOT="$WORK/www/kaidi"
|
||||||
|
CONFIG_ROOT="$WORK/etc/kaidi"
|
||||||
|
STATE_ROOT="$WORK/var/lib/kaidi"
|
||||||
|
UPDATE_STATE_ROOT="$WORK/var/lib/kaidi-update"
|
||||||
|
LOG_ROOT="$WORK/var/log/kaidi"
|
||||||
|
FIRST_LOGIN_FILE="$WORK/root/kaidi-first-login.txt"
|
||||||
|
# shellcheck disable=SC2034 # Referenced by the sourced purge helper.
|
||||||
|
BACKUP_ROOT="$WORK/recovery"
|
||||||
|
BACKUP_ARCHIVE=
|
||||||
|
|
||||||
|
mkdir -p \
|
||||||
|
"$APP_ROOT" \
|
||||||
|
"$BAOTA_ROOT" \
|
||||||
|
"$CONFIG_ROOT" \
|
||||||
|
"$STATE_ROOT/files" \
|
||||||
|
"$UPDATE_STATE_ROOT/backups" \
|
||||||
|
"$UPDATE_STATE_ROOT/failed" \
|
||||||
|
"$UPDATE_STATE_ROOT/transactions" \
|
||||||
|
"$LOG_ROOT" \
|
||||||
|
"$(dirname "$FIRST_LOGIN_FILE")" \
|
||||||
|
"$WORK/external-mysql" \
|
||||||
|
"$WORK/reverse-proxy"
|
||||||
|
printf 'DB_PASSWORD="secret"\n' > "$CONFIG_ROOT/kaidi.env"
|
||||||
|
printf 'document\n' > "$STATE_ROOT/files/document.txt"
|
||||||
|
printf 'dump\n' > "$UPDATE_STATE_ROOT/backups/mysql.sql"
|
||||||
|
printf 'setup code\n' > "$FIRST_LOGIN_FILE"
|
||||||
|
printf 'database sentinel\n' > "$WORK/external-mysql/keep"
|
||||||
|
printf 'proxy sentinel\n' > "$WORK/reverse-proxy/keep"
|
||||||
|
|
||||||
|
create_recovery_backup
|
||||||
|
[ -s "$BACKUP_ARCHIVE" ] || fail 'root-only recovery archive was not created'
|
||||||
|
[ "$(stat -c '%a' "$BACKUP_ARCHIVE" 2>/dev/null || stat -f '%Lp' "$BACKUP_ARCHIVE")" = 600 ] \
|
||||||
|
|| fail 'recovery archive mode is not 0600'
|
||||||
|
archive_list=$(tar -tzf "$BACKUP_ARCHIVE")
|
||||||
|
grep -Fq "${CONFIG_ROOT#/}/kaidi.env" <<< "$archive_list" \
|
||||||
|
|| fail 'configuration was omitted from the recovery archive'
|
||||||
|
grep -Fq "${STATE_ROOT#/}/files/document.txt" <<< "$archive_list" \
|
||||||
|
|| fail 'file storage was omitted from the recovery archive'
|
||||||
|
grep -Fq "${UPDATE_STATE_ROOT#/}/backups/mysql.sql" <<< "$archive_list" \
|
||||||
|
|| fail 'database backup was omitted from the recovery archive'
|
||||||
|
|
||||||
|
remove_managed_paths
|
||||||
|
for path in "$APP_ROOT" "$BAOTA_ROOT" "$CONFIG_ROOT" "$STATE_ROOT" "$UPDATE_STATE_ROOT" "$LOG_ROOT"; do
|
||||||
|
[ ! -e "$path" ] || fail "managed path was not removed: $path"
|
||||||
|
done
|
||||||
|
[ ! -e "$FIRST_LOGIN_FILE" ] || fail 'first-login file was not removed'
|
||||||
|
[ -f "$WORK/external-mysql/keep" ] || fail 'external database state was removed'
|
||||||
|
[ -f "$WORK/reverse-proxy/keep" ] || fail 'reverse-proxy state was removed'
|
||||||
|
[ -s "$BACKUP_ARCHIVE" ] || fail 'recovery archive was removed with the installation'
|
||||||
|
|
||||||
|
[ "$(tail -n 1 "$ROOT/deploy/purge.sh")" = 'main "$@"' ] \
|
||||||
|
|| fail 'purge actions can execute before a complete curl stream is parsed'
|
||||||
|
# shellcheck disable=SC2016 # Match the literal guard in the purge source.
|
||||||
|
grep -Fq 'KAIDI_PURGE_CONFIRM=$REQUIRED_CONFIRMATION' "$ROOT/deploy/purge.sh" \
|
||||||
|
|| fail 'destructive removal no longer requires explicit confirmation'
|
||||||
|
grep -Fq 'External MySQL data and reverse-proxy configuration will not be modified' "$ROOT/deploy/purge.sh" \
|
||||||
|
|| fail 'purge boundary is no longer explicit'
|
||||||
|
|
||||||
|
printf 'Local purge and recovery fixture passed\n'
|
||||||
@@ -178,7 +178,9 @@ build_release() {
|
|||||||
printf '<!doctype html><title>new</title>\n' > "$stage/public/index.html"
|
printf '<!doctype html><title>new</title>\n' > "$stage/public/index.html"
|
||||||
printf '%s\n' "$version" > "$stage/VERSION"
|
printf '%s\n' "$version" > "$stage/VERSION"
|
||||||
printf '#!/bin/sh\nprintf "new updater\\n"\n' > "$stage/ops/update.sh"
|
printf '#!/bin/sh\nprintf "new updater\\n"\n' > "$stage/ops/update.sh"
|
||||||
|
printf '#!/usr/bin/env bash\nexit 0\n' > "$stage/ops/baota-start.sh"
|
||||||
chmod 0755 "$stage/ops/update.sh"
|
chmod 0755 "$stage/ops/update.sh"
|
||||||
|
chmod 0755 "$stage/ops/baota-start.sh"
|
||||||
for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do
|
for name in kaidi-finance.service kaidi-update.service kaidi-update.path; do
|
||||||
printf '%s %s\n' "$unit_prefix" "$name" > "$stage/ops/$name"
|
printf '%s %s\n' "$unit_prefix" "$name" > "$stage/ops/$name"
|
||||||
done
|
done
|
||||||
@@ -364,6 +366,10 @@ assert_success_case() {
|
|||||||
[ ! -e "$fixture/state/processing/request.json" ] \
|
[ ! -e "$fixture/state/processing/request.json" ] \
|
||||||
|| fail 'success case left a claimed request behind'
|
|| fail 'success case left a claimed request behind'
|
||||||
grep -qx 'daemon-reload' "$fixture/systemctl.log" || fail 'systemd units were not reloaded'
|
grep -qx 'daemon-reload' "$fixture/systemctl.log" || fail 'systemd units were not reloaded'
|
||||||
|
grep -qx 'restart kaidi-finance.service' "$fixture/systemctl.log" \
|
||||||
|
|| fail 'success case did not restart the application after the atomic switch'
|
||||||
|
! grep -qx 'stop kaidi-finance.service' "$fixture/systemctl.log" \
|
||||||
|
|| fail 'success case stopped the application before switching releases'
|
||||||
! grep -qi nginx "$fixture/systemctl.log" || fail 'updater unexpectedly managed Nginx'
|
! grep -qi nginx "$fixture/systemctl.log" || fail 'updater unexpectedly managed Nginx'
|
||||||
[ "$(mode_of "$fixture/app")" = 750 ] || fail 'application root is not traversable by the service group'
|
[ "$(mode_of "$fixture/app")" = 750 ] || fail 'application root is not traversable by the service group'
|
||||||
[ "$(mode_of "$fixture/app/releases/$version")" = 750 ] || fail 'release root mode is not 0750'
|
[ "$(mode_of "$fixture/app/releases/$version")" = 750 ] || fail 'release root mode is not 0750'
|
||||||
@@ -426,6 +432,10 @@ assert_rollback_case() {
|
|||||||
|| fail 'rollback case did not restore the previous updater'
|
|| fail 'rollback case did not restore the previous updater'
|
||||||
grep -qx 'old kaidi-update.path' "$fixture/systemd/kaidi-update.path" \
|
grep -qx 'old kaidi-update.path' "$fixture/systemd/kaidi-update.path" \
|
||||||
|| fail 'rollback case did not restore the previous path unit'
|
|| fail 'rollback case did not restore the previous path unit'
|
||||||
|
! grep -qx 'stop kaidi-finance.service' "$fixture/systemctl.log" \
|
||||||
|
|| fail 'rollback case stopped the application before restoring the previous release'
|
||||||
|
[ "$(grep -c '^restart kaidi-finance.service$' "$fixture/systemctl.log")" -ge 2 ] \
|
||||||
|
|| fail 'rollback case did not restart both the candidate and restored releases'
|
||||||
[ "$(jq -r '.state' "$fixture/state/status.json")" = FAILED ] \
|
[ "$(jq -r '.state' "$fixture/state/status.json")" = FAILED ] \
|
||||||
|| fail 'rollback case did not persist FAILED'
|
|| fail 'rollback case did not persist FAILED'
|
||||||
[ ! -e "$fixture/app/releases/$version" ] \
|
[ ! -e "$fixture/app/releases/$version" ] \
|
||||||
@@ -434,6 +444,28 @@ assert_rollback_case() {
|
|||||||
|| fail 'rollback case did not archive the failed request'
|
|| fail 'rollback case did not archive the failed request'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_incomplete_rollback_requires_manual_recovery_case() {
|
||||||
|
local fixture="$WORK/incomplete-rollback"
|
||||||
|
local version='1.0.0-preview.2'
|
||||||
|
mkdir -p "$fixture"
|
||||||
|
write_mock_commands "$fixture/mock-bin"
|
||||||
|
build_release "$fixture" "$version"
|
||||||
|
prepare_installation "$fixture" "$version"
|
||||||
|
download_and_prepare_install "$fixture" "$version"
|
||||||
|
|
||||||
|
if run_update "$fixture" fail > "$fixture/update.log" 2>&1; then
|
||||||
|
fail 'incomplete rollback case unexpectedly succeeded'
|
||||||
|
fi
|
||||||
|
grep -Fq 'manual recovery is required' "$fixture/update.log" \
|
||||||
|
|| fail 'incomplete rollback case did not require explicit recovery'
|
||||||
|
[ ! -e "$fixture/state/processing/request.json" ] \
|
||||||
|
|| fail 'incomplete rollback case left an automatically retriggered processing request'
|
||||||
|
[ -d "$fixture/state/transactions/active" ] \
|
||||||
|
|| fail 'incomplete rollback case did not preserve transaction evidence'
|
||||||
|
find "$fixture/state/failed" -type f -name 'request-*.json' -print -quit | grep -q . \
|
||||||
|
|| fail 'incomplete rollback case did not archive its claimed request'
|
||||||
|
}
|
||||||
|
|
||||||
assert_download_failure_case() {
|
assert_download_failure_case() {
|
||||||
local fixture="$WORK/download-failure"
|
local fixture="$WORK/download-failure"
|
||||||
local version='1.0.0-preview.2'
|
local version='1.0.0-preview.2'
|
||||||
@@ -521,6 +553,7 @@ assert_success_case
|
|||||||
assert_identical_systemd_operations_case
|
assert_identical_systemd_operations_case
|
||||||
assert_update_path_failure_keeps_application_case
|
assert_update_path_failure_keeps_application_case
|
||||||
assert_rollback_case
|
assert_rollback_case
|
||||||
|
assert_incomplete_rollback_requires_manual_recovery_case
|
||||||
assert_download_failure_case
|
assert_download_failure_case
|
||||||
assert_database_failure_case
|
assert_database_failure_case
|
||||||
assert_symlink_request_rejected
|
assert_symlink_request_rejected
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ printf '%s\n' \
|
|||||||
bootstrap-checksums.txt \
|
bootstrap-checksums.txt \
|
||||||
frontend-sbom.cdx.json \
|
frontend-sbom.cdx.json \
|
||||||
install.sh \
|
install.sh \
|
||||||
|
purge.sh \
|
||||||
"$ARTIFACT" \
|
"$ARTIFACT" \
|
||||||
release-manifest.json \
|
release-manifest.json \
|
||||||
release-manifest.sig \
|
release-manifest.sig \
|
||||||
@@ -49,7 +50,7 @@ for path in ./* ./.[!.]* ./..?*; do
|
|||||||
basename "$path"
|
basename "$path"
|
||||||
done | LC_ALL=C sort > "$WORK/actual-assets.txt"
|
done | LC_ALL=C sort > "$WORK/actual-assets.txt"
|
||||||
diff -u "$WORK/expected-assets.txt" "$WORK/actual-assets.txt" \
|
diff -u "$WORK/expected-assets.txt" "$WORK/actual-assets.txt" \
|
||||||
|| { printf 'Release directory must contain exactly the nine published assets\n' >&2; exit 1; }
|
|| { printf 'Release directory must contain exactly the ten published assets\n' >&2; exit 1; }
|
||||||
EXPECTED_ARTIFACT_SHA256=$(jq -er \
|
EXPECTED_ARTIFACT_SHA256=$(jq -er \
|
||||||
'.sha256 | strings | ascii_downcase | select(test("^[0-9a-f]{64}$"))' \
|
'.sha256 | strings | ascii_downcase | select(test("^[0-9a-f]{64}$"))' \
|
||||||
release-manifest.json)
|
release-manifest.json)
|
||||||
@@ -109,16 +110,21 @@ done
|
|||||||
|
|
||||||
PUBLIC_KEY_SHA256=$(sha256_file release-public.pem)
|
PUBLIC_KEY_SHA256=$(sha256_file release-public.pem)
|
||||||
INSTALLER_SHA256=$(sha256_file install.sh)
|
INSTALLER_SHA256=$(sha256_file install.sh)
|
||||||
|
PURGER_SHA256=$(sha256_file purge.sh)
|
||||||
BOOTSTRAP_SHA256=$(sha256_file bootstrap-checksums.txt)
|
BOOTSTRAP_SHA256=$(sha256_file bootstrap-checksums.txt)
|
||||||
SIGNED_INSTALLER=$(jq -er '.bootstrap.installer' release-manifest.json)
|
SIGNED_INSTALLER=$(jq -er '.bootstrap.installer' release-manifest.json)
|
||||||
|
SIGNED_PURGER=$(jq -er '.bootstrap.purger' release-manifest.json)
|
||||||
SIGNED_PUBLIC_KEY=$(jq -er '.bootstrap.publicKey' release-manifest.json)
|
SIGNED_PUBLIC_KEY=$(jq -er '.bootstrap.publicKey' release-manifest.json)
|
||||||
SIGNED_BOOTSTRAP=$(jq -er '.bootstrap.checksums' release-manifest.json)
|
SIGNED_BOOTSTRAP=$(jq -er '.bootstrap.checksums' release-manifest.json)
|
||||||
[ "$SIGNED_INSTALLER" = install.sh ] && [ "$SIGNED_PUBLIC_KEY" = release-public.pem ] \
|
[ "$SIGNED_INSTALLER" = install.sh ] && [ "$SIGNED_PURGER" = purge.sh ] \
|
||||||
|
&& [ "$SIGNED_PUBLIC_KEY" = release-public.pem ] \
|
||||||
&& [ "$SIGNED_BOOTSTRAP" = bootstrap-checksums.txt ] \
|
&& [ "$SIGNED_BOOTSTRAP" = bootstrap-checksums.txt ] \
|
||||||
|| { printf 'Release manifest bootstrap asset names are invalid\n' >&2; exit 1; }
|
|| { printf 'Release manifest bootstrap asset names are invalid\n' >&2; exit 1; }
|
||||||
jq -e --arg installer "$INSTALLER_SHA256" --arg publicKey "$PUBLIC_KEY_SHA256" \
|
jq -e --arg installer "$INSTALLER_SHA256" --arg purger "$PURGER_SHA256" \
|
||||||
|
--arg publicKey "$PUBLIC_KEY_SHA256" \
|
||||||
--arg bootstrap "$BOOTSTRAP_SHA256" \
|
--arg bootstrap "$BOOTSTRAP_SHA256" \
|
||||||
'.bootstrap.installerSha256 == $installer
|
'.bootstrap.installerSha256 == $installer
|
||||||
|
and .bootstrap.purgerSha256 == $purger
|
||||||
and .bootstrap.publicKeySha256 == $publicKey
|
and .bootstrap.publicKeySha256 == $publicKey
|
||||||
and .bootstrap.checksumsSha256 == $bootstrap' release-manifest.json >/dev/null \
|
and .bootstrap.checksumsSha256 == $bootstrap' release-manifest.json >/dev/null \
|
||||||
|| { printf 'Bootstrap assets do not match the signed manifest\n' >&2; exit 1; }
|
|| { printf 'Bootstrap assets do not match the signed manifest\n' >&2; exit 1; }
|
||||||
@@ -131,6 +137,8 @@ NORMALIZED_TRUSTED_PUBLIC_KEY_SHA256=$(printf '%s' "$TRUSTED_PUBLIC_KEY_SHA256"
|
|||||||
|| { printf 'Bootstrap public-key fingerprint does not match\n' >&2; exit 1; }
|
|| { printf 'Bootstrap public-key fingerprint does not match\n' >&2; exit 1; }
|
||||||
[ "$(sed -n 's/^KAIDI_INSTALLER_SHA256=//p' bootstrap-checksums.txt)" = "$INSTALLER_SHA256" ] \
|
[ "$(sed -n 's/^KAIDI_INSTALLER_SHA256=//p' bootstrap-checksums.txt)" = "$INSTALLER_SHA256" ] \
|
||||||
|| { printf 'Bootstrap installer digest does not match\n' >&2; exit 1; }
|
|| { printf 'Bootstrap installer digest does not match\n' >&2; exit 1; }
|
||||||
|
[ "$(sed -n 's/^KAIDI_PURGER_SHA256=//p' bootstrap-checksums.txt)" = "$PURGER_SHA256" ] \
|
||||||
|
|| { printf 'Bootstrap purger digest does not match\n' >&2; exit 1; }
|
||||||
|
|
||||||
ARCHIVE_LIST=$(tar -tzf "$ARTIFACT")
|
ARCHIVE_LIST=$(tar -tzf "$ARTIFACT")
|
||||||
if grep -Eq '(^/|(^|/)\.\.(/|$))' <<< "$ARCHIVE_LIST"; then
|
if grep -Eq '(^/|(^|/)\.\.(/|$))' <<< "$ARCHIVE_LIST"; then
|
||||||
@@ -160,11 +168,19 @@ compare_archive_file() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compare_archive_file ./ops/update.sh deploy/update.sh
|
compare_archive_file ./ops/update.sh deploy/update.sh
|
||||||
|
compare_archive_file ./ops/baota-start.sh deploy/baota-start.sh
|
||||||
|
compare_archive_file ./ops/baota.env.example deploy/baota.env.example
|
||||||
compare_archive_file ./ops/kaidi-update.path deploy/systemd/kaidi-update.path
|
compare_archive_file ./ops/kaidi-update.path deploy/systemd/kaidi-update.path
|
||||||
compare_archive_file ./ops/kaidi-update.service deploy/systemd/kaidi-update.service
|
compare_archive_file ./ops/kaidi-update.service deploy/systemd/kaidi-update.service
|
||||||
compare_archive_file ./ops/kaidi-finance.service deploy/systemd/kaidi-finance.service
|
compare_archive_file ./ops/kaidi-finance.service deploy/systemd/kaidi-finance.service
|
||||||
compare_archive_file ./ops/kaidi-finance.conf deploy/nginx/kaidi-finance.conf
|
compare_archive_file ./ops/kaidi-finance.conf deploy/nginx/kaidi-finance.conf
|
||||||
|
tar -tvzf "$ARTIFACT" ./ops/baota-start.sh | grep -Eq '^-.{2}x.{2}x.{2}x' \
|
||||||
|
|| { printf 'Baota Spring Boot launcher is not executable\n' >&2; exit 1; }
|
||||||
|
bash -n "$ROOT/deploy/baota-start.sh"
|
||||||
cmp install.sh "$ROOT/deploy/install.sh" \
|
cmp install.sh "$ROOT/deploy/install.sh" \
|
||||||
|| { printf 'Release installer does not match deploy/install.sh\n' >&2; exit 1; }
|
|| { printf 'Release installer does not match deploy/install.sh\n' >&2; exit 1; }
|
||||||
|
bash -n "$ROOT/deploy/purge.sh"
|
||||||
|
cmp purge.sh "$ROOT/deploy/purge.sh" \
|
||||||
|
|| { printf 'Release purger does not match deploy/purge.sh\n' >&2; exit 1; }
|
||||||
|
|
||||||
printf 'Release %s verified; installer SHA-256: %s\n' "$VERSION" "$INSTALLER_SHA256"
|
printf 'Release %s verified; installer SHA-256: %s\n' "$VERSION" "$INSTALLER_SHA256"
|
||||||
|
|||||||
Reference in New Issue
Block a user