feat: simplify public preview installation
Release / release (push) Failing after 12s

This commit is contained in:
Qiufeng
2026-08-17 15:27:32 +08:00
parent c8b0151769
commit aed561c702
7 changed files with 90 additions and 182 deletions
+40 -73
View File
@@ -43,12 +43,11 @@ npm run build
## R1 Preview 一键安装 ## R1 Preview 一键安装
Release 发布后,在 Linux 服务器执行下面一组命令即可安装。代码仓库和更新源固定为私有 Gitea 代码仓库和 Release 已公开,Linux 服务器不需要 Gitea Token。安装器读取
`https://git.awaioi.com/ERP-Team/kaidi`;服务器读取 `https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest`,生产机不执行 `git pull` 或现场编译。
`https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest`,不在生产机执行 `git pull` 或现场编译。 安装脚本内置固定的发布公钥指纹,下载的应用包、清单、SBOM 和更新脚本仍须通过 RSA 签名与 SHA-256 校验。
先从受信任的 CI 输出或内部发布记录取得 `RELEASE_TAG`、`INSTALLER_SHA256` 和 `PUBLIC_KEY_SHA256`。 本版只支持由运维人员预先准备的外部 MySQL 8.4.x;安装器不会安装 MySQL、创建数据库容器或修改现有
后两项也会写入 Release 的 `bootstrap-checksums.txt`,但首次安装必须通过独立渠道核对,不能把同源下载值 PostgreSQL 18。PostgreSQL 兼容开发已冻结,不属于本次 Preview.9 发布范围。
直接当作信任根。私有仓库 Token 只授予仓库/Release 读取权限,不得复用 CI 的发布写 Token。
执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java、 执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java、
Nginx 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、 Nginx 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、
@@ -58,60 +57,38 @@ Nginx 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaid
### 直接 curl 安装 ### 直接 curl 安装
```bash ```bash
( curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh | sudo bash
set -Eeuo pipefail ```
RELEASE_TAG=v1.0.0-preview.8
INSTALLER_SHA256=33d4921bcad7ef2be12f20bc0512a5f6df4227ea6dd3b7e6225164077d9cd9e7 这条命令会安装最新签名 Release,并默认进入 `/setup` 安装向导。需要在执行前独立校验安装脚本时使用:
PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
REINSTALL=false ```bash
SETUP_WIZARD=true curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh -o /tmp/kaidi-install.sh
read -rsp 'Gitea 只读 Token: ' GITEA_READ_TOKEN; printf '\n' printf '%s %s\n' faf52cc902abbc2bd48571caee3f4207de50ce339b82ce88fe23f9c4ce8f89ef /tmp/kaidi-install.sh | sha256sum -c -
token_file=$(mktemp); header_file=$(mktemp); installer=$(mktemp) sudo bash /tmp/kaidi-install.sh
trap 'rm -f "$token_file" "$header_file" "$installer"' EXIT rm -f /tmp/kaidi-install.sh
printf '%s' "$GITEA_READ_TOKEN" > "$token_file"
printf 'Authorization: token %s\n' "$GITEA_READ_TOKEN" > "$header_file"
unset GITEA_READ_TOKEN
chmod 0600 "$token_file" "$header_file" "$installer"
curl --fail --silent --show-error --proto '=https' --tlsv1.2 \
--header "@$header_file" \
"https://git.awaioi.com/ERP-Team/kaidi/releases/download/$RELEASE_TAG/install.sh" \
-o "$installer"
printf '%s %s\n' "$INSTALLER_SHA256" "$installer" | sha256sum -c -
sudo env \
KAIDI_RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest \
KAIDI_RELEASE_TOKEN_FILE="$token_file" \
KAIDI_RELEASE_PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
KAIDI_REINSTALL="$REINSTALL" \
KAIDI_SETUP_WIZARD="$SETUP_WIZARD" \
bash "$installer"
)
``` ```
### 从固定 Git tag 拉取后安装 ### 从固定 Git tag 拉取后安装
需要保留源码快照时,可以拉取与 Release 对应的固定 tag,再运行仓库内包装器。私有仓库会在 `git clone` 需要保留源码快照时,可以拉取与 Release 对应的固定 tag,再运行仓库内包装器。公共仓库不要求凭据,
时要求 Gitea 凭据,包装器随后单独要求只读 Release Token;它会在 `sudo` 前校验 `deploy/install.sh` 包装器会在 `sudo` 前校验 `deploy/install.sh` 的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。
的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。
```bash ```bash
git clone --branch v1.0.0-preview.8 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview git clone --branch v1.0.0-preview.9 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview
cd kaidi-preview cd kaidi-preview
KAIDI_SETUP_WIZARD=true ./deploy/install-from-git.sh ./deploy/install-from-git.sh
``` ```
首次安装使用向导时不要传 `KAIDI_DB_URL`、`KAIDI_DB_USERNAME` 或 `KAIDI_DB_PASSWORD`;这些值在浏览器中填写。 首次安装使用向导时不要传 `KAIDI_DB_URL`、`KAIDI_DB_USERNAME` 或 `KAIDI_DB_PASSWORD`;这些值在浏览器中填写。
只有已完成安装的修复性重装才从运行时配置读取数据库值,详见下文。 只有已完成安装的修复性重装才从运行时配置读取数据库值,详见下文。
```bash
KAIDI_DB_URL='jdbc:mysql://MYSQL_HOST:3306/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC' \
KAIDI_DB_USERNAME=kaidi KAIDI_DB_PASSWORD='DB_PASSWORD' ./deploy/install-from-git.sh
```
安装器会完成以下动作: 安装器会完成以下动作:
- 仅在 Linux + systemd 环境执行;首版 32 位支持基线为带 systemd 的 Debian/Ubuntu x86 32 位 Linux。 - 仅在 Linux + systemd 环境执行;首版 32 位支持基线为带 systemd 的 Debian/Ubuntu x86 32 位 Linux。
- 不安装或创建数据库;在 `/setup` 中连接运维人员预先准备的外部 MySQL 8.4.x。
- 识别 `x86_64`、`aarch64`、`armv7` 或 32 位 `i386/i486/i586/i686`,校验 SHA-256 后安装对应的 Azul Java 17 JRE。 - 识别 `x86_64`、`aarch64`、`armv7` 或 32 位 `i386/i486/i586/i686`,校验 SHA-256 后安装对应的 Azul Java 17 JRE。
- 使用安装命令固定的 SHA-256 指纹校验 Release 公钥,再用该公钥验证发布清单 RSA 签名。 - 使用安装器内置的 SHA-256 指纹校验 Release 公钥,再用该公钥验证发布清单 RSA 签名。
- 首次安装默认启用 `/setup` 向导,不在命令行保存数据库密码;向导只接受 MySQL 8.4.x,并在提交前验证 DDL/DML 权限。 - 首次安装默认启用 `/setup` 向导,不在命令行保存数据库密码;向导只接受 MySQL 8.4.x,并在提交前验证 DDL/DML 权限。
- 安装签名 Release 到 `/opt/kaidi/releases/<version>`,以 `/opt/kaidi/current` 原子切换当前版本。 - 安装签名 Release 到 `/opt/kaidi/releases/<version>`,以 `/opt/kaidi/current` 原子切换当前版本。
- 安装 Nginx、`kaidi-finance.service`、更新监听服务和健康检查。 - 安装 Nginx、`kaidi-finance.service`、更新监听服务和健康检查。
@@ -137,21 +114,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.9/install.sh | sudo bash
set -Eeuo pipefail
RELEASE_TAG=v1.0.0-preview.8; INSTALLER_SHA256=33d4921bcad7ef2be12f20bc0512a5f6df4227ea6dd3b7e6225164077d9cd9e7; PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
read -rsp 'Gitea 只读 Token: ' GITEA_READ_TOKEN; printf '\n'
token_file=$(mktemp); header_file=$(mktemp); installer=$(mktemp)
trap 'rm -f "$token_file" "$header_file" "$installer"' EXIT
printf '%s' "$GITEA_READ_TOKEN" > "$token_file"
printf 'Authorization: token %s\n' "$GITEA_READ_TOKEN" > "$header_file"
unset GITEA_READ_TOKEN; chmod 0600 "$token_file" "$header_file" "$installer"
curl --fail --silent --show-error --proto '=https' --tlsv1.2 --header "@$header_file" \
"https://git.awaioi.com/ERP-Team/kaidi/releases/download/$RELEASE_TAG/install.sh" -o "$installer"
printf '%s %s\n' "$INSTALLER_SHA256" "$installer" | sha256sum -c -
sudo env KAIDI_RELEASE_TOKEN_FILE="$token_file" KAIDI_RELEASE_PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
KAIDI_SETUP_WIZARD=true bash "$installer"
)
``` ```
32 位主机不能运行安装器自动创建的 MySQL 容器,因此在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的 32 位主机不能运行安装器自动创建的 MySQL 容器,因此在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的
@@ -171,14 +134,19 @@ GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP';
管理员数据和运维人员新增的环境变量: 管理员数据和运维人员新增的环境变量:
```bash ```bash
# 使用上方同一安装命令,把 REINSTALL=false 改成 REINSTALL=true。 curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh \
| sudo env KAIDI_REINSTALL=true KAIDI_SETUP_WIZARD=false bash
``` ```
重装失败会恢复原应用链接、Java 运行时、环境文件、systemd 单元和 Nginx 配置;脚本会明确报告回滚不完整, 重装失败会恢复原应用链接、Java 运行时、环境文件、systemd 单元和 Nginx 配置;脚本会明确报告回滚不完整,
不会把恢复失败吞掉。 不会把恢复失败吞掉。
如果安装器已完成但向导尚未提交,可使用同一命令同时设置 `REINSTALL=true` 和 `KAIDI_SETUP_WIZARD=true` 重新生成一次性安装码; 如果安装器已完成但向导尚未提交,可执行下面的命令重新生成一次性安装码;该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
```bash
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.9/install.sh \
| sudo env KAIDI_REINSTALL=true bash
```
### 停用并移除程序 ### 停用并移除程序
@@ -205,8 +173,8 @@ base64 < release-signing-private.pem | tr -d '\n'
``` ```
将私钥的 Base64 内容保存为 Gitea Actions Secret `RELEASE_SIGNING_KEY_B64`,并将命令输出的公钥 SHA-256 将私钥的 Base64 内容保存为 Gitea Actions Secret `RELEASE_SIGNING_KEY_B64`,并将命令输出的公钥 SHA-256
保存为 Gitea Actions Variable `KAIDI_RELEASE_PUBLIC_KEY_SHA256`。Gitea 内建 `GITEA_TOKEN` 只用于该工作流创建 保存为 Gitea Actions Variable `KAIDI_RELEASE_PUBLIC_KEY_SHA256`。Gitea 发布 Token 只用于该工作流创建
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 的 工作流先建立不可见草稿,再显式上传并核对 9 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的
@@ -214,13 +182,13 @@ act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前
Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布: Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布:
```bash ```bash
git tag v1.0.0-preview.8 git tag v1.0.0-preview.9
git push origin v1.0.0-preview.8 git push origin v1.0.0-preview.9
``` ```
在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。 在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。
更新源由 root 在 `/etc/kaidi/update.env` 固定为私有 Gitea Latest Release API;页面和普通 API 都看不到 Token, 更新源由 root 在 `/etc/kaidi/update.env` 固定为公共 Gitea Latest Release API;页面和普通 API 不能提交
也不能提交 URL、脚本或命令。更新流程固定为: URL、Token、脚本或命令。更新流程固定为:
1. 点击“获取更新”,后端先读取 Release 元数据,再自动排队 `DOWNLOAD`;root 更新器从 Gitea 下载 manifest、签名和应用包,执行 RSA、 1. 点击“获取更新”,后端先读取 Release 元数据,再自动排队 `DOWNLOAD`;root 更新器从 Gitea 下载 manifest、签名和应用包,执行 RSA、
SHA-256、版本、文件名和压缩包路径校验后缓存到 `/var/lib/kaidi-update/cache/<version>`。业务服务不停机。 SHA-256、版本、文件名和压缩包路径校验后缓存到 `/var/lib/kaidi-update/cache/<version>`。业务服务不停机。
@@ -235,10 +203,9 @@ git push origin v1.0.0-preview.8
忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新, 忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新,
不会延迟服务端切换。systemd 在 300 秒内连续失败 3 次后停止自动重试,避免失败任务空跑。 不会延迟服务端切换。systemd 在 300 秒内连续失败 3 次后停止自动重试,避免失败任务空跑。
安装器会把 API 地址和只读 Token 同步写入 root-only 的 `/etc/kaidi/kaidi.env` 与 安装器会把公共 API 地址写入 root-only 的 `/etc/kaidi/kaidi.env` 与 `/etc/kaidi/update.env`,两者权限均为
`/etc/kaidi/update.env`,两者权限均为 `0600`:前者供 Java 后端“检查更新”读取,后者供 root 更新器下载资产。 `0600`:前者供 Java 后端“检查更新”读取,后者供 root 更新器下载资产。默认 Token 为空;如改接私有镜像,
轮换 Token 时必须同时更新两个文件,再执行 `sudo systemctl restart kaidi-finance`;Token 不写入页面、状态 JSON、 可由运维人员在这两个文件中配置独立只读 Token,Token 不写入页面、状态 JSON、审计参数或更新日志。
审计参数或更新日志。
数据库迁移必须保持至少一个版本的向后兼容。查看状态和日志: 数据库迁移必须保持至少一个版本的向后兼容。查看状态和日志:
@@ -267,7 +234,7 @@ 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.8 ./scripts/package-release.sh 1.0.0-preview.9
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
``` ```
+2 -2
View File
@@ -14,10 +14,10 @@ FILE_SCANNER_ENABLED=false
FINANCE_BOOTSTRAP_ENABLED=false FINANCE_BOOTSTRAP_ENABLED=false
FINANCE_BOOTSTRAP_PASSWORD= FINANCE_BOOTSTRAP_PASSWORD=
APP_VERSION=1.0.0-preview.8 APP_VERSION=1.0.0-preview.9
UPDATE_CURRENT_VERSION_FILE=/opt/kaidi/current/VERSION UPDATE_CURRENT_VERSION_FILE=/opt/kaidi/current/VERSION
FINANCE_UPDATE_ENABLED=true FINANCE_UPDATE_ENABLED=true
# Use either a stable direct asset base URL or the private Gitea latest-release API. # Use either a stable direct asset base URL or the public Gitea latest-release API.
UPDATE_RELEASE_BASE_URL= UPDATE_RELEASE_BASE_URL=
UPDATE_RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest UPDATE_RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest
UPDATE_RELEASE_TOKEN=READ_ONLY_GITEA_TOKEN UPDATE_RELEASE_TOKEN=READ_ONLY_GITEA_TOKEN
+9 -27
View File
@@ -5,20 +5,14 @@ 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:-33d4921bcad7ef2be12f20bc0512a5f6df4227ea6dd3b7e6225164077d9cd9e7} INSTALLER_SHA256=${KAIDI_INSTALLER_SHA256:-faf52cc902abbc2bd48571caee3f4207de50ce339b82ce88fe23f9c4ce8f89ef}
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:-}
TEMP_TOKEN_FILE=
log() { printf '[kaidi-git-install] %s\n' "$*"; } log() { printf '[kaidi-git-install] %s\n' "$*"; }
die() { printf '[kaidi-git-install] ERROR: %s\n' "$*" >&2; exit 1; } die() { printf '[kaidi-git-install] ERROR: %s\n' "$*" >&2; exit 1; }
cleanup() {
[ -z "$TEMP_TOKEN_FILE" ] || rm -f "$TEMP_TOKEN_FILE"
}
trap cleanup EXIT
[ -f "$INSTALLER" ] || die "deploy/install.sh is missing; run this command from the Kaidi Git checkout" [ -f "$INSTALLER" ] || die "deploy/install.sh is missing; run this command from the Kaidi Git checkout"
command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required" command -v sha256sum >/dev/null 2>&1 || die "sha256sum is required"
[[ "$INSTALLER_SHA256" =~ ^[0-9A-Fa-f]{64}$ ]] || die "The installer SHA-256 is invalid" [[ "$INSTALLER_SHA256" =~ ^[0-9A-Fa-f]{64}$ ]] || die "The installer SHA-256 is invalid"
@@ -29,25 +23,6 @@ normalized_installer_sha256=$(printf '%s' "$INSTALLER_SHA256" | tr '[:upper:]' '
[ "$normalized_actual_installer_sha256" = "$normalized_installer_sha256" ] \ [ "$normalized_actual_installer_sha256" = "$normalized_installer_sha256" ] \
|| die "deploy/install.sh does not match the trusted SHA-256 for this Git tag" || die "deploy/install.sh does not match the trusted SHA-256 for this Git tag"
if [ -z "$TOKEN_FILE" ]; then
TOKEN_FILE=$(mktemp)
TEMP_TOKEN_FILE=$TOKEN_FILE
chmod 0600 "$TOKEN_FILE"
if [ -t 0 ]; then
read -rsp 'Gitea read-only token: ' token
printf '\n'
printf '%s' "$token" > "$TOKEN_FILE"
unset token
else
die "Set KAIDI_RELEASE_TOKEN_FILE or run interactively to enter the Gitea read-only token"
fi
fi
[ -f "$TOKEN_FILE" ] && [ ! -L "$TOKEN_FILE" ] || die "The Gitea token file must be a regular file"
token_size=$(wc -c < "$TOKEN_FILE" | tr -d '[:space:]')
[ "$token_size" -gt 0 ] && [ "$token_size" -le 512 ] || die "The Gitea token file must contain 1 to 512 bytes"
LC_ALL=C grep -q '[[:cntrl:]]' "$TOKEN_FILE" && die "The Gitea token file contains invalid control characters"
root_command=(env) root_command=(env)
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
command -v sudo >/dev/null 2>&1 || die "sudo is required when not running as root" command -v sudo >/dev/null 2>&1 || die "sudo is required when not running as root"
@@ -56,10 +31,17 @@ fi
install_env=( install_env=(
"KAIDI_RELEASE_API_URL=$RELEASE_API_URL" "KAIDI_RELEASE_API_URL=$RELEASE_API_URL"
"KAIDI_RELEASE_TOKEN_FILE=$TOKEN_FILE"
"KAIDI_RELEASE_PUBLIC_KEY_SHA256=$PUBLIC_KEY_SHA256" "KAIDI_RELEASE_PUBLIC_KEY_SHA256=$PUBLIC_KEY_SHA256"
) )
if [ -n "$TOKEN_FILE" ]; then
[ -f "$TOKEN_FILE" ] && [ ! -L "$TOKEN_FILE" ] || die "The Gitea token file must be a regular file"
token_size=$(wc -c < "$TOKEN_FILE" | tr -d '[:space:]')
[ "$token_size" -gt 0 ] && [ "$token_size" -le 512 ] || die "The Gitea token file must contain 1 to 512 bytes"
LC_ALL=C grep -q '[[:cntrl:]]' "$TOKEN_FILE" && die "The Gitea token file contains invalid control characters"
install_env+=("KAIDI_RELEASE_TOKEN_FILE=$TOKEN_FILE")
fi
KAIDI_REINSTALL=${KAIDI_REINSTALL:-} KAIDI_REINSTALL=${KAIDI_REINSTALL:-}
KAIDI_SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-} KAIDI_SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-}
KAIDI_DB_URL=${KAIDI_DB_URL:-} KAIDI_DB_URL=${KAIDI_DB_URL:-}
+4 -67
View File
@@ -12,9 +12,9 @@ RELEASE_API_URL=${KAIDI_RELEASE_API_URL:-https://git.awaioi.com/api/v1/repos/ERP
RELEASE_TOKEN=${KAIDI_RELEASE_TOKEN:-} RELEASE_TOKEN=${KAIDI_RELEASE_TOKEN:-}
RELEASE_TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-} RELEASE_TOKEN_FILE=${KAIDI_RELEASE_TOKEN_FILE:-}
HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health} HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health}
TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-} TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}
REINSTALL=${KAIDI_REINSTALL:-false} REINSTALL=${KAIDI_REINSTALL:-false}
SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-false} SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-true}
WORK_DIR= WORK_DIR=
RELEASE_AUTH_HEADER_FILE= RELEASE_AUTH_HEADER_FILE=
INSTALL_TRANSACTION_ARMED=false INSTALL_TRANSACTION_ARMED=false
@@ -24,8 +24,6 @@ BACKUP_DIR=
JAVA_STAGED_DIR= JAVA_STAGED_DIR=
JAVA_PREVIOUS_DIR= JAVA_PREVIOUS_DIR=
JAVA_ACTIVATED=false JAVA_ACTIVATED=false
CREATED_DB_CONTAINER=false
CREATED_DB_VOLUME=false
log() { printf '[kaidi-install] %s\n' "$*"; } log() { printf '[kaidi-install] %s\n' "$*"; }
die() { printf '[kaidi-install] ERROR: %s\n' "$*" >&2; exit 1; } die() { printf '[kaidi-install] ERROR: %s\n' "$*" >&2; exit 1; }
@@ -160,18 +158,6 @@ install_packages() {
fi fi
} }
install_docker() {
command -v docker >/dev/null 2>&1 && return
if command -v apt-get >/dev/null 2>&1; then
apt-get install -y -qq docker.io
elif command -v dnf >/dev/null 2>&1; then
dnf install -y docker
else
yum install -y docker
fi
systemctl enable --now docker
}
azul_arch() { azul_arch() {
case "$(uname -m)" in case "$(uname -m)" in
x86_64|amd64) printf x86 ;; x86_64|amd64) printf x86 ;;
@@ -312,48 +298,7 @@ configure_database() {
fi fi
[ "$REINSTALL" != true ] \ [ "$REINSTALL" != true ] \
|| die "KAIDI_REINSTALL needs database credentials in the existing installation or command environment" || die "KAIDI_REINSTALL needs database credentials in the existing installation or command environment"
case "$(uname -m)" in die "An external MySQL 8.4 database is required; set KAIDI_DB_URL, KAIDI_DB_USERNAME, and KAIDI_DB_PASSWORD"
i386|i486|i586|i686)
die "32-bit Linux needs an external MySQL 8.4 database; set KAIDI_DB_URL, KAIDI_DB_USERNAME, and KAIDI_DB_PASSWORD"
;;
esac
install_docker
DB_USERNAME=kaidi
DB_PASSWORD=$(random_secret)
DB_ROOT_PASSWORD=$(random_secret)
! docker inspect kaidi-finance-mysql >/dev/null 2>&1 \
|| die "Docker container kaidi-finance-mysql already exists; provide explicit database parameters"
! docker volume inspect kaidi_mysql_data >/dev/null 2>&1 \
|| die "Docker volume kaidi_mysql_data already exists; provide explicit database parameters"
docker volume create kaidi_mysql_data >/dev/null
CREATED_DB_VOLUME=true
docker run -d \
--name kaidi-finance-mysql \
--restart unless-stopped \
-e MYSQL_DATABASE=kaidi_finance \
-e MYSQL_USER="$DB_USERNAME" \
-e MYSQL_PASSWORD="$DB_PASSWORD" \
-e MYSQL_ROOT_PASSWORD="$DB_ROOT_PASSWORD" \
-e TZ=Asia/Shanghai \
-p 127.0.0.1:3307:3306 \
-v kaidi_mysql_data:/var/lib/mysql \
--health-cmd='mysqladmin ping -h localhost -uroot -p$MYSQL_ROOT_PASSWORD --silent' \
--health-interval=5s --health-timeout=5s --health-retries=30 \
mysql:8.4 \
--character-set-server=utf8mb4 \
--collation-server=utf8mb4_0900_ai_ci \
--default-time-zone=+00:00 >/dev/null
CREATED_DB_CONTAINER=true
local attempts=0 health
while [ "$attempts" -lt 60 ]; do
health=$(docker inspect --format '{{.State.Health.Status}}' kaidi-finance-mysql 2>/dev/null || true)
[ "$health" = healthy ] && break
[ "$health" = unhealthy ] && die "MySQL container failed its health check"
attempts=$((attempts + 1))
sleep 2
done
[ "$health" = healthy ] || die "MySQL did not become ready"
DB_URL='jdbc:mysql://127.0.0.1:3307/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC'
} }
database_host() { database_host() {
@@ -599,14 +544,6 @@ cleanup() {
&& [ "$INSTALL_COMMITTED" != true ]; then && [ "$INSTALL_COMMITTED" != true ]; then
rollback_install || log "ERROR: rollback was incomplete; inspect systemd and Nginx state" rollback_install || log "ERROR: rollback was incomplete; inspect systemd and Nginx state"
fi fi
if [ "$result" -ne 0 ] && [ "$INSTALL_COMMITTED" != true ] \
&& [ "$CREATED_DB_CONTAINER" = true ]; then
docker rm -f kaidi-finance-mysql >/dev/null 2>&1 || true
fi
if [ "$result" -ne 0 ] && [ "$INSTALL_COMMITTED" != true ] \
&& [ "$CREATED_DB_VOLUME" = true ]; then
docker volume rm kaidi_mysql_data >/dev/null 2>&1 || true
fi
[ -z "$WORK_DIR" ] || rm -rf "$WORK_DIR" [ -z "$WORK_DIR" ] || rm -rf "$WORK_DIR"
exit "$result" exit "$result"
} }
@@ -755,7 +692,7 @@ write_env_file "$CONFIG_ROOT/update.env" \
KAIDI_UPDATE_STATE_ROOT "$UPDATE_STATE_ROOT" \ KAIDI_UPDATE_STATE_ROOT "$UPDATE_STATE_ROOT" \
KAIDI_SERVICE_NAME kaidi-finance.service \ KAIDI_SERVICE_NAME kaidi-finance.service \
KAIDI_HEALTH_URL "$HEALTH_URL" \ KAIDI_HEALTH_URL "$HEALTH_URL" \
KAIDI_DB_CONTAINER "${KAIDI_DB_CONTAINER:-kaidi-finance-mysql}" \ KAIDI_DB_CONTAINER "${KAIDI_DB_CONTAINER:-}" \
KAIDI_DB_HOST "$(database_host)" \ KAIDI_DB_HOST "$(database_host)" \
KAIDI_DB_PORT "$(database_port)" \ KAIDI_DB_PORT "$(database_port)" \
KAIDI_DB_NAME "$(database_name)" \ KAIDI_DB_NAME "$(database_name)" \
+6 -6
View File
@@ -1,10 +1,10 @@
# 财务项目基础系统开发交付总方案 # 财务项目基础系统开发交付总方案
> 文档编号:KD-FIN-DELIVERY-001<br> > 文档编号:KD-FIN-DELIVERY-001<br>
> 版本:V3.25(Preview.8 首次安装向导与部署收口)<br> > 版本:V3.26(Preview.9 公共 Release 一键安装)<br>
> 状态:第一版 Preview 候选包已具备 22 页功能框架、核心业务链和可部署制品;PAGE-13/14 已通过模块验收,PAGE-12/16/17/18/19/20/21/22 为 `IMPLEMENTED_PENDING_ACCEPTANCE`,PAGE-15 正式业务矩阵仍为 `IN_PROGRESS`;整套 R1 尚未达到第 12.9 节 Definition of Done,不得把 Preview 上线等同于甲方最终验收<br> > 状态:第一版 Preview 候选包已具备 22 页功能框架、核心业务链和可部署制品;PAGE-13/14 已通过模块验收,PAGE-12/16/17/18/19/20/21/22 为 `IMPLEMENTED_PENDING_ACCEPTANCE`,PAGE-15 正式业务矩阵仍为 `IN_PROGRESS`;整套 R1 尚未达到第 12.9 节 Definition of Done,不得把 Preview 上线等同于甲方最终验收<br>
> 编制依据:2026-08-05 腾讯会议转写、14 张 OA 表单、财务岗位职责资料、项目管理流程图<br> > 编制依据:2026-08-05 腾讯会议转写、14 张 OA 表单、财务岗位职责资料、项目管理流程图<br>
> 最新项目决策:按既定 Java/TDesign 技术栈交付第一版 Preview;2026-08-17 已完成 PAGE-08 来源、记账、档案双向穿透,PAGE-20 审计稳定排序/筛选/脱敏导出、PAGE-21 权限与配置、PAGE-22 私有 Gitea 签名在线更新、OA-04/OA-06 动态附件矩阵、OA-06 财务终审付款投影、Release/SBOM/依赖门禁、Linux i386/i486/i586/i686 外部 MySQL 8.4.x 单命令安装、更新请求持久领取/中断恢复/数据库备份校验/回滚健康复核,以及隔离超级管理员全部功能权限和全局数据范围;本轮新增 PAGE-23 首次安装向导、无业务数据库启动上下文、MySQL 8.4 DDL/DML 预检、一次性安装码和安装状态锁定,数据库基线新增 `V072`。远端仓库已固定为 `https://git.awaioi.com/ERP-Team/kaidi.git`;配置 Gitea Actions 签名 Secret、公钥指纹 Variable、可用 runner 和生产只读 Token 后,推送符合 SemVer 的 `v*` tag 即生成 Release;PAGE-15 完整业务矩阵继续保持 `DRAFT`,须经 D-02/D-09 确认后才允许作为正式规则发布<br> > 最新项目决策:按既定 Java/TDesign 技术栈交付第一版 Preview;2026-08-17 已完成 PAGE-08 来源、记账、档案双向穿透,PAGE-20 审计稳定排序/筛选/脱敏导出、PAGE-21 权限与配置、PAGE-22 公共 Gitea 签名在线更新、OA-04/OA-06 动态附件矩阵、OA-06 财务终审付款投影、Release/SBOM/依赖门禁、Linux i386/i486/i586/i686 外部 MySQL 8.4.x 单命令安装、更新请求持久领取/中断恢复/数据库备份校验/回滚健康复核,以及隔离超级管理员全部功能权限和全局数据范围;本轮新增 PAGE-23 首次安装向导、无业务数据库启动上下文、MySQL 8.4 DDL/DML 预检、一次性安装码和安装状态锁定,数据库基线新增 `V072`。远端仓库 `https://git.awaioi.com/ERP-Team/kaidi.git` 与 Release 均已公开;安装和在线更新默认不使用 Token,安装器内置发布公钥指纹,推送符合 SemVer 的 `v*` tag 后由带签名密钥的发布流程生成 Release;PAGE-15 完整业务矩阵继续保持 `DRAFT`,须经 D-02/D-09 确认后才允许作为正式规则发布<br>
> 实际开发技术:Java 17 + Spring Boot 3.5.16 + MyBatis 3.0.5 + MySQL 8.4 + TDesign Vue Next Starter;前端仍只允许在现有 `frontend/` Starter 中原位增加菜单、页面、组件和真实功能 > 实际开发技术:Java 17 + Spring Boot 3.5.16 + MyBatis 3.0.5 + MySQL 8.4 + TDesign Vue Next Starter;前端仍只允许在现有 `frontend/` Starter 中原位增加菜单、页面、组件和真实功能
> 文档性质:本项目唯一 PRD、SPEC、开发任务书、测试验收与交付基线<br> > 文档性质:本项目唯一 PRD、SPEC、开发任务书、测试验收与交付基线<br>
> 文档用途:甲方需求确认、产品设计、开发拆分、测试验收和交付培训<br> > 文档用途:甲方需求确认、产品设计、开发拆分、测试验收和交付培训<br>
@@ -72,14 +72,14 @@
| 超级管理员权限 | V071 将 `SYSTEM_ADMIN` 固定为隔离超级管理员,逐条授予全部 `iam_permission` 和无额度 `GLOBAL` scope,并由启动同步补齐后续权限;前端菜单、直链和按钮统一放行全部 PAGE-04~21。管理员可管理跨人员表单/导入/付款草稿,PAGE-15 增加仅管理员可见的“全部付款”,三类业务工作台显示跨人员项目、资金、档案和全部待办;审批实际处理人、SOD、状态机、资金、附件安全和审计继续强制执行 | | 超级管理员权限 | V071 将 `SYSTEM_ADMIN` 固定为隔离超级管理员,逐条授予全部 `iam_permission` 和无额度 `GLOBAL` scope,并由启动同步补齐后续权限;前端菜单、直链和按钮统一放行全部 PAGE-04~21。管理员可管理跨人员表单/导入/付款草稿,PAGE-15 增加仅管理员可见的“全部付款”,三类业务工作台显示跨人员项目、资金、档案和全部待办;审批实际处理人、SOD、状态机、资金、附件安全和审计继续强制执行 |
| OA-06 付款投影 | V070 发布 OA-06 v2,新增生效合同、供应商和已确认应付稳定引用;财务终审在来源审批事务内生成 `OFFLINE_PENDING` 付款,执行付款检查、冻结项目资金并追加资金流水。余额不足等投影失败时来源状态、审批任务、付款、资金控制和流水整体回滚;不产生银行付款指令,不接网银、U 盾或第三方平台 | | OA-06 付款投影 | V070 发布 OA-06 v2,新增生效合同、供应商和已确认应付稳定引用;财务终审在来源审批事务内生成 `OFFLINE_PENDING` 付款,执行付款检查、冻结项目资金并追加资金流水。余额不足等投影失败时来源状态、审批任务、付款、资金控制和流水整体回滚;不产生银行付款指令,不接网银、U 盾或第三方平台 |
| OA 动态附件矩阵 | OA-04/OA-06 的条件附件行支持 TDesign 布尔开关、长文本、不涉及原因和嵌套文件上传;仅 `required=true` 的行要求文件或原因,可选空行不阻断。上传状态按字段和表格单元隔离,上传期间锁定刷新、保存、校验和提交;更新请求不再携带创建专用字段,校验前自动保存当前草稿 | | OA 动态附件矩阵 | OA-04/OA-06 的条件附件行支持 TDesign 布尔开关、长文本、不涉及原因和嵌套文件上传;仅 `required=true` 的行要求文件或原因,可选空行不阻断。上传状态按字段和表格单元隔离,上传期间锁定刷新、保存、校验和提交;更新请求不再携带创建专用字段,校验前自动保存当前草稿 |
| 本轮运行验收 | 最近完整后端 MySQL 8.4/Testcontainers 基线 `213/213`,Failures/Errors/Skipped 均为 0,迁移基线扩展至 `V072`;本轮新增 setup context smoke、安装器/包装器夹具和 PAGE-23 Playwright 回归;最新签名候选为 `1.0.0-preview.8`,正式 Release 仍须在干净 Git tag 上由 Gitea Actions 重新生成安装器摘要 | | 本轮运行验收 | 最近完整后端 MySQL 8.4/Testcontainers 基线 `213/213`,Failures/Errors/Skipped 均为 0,迁移基线扩展至 `V072`;本轮新增 setup context smoke、安装器/包装器夹具和 PAGE-23 Playwright 回归;`1.0.0-preview.8` 已发布 9 个验签资产,Preview.9 进一步将公共仓库安装入口精简为单行 `curl | sudo bash` |
| 第一版 Preview | PAGE-01~22 页面和菜单框架齐备,真实前后端核心链保持可用;Preview 上线与整套 R1 最终验收分开计量 | | 第一版 Preview | PAGE-01~22 页面和菜单框架齐备,真实前后端核心链保持可用;Preview 上线与整套 R1 最终验收分开计量 |
| 来源表格校验 | V068 为 14 类 OA 的 33 个 TABLE 字段补齐嵌套类型、必填和文件引用规则;V069 保留 OA-02 v1 历史账户表格契约,避免 v2 字段追溯污染 | | 来源表格校验 | V068 为 14 类 OA 的 33 个 TABLE 字段补齐嵌套类型、必填和文件引用规则;V069 保留 OA-02 v1 历史账户表格契约,避免 v2 字段追溯污染 |
| PAGE-20 审计 | 增加不可变事件序号、四种稳定排序、URL 查询状态、同排序 CSV 导出、脱敏详情及 OpenAPI `422` 参数门禁 | | PAGE-20 审计 | 增加不可变事件序号、四种稳定排序、URL 查询状态、同排序 CSV 导出、脱敏详情及 OpenAPI `422` 参数门禁 |
| PAGE-22 在线更新 | 更新源固定为私有 Gitea Latest Release API;系统管理员点击“获取更新”后自动检查并下载,验签缓存进入 `READY` 后显示“立即重启”。安装健康检查成功后页面从 10 秒倒计时自动刷新;刷新或短暂断线发生在安装中时恢复轮询。后台只写固定结构请求,由 root oneshot 服务验签、验哈希、备份、切换和回滚,不接受页面传入地址、Token 或命令 | | PAGE-22 在线更新 | 更新源固定为公共 Gitea Latest Release API,默认不使用 Token;系统管理员点击“获取更新”后自动检查并下载,验签缓存进入 `READY` 后显示“立即重启”。安装健康检查成功后页面从 10 秒倒计时自动刷新;刷新或短暂断线发生在安装中时恢复轮询。后台只写固定结构请求,由 root oneshot 服务验签、验哈希、备份、切换和回滚,不接受页面传入地址、Token 或命令 |
| Release 工程 | `.gitea/workflows/release.yml` 监听严格 SemVer `v*` tag;Maven `revision`、npm、JAR、前后端 SBOM、签名 manifest 与 tag 必须同版本。工作流执行 Java/前端/OpenAPI/Playwright/依赖审计,使用至少 3072 位 RSA 密钥生成并独立验收恰好 9 个资产;先创建不可见草稿、逐项上传并核对名称/大小,最后发布为可被 `/releases/latest` 读取的正式 Release | | Release 工程 | `.gitea/workflows/release.yml` 监听严格 SemVer `v*` tag;Maven `revision`、npm、JAR、前后端 SBOM、签名 manifest 与 tag 必须同版本。工作流执行 Java/前端/OpenAPI/Playwright/依赖审计,使用至少 3072 位 RSA 密钥生成并独立验收恰好 9 个资产;先创建不可见草稿、逐项上传并核对名称/大小,最后发布为可被 `/releases/latest` 读取的正式 Release |
| Linux 32 位 | i386/i486/i586/i686 下载 Java 17 i686 JRE;JRE 元数据和归档均使用仅允许 HTTPS/TLS 1.2 及以上的受限下载器;由于 MySQL 8.4 无对应服务端镜像,要求预置外部 MySQL 8.4.x,curl 安装只开启 `/setup` 向导,数据库密码在浏览器中提交并完成连接、版本及 DDL/DML 预检 | | Linux 32 位 | i386/i486/i586/i686 下载 Java 17 i686 JRE;JRE 元数据和归档均使用仅允许 HTTPS/TLS 1.2 及以上的受限下载器;由于 MySQL 8.4 无对应服务端镜像,要求预置外部 MySQL 8.4.x,curl 安装只开启 `/setup` 向导,数据库密码在浏览器中提交并完成连接、版本及 DDL/DML 预检 |
| 更新可靠性 | 下载和安装拆为两个持久动作;下载阶段不停止业务服务,安装阶段只接受同版本 `READY` 缓存并重新验签。私有 Gitea Token 仅从权限为 `0600` 的 root 配置/临时文件读取,不进入 `curl` 参数、页面、状态或日志,且只允许同源 API/资产使用;请求原子领取到持久 `processing`,systemd 限制失败重试;`mysqldump`、新旧版本健康和回滚均有独立门禁,私有 Gitea、跨来源拒绝、成功、健康回滚、下载失败、备份失败和不安全请求夹具纳入 CI | | 更新可靠性 | 下载和安装拆为两个持久动作;下载阶段不停止业务服务,安装阶段只接受同版本 `READY` 缓存并重新验签。公共 Gitea 默认不使用 Token;如改接私有镜像,Token 仅从权限为 `0600` 的 root 配置/临时文件读取,不进入 `curl` 参数、页面、状态或日志,且只允许同源 API/资产使用;请求原子领取到持久 `processing`,systemd 限制失败重试;`mysqldump`、新旧版本健康和回滚均有独立门禁,跨来源拒绝、成功、健康回滚、下载失败、备份失败和不安全请求夹具纳入 CI |
| 阶段口径 | 把“开发前冻结”更新为“R1 开发中”;明确模块级验收与整套 R1 交付是两个层级,避免一页完成后虚报整套系统完成 | | 阶段口径 | 把“开发前冻结”更新为“R1 开发中”;明确模块级验收与整套 R1 交付是两个层级,避免一页完成后虚报整套系统完成 |
| PAGE-13 页面 | 完成合同执行汇总、筛选、服务端分页、来源下钻、CSV 导出、合同详情及变更/结算/应付操作;页面仅使用现有 TDesign Starter 和 TDesign 组件 | | PAGE-13 页面 | 完成合同执行汇总、筛选、服务端分页、来源下钻、CSV 导出、合同详情及变更/结算/应付操作;页面仅使用现有 TDesign Starter 和 TDesign 组件 |
| PAGE-13 后端 | 新增 `contractcost:payable:create`,项目经理可登记但不可确认;同人登记/确认返回 `422 SOD_VIOLATION`;应付必须关联已批准 OA-04,结算必须关联已批准 OA-11,同一来源不得重复消费 | | PAGE-13 后端 | 新增 `contractcost:payable:create`,项目经理可登记但不可确认;同人登记/确认返回 `422 SOD_VIOLATION`;应付必须关联已批准 OA-04,结算必须关联已批准 OA-11,同一来源不得重复消费 |
@@ -1006,7 +1006,7 @@ PAGE-21 只向超级管理员开放。超级管理员账号不得混授普通业
更新固定分两阶段。管理员点击“获取更新”后,前端先检查最新版本,再自动提交 `DOWNLOAD` 请求;root oneshot 从固定 Gitea Latest Release API 下载 manifest、签名和应用包,验签/验哈希后写入 root-only 版本缓存并进入 `READY`,期间当前业务版本继续运行。进入 `READY` 后页面显示“立即重启”,点击后只允许安装同版本缓存,并在切换前重新验证。健康检查成功后页面显示 10 秒倒计时并自动刷新;若页面在 `INSTALL_QUEUED/BACKING_UP/INSTALLING/RUNNING` 时刷新或短暂断线,重新进入后自动恢复 3 秒轮询。失败时保留明确状态并恢复已验证的上一版本。 更新固定分两阶段。管理员点击“获取更新”后,前端先检查最新版本,再自动提交 `DOWNLOAD` 请求;root oneshot 从固定 Gitea Latest Release API 下载 manifest、签名和应用包,验签/验哈希后写入 root-only 版本缓存并进入 `READY`,期间当前业务版本继续运行。进入 `READY` 后页面显示“立即重启”,点击后只允许安装同版本缓存,并在切换前重新验证。健康检查成功后页面显示 10 秒倒计时并自动刷新;若页面在 `INSTALL_QUEUED/BACKING_UP/INSTALLING/RUNNING` 时刷新或短暂断线,重新进入后自动恢复 3 秒轮询。失败时保留明确状态并恢复已验证的上一版本。
页面只提交目标版本和原因,不接收下载地址、Token、脚本或任意命令。Gitea 只读 Token 仅存在于权限为 `0600` 的 root 环境文件和临时 Header 文件,不进入进程参数、DTO、状态 JSON、审计参数或页面响应;API 和资产必须同 scheme、host、port,认证请求不跟随跨来源重定向。文件锁和 inbox/processing 双路径防止并发覆盖,独立 root oneshot 执行 RSA/SHA-256 校验、可验证数据库备份、持久事务、应用与运维文件原子切换、新旧版本健康检查和失败回滚。 页面只提交目标版本和原因,不接收下载地址、Token、脚本或任意命令。公共 Gitea 默认不配置 Token;如改接私有镜像,只读 Token 仅存在于权限为 `0600` 的 root 环境文件和临时 Header 文件,不进入进程参数、DTO、状态 JSON、审计参数或页面响应;API 和资产必须同 scheme、host、port,认证请求不跟随跨来源重定向。文件锁和 inbox/processing 双路径防止并发覆盖,独立 root oneshot 执行 RSA/SHA-256 校验、可验证数据库备份、持久事务、应用与运维文件原子切换、新旧版本健康检查和失败回滚。
### 8.23 页面通用要求 ### 8.23 页面通用要求
+12 -2
View File
@@ -30,7 +30,11 @@ else
[ "$KAIDI_DB_USERNAME" = 'kaidi' ] [ "$KAIDI_DB_USERNAME" = 'kaidi' ]
[ "$KAIDI_DB_PASSWORD" = 'fixture-password' ] [ "$KAIDI_DB_PASSWORD" = 'fixture-password' ]
fi fi
if [ "${EXPECT_TOKEN:-false}" = true ]; then
[ "$(cat "$KAIDI_RELEASE_TOKEN_FILE")" = 'fixture-read-token' ] [ "$(cat "$KAIDI_RELEASE_TOKEN_FILE")" = 'fixture-read-token' ]
else
[ -z "${KAIDI_RELEASE_TOKEN_FILE:-}" ]
fi
printf 'fixture-installer: PASS\n' printf 'fixture-installer: PASS\n'
SH SH
@@ -40,7 +44,6 @@ chmod 0600 "$FIXTURE/token"
installer_sha256=$(sha256sum "$FIXTURE/repo/deploy/install.sh" | awk '{print $1}') installer_sha256=$(sha256sum "$FIXTURE/repo/deploy/install.sh" | awk '{print $1}')
PATH="$FIXTURE/bin:$PATH" \ PATH="$FIXTURE/bin:$PATH" \
KAIDI_RELEASE_TOKEN_FILE="$FIXTURE/token" \
KAIDI_INSTALLER_SHA256="$installer_sha256" \ KAIDI_INSTALLER_SHA256="$installer_sha256" \
KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \ KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \
KAIDI_RELEASE_PUBLIC_KEY_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ KAIDI_RELEASE_PUBLIC_KEY_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
@@ -50,6 +53,14 @@ KAIDI_DB_PASSWORD=fixture-password \
"$FIXTURE/repo/deploy/install-from-git.sh" "$FIXTURE/repo/deploy/install-from-git.sh"
PATH="$FIXTURE/bin:$PATH" \ PATH="$FIXTURE/bin:$PATH" \
KAIDI_INSTALLER_SHA256="$installer_sha256" \
KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \
KAIDI_RELEASE_PUBLIC_KEY_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
KAIDI_SETUP_WIZARD=true \
"$FIXTURE/repo/deploy/install-from-git.sh"
PATH="$FIXTURE/bin:$PATH" \
EXPECT_TOKEN=true \
KAIDI_RELEASE_TOKEN_FILE="$FIXTURE/token" \ KAIDI_RELEASE_TOKEN_FILE="$FIXTURE/token" \
KAIDI_INSTALLER_SHA256="$installer_sha256" \ KAIDI_INSTALLER_SHA256="$installer_sha256" \
KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \ KAIDI_RELEASE_API_URL=https://git.example.test/api/v1/repos/TEAM/REPO/releases/latest \
@@ -58,7 +69,6 @@ KAIDI_SETUP_WIZARD=true \
"$FIXTURE/repo/deploy/install-from-git.sh" "$FIXTURE/repo/deploy/install-from-git.sh"
if PATH="$FIXTURE/bin:$PATH" \ if PATH="$FIXTURE/bin:$PATH" \
KAIDI_RELEASE_TOKEN_FILE="$FIXTURE/token" \
KAIDI_INSTALLER_SHA256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ KAIDI_INSTALLER_SHA256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \
"$FIXTURE/repo/deploy/install-from-git.sh" > "$FIXTURE/hash-mismatch.log" 2>&1; then "$FIXTURE/repo/deploy/install-from-git.sh" > "$FIXTURE/hash-mismatch.log" 2>&1; then
printf 'Mismatched installer SHA-256 was accepted\n' >&2 printf 'Mismatched installer SHA-256 was accepted\n' >&2
+16 -4
View File
@@ -199,7 +199,19 @@ grep -Fq 'java_sha256=$(jq -er' "$ROOT/deploy/install.sh" \
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'
grep -Fq 'https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest' "$ROOT/deploy/install.sh" \ grep -Fq 'https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest' "$ROOT/deploy/install.sh" \
|| fail 'installer default release source is not the private Gitea latest API' || fail 'installer default release source is not the public Gitea latest API'
# shellcheck disable=SC2016 # Match literal installer source.
grep -Fq 'TRUSTED_PUBLIC_KEY_SHA256=${KAIDI_RELEASE_PUBLIC_KEY_SHA256:-807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9}' "$ROOT/deploy/install.sh" \
|| fail 'installer does not pin the default release public-key fingerprint'
# shellcheck disable=SC2016 # Match literal installer source.
grep -Fq 'SETUP_WIZARD=${KAIDI_SETUP_WIZARD:-true}' "$ROOT/deploy/install.sh" \
|| fail 'installer does not enable the first-run setup wizard by default'
! grep -Fq 'docker run' "$ROOT/deploy/install.sh" \
|| fail 'installer must not create a MySQL container'
! grep -Fq 'install_docker' "$ROOT/deploy/install.sh" \
|| fail 'installer must not install Docker or MySQL automatically'
grep -Fq 'An external MySQL 8.4 database is required' "$ROOT/deploy/install.sh" \
|| fail 'installer does not require an operator-managed external MySQL database'
grep -Fq 'KAIDI_RELEASE_TOKEN_FILE' "$ROOT/deploy/install.sh" \ grep -Fq 'KAIDI_RELEASE_TOKEN_FILE' "$ROOT/deploy/install.sh" \
|| fail 'installer no longer supports a protected initial Gitea token file' || fail 'installer no longer supports a protected initial Gitea token file'
grep -Fq 'KAIDI_SETUP_WIZARD' "$ROOT/deploy/install.sh" \ grep -Fq 'KAIDI_SETUP_WIZARD' "$ROOT/deploy/install.sh" \
@@ -215,7 +227,7 @@ grep -Fq 'EnvironmentFile=-/var/lib/kaidi/setup/application.env' \
# shellcheck disable=SC2016 # Match the literal installer source. # shellcheck disable=SC2016 # Match the literal installer source.
grep -Fq 'chown root:kaidi "$UPDATE_STATE_ROOT"' "$ROOT/deploy/install.sh" \ grep -Fq 'chown root:kaidi "$UPDATE_STATE_ROOT"' "$ROOT/deploy/install.sh" \
|| fail 'update state parent is not group-accessible to the application user' || fail 'update state parent is not group-accessible to the application user'
grep -Fq 'KAIDI_SETUP_WIZARD=true' "$ROOT/README.md" \ grep -Fq 'install.sh | sudo bash' "$ROOT/README.md" \
|| fail 'README does not document the setup-wizard install path' || fail 'README does not document the public one-line setup-wizard install path'
printf 'Install configuration, private Gitea, i686, setup wizard, and MySQL 8.4 fixtures passed\n' printf 'Install configuration, public Gitea, optional private token, i686, setup wizard, and MySQL 8.4 fixtures passed\n'