Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36f1a8b00d | ||
|
|
69f738465e | ||
|
|
2712c5b8c1 | ||
|
|
5a48993d26 | ||
|
|
0b91e1fc5f | ||
|
|
95cd1daeb8 | ||
|
|
d6e97f1b4a |
@@ -10,9 +10,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
# The repository runner advertises Gitea's default ubuntu-latest label.
|
||||
# Using an unavailable custom label leaves every release permanently queued.
|
||||
runs-on: ubuntu-latest
|
||||
# The temporary release runner is isolated from stale queued jobs that use
|
||||
# the old ubuntu-24.04 label. Restore ubuntu-latest after this release when
|
||||
# the repository's normal Linux runner is online again.
|
||||
runs-on: kaidi-release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -20,16 +21,25 @@ jobs:
|
||||
|
||||
- name: Validate release tag
|
||||
id: release_meta
|
||||
env:
|
||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||
GITEA_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
GITEA_REF_NAME=${GITHUB_REF_NAME:-}
|
||||
if [ -z "$GITEA_REF_NAME" ]; then GITEA_REF_NAME=${GITHUB_REF#refs/tags/}; fi
|
||||
if [ -z "$GITEA_REF_NAME" ] || [ "$GITEA_REF_NAME" = "$GITHUB_REF" ]; then
|
||||
GITEA_REF_NAME=$(git tag --points-at HEAD | awk '/^v[0-9A-Za-z][0-9A-Za-z._+-]*$/{print; exit}')
|
||||
fi
|
||||
VERSION=${GITEA_REF_NAME#v}
|
||||
test "$GITEA_REF_NAME" = "v$VERSION"
|
||||
./scripts/check-semver.sh "$VERSION"
|
||||
GITEA_SHA=$(git rev-parse HEAD)
|
||||
test "$(git rev-parse "refs/tags/$GITEA_REF_NAME^{commit}")" = "$GITEA_SHA"
|
||||
test -z "$(git status --porcelain --untracked-files=all)"
|
||||
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT"
|
||||
test -z "$(git ls-files --others --exclude-standard)"
|
||||
git diff --quiet --ignore-space-at-eol --ignore-cr-at-eol \
|
||||
|| { printf 'Release checkout has non-whitespace tracked changes:\n' >&2; git diff --stat >&2; exit 1; }
|
||||
{
|
||||
printf 'version=%s\n' "$VERSION"
|
||||
printf 'ref=%s\n' "$GITEA_REF_NAME"
|
||||
printf 'revision=%s\n' "$GITEA_SHA"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
@@ -57,7 +67,11 @@ jobs:
|
||||
npm test
|
||||
npm run audit:dependencies
|
||||
npm run build
|
||||
npx playwright install --with-deps chromium
|
||||
if [ "${RUNNER_OS:-Linux}" = macOS ]; then
|
||||
npx playwright install chromium
|
||||
else
|
||||
npx playwright install --with-deps chromium
|
||||
fi
|
||||
npm run test:e2e
|
||||
npm run test:dist
|
||||
|
||||
@@ -85,14 +99,18 @@ jobs:
|
||||
RELEASE_SIGNING_KEY_B64: ${{ secrets.RELEASE_SIGNING_KEY_B64 }}
|
||||
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
|
||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }}
|
||||
KAIDI_RELEASE_NOTES: Kaidi Finance ${{ github.ref_name }}
|
||||
KAIDI_SOURCE_REVISION: ${{ github.sha }}
|
||||
KAIDI_SOURCE_REF: ${{ github.ref_name }}
|
||||
KAIDI_RELEASE_NOTES: Kaidi Finance ${{ steps.release_meta.outputs.ref }}
|
||||
KAIDI_SOURCE_REVISION: ${{ steps.release_meta.outputs.revision }}
|
||||
KAIDI_SOURCE_REF: ${{ steps.release_meta.outputs.ref }}
|
||||
KAIDI_SOURCE_DIRTY: 'false'
|
||||
run: |
|
||||
test -n "$RELEASE_SIGNING_KEY_B64"
|
||||
trap 'shred -u "$RUNNER_TEMP/release-key.pem" 2>/dev/null || rm -f "$RUNNER_TEMP/release-key.pem"' EXIT
|
||||
printf '%s' "$RELEASE_SIGNING_KEY_B64" | base64 --decode > "$RUNNER_TEMP/release-key.pem"
|
||||
if base64 --decode </dev/null >/dev/null 2>&1; then
|
||||
printf '%s' "$RELEASE_SIGNING_KEY_B64" | base64 --decode > "$RUNNER_TEMP/release-key.pem"
|
||||
else
|
||||
printf '%s' "$RELEASE_SIGNING_KEY_B64" | base64 -D > "$RUNNER_TEMP/release-key.pem"
|
||||
fi
|
||||
chmod 600 "$RUNNER_TEMP/release-key.pem"
|
||||
KAIDI_RELEASE_SIGNING_KEY="$RUNNER_TEMP/release-key.pem" \
|
||||
./scripts/package-release.sh "$RELEASE_VERSION"
|
||||
@@ -100,18 +118,20 @@ jobs:
|
||||
- name: Verify signed release assets
|
||||
env:
|
||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }}
|
||||
KAIDI_EXPECTED_SOURCE_REVISION: ${{ github.sha }}
|
||||
KAIDI_EXPECTED_SOURCE_REF: ${{ github.ref_name }}
|
||||
KAIDI_EXPECTED_SOURCE_REVISION: ${{ steps.release_meta.outputs.revision }}
|
||||
KAIDI_EXPECTED_SOURCE_REF: ${{ steps.release_meta.outputs.ref }}
|
||||
KAIDI_EXPECTED_SOURCE_DIRTY: 'false'
|
||||
run: ./scripts/verify-release.sh dist/release
|
||||
|
||||
- name: Publish Gitea release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_SERVER_URL: ${{ github.server_url }}
|
||||
GITEA_REPOSITORY: ${{ github.repository }}
|
||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||
GITEA_SHA: ${{ github.sha }}
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_GITEA_TOKEN }}
|
||||
# Gitea's runner leaves github.server_url/repository empty on some
|
||||
# host-mode tag events; keep the release destination explicit.
|
||||
GITEA_SERVER_URL: https://git.awaioi.com
|
||||
GITEA_REPOSITORY: ERP-Team/kaidi
|
||||
GITEA_REF_NAME: ${{ steps.release_meta.outputs.ref }}
|
||||
GITEA_SHA: ${{ steps.release_meta.outputs.revision }}
|
||||
run: ./scripts/publish-gitea-release.sh
|
||||
|
||||
- name: Upload Playwright report after failure
|
||||
|
||||
@@ -75,7 +75,7 @@ PostgreSQL 18 兼容工作继续冻结。
|
||||
先在宝塔面板停止并删除当前错误的 Java 项目,再执行:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.31/install.sh | sudo env KAIDI_APP_PORT=18080 bash
|
||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.34/install.sh | sudo env KAIDI_APP_PORT=18080 bash
|
||||
```
|
||||
|
||||
该命令只安装程序运行所需的 systemd 单元,自动创建 `kaidi` 用户并检测现有 Java 17(包括
|
||||
@@ -88,7 +88,7 @@ curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-previe
|
||||
随后执行一键清理:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.31/purge.sh \
|
||||
curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.34/purge.sh \
|
||||
-o /tmp/kaidi-purge.sh \
|
||||
&& printf '%s %s\n' 44dbf10a9540aa9235bb8aff2dec603f1febbd45072cd8c4f6c40b25a6127801 /tmp/kaidi-purge.sh \
|
||||
| sha256sum -c - \
|
||||
@@ -109,14 +109,16 @@ Spring Boot 项目。数据库、JDK、Nginx 和宝塔本身都由运维人员
|
||||
|
||||
下载地址:
|
||||
|
||||
`https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.31/kaidi-finance-1.0.0-preview.31.tar.gz`
|
||||
`https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.34/kaidi-finance-1.0.0-preview.34.tar.gz`
|
||||
|
||||
校验文件:`https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.31/SHA256SUMS`
|
||||
校验文件:`https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.34/SHA256SUMS`
|
||||
|
||||
服务器要求:Linux + systemd、Java 17(宝塔项目选择 JDK 17)、可访问外部 MySQL 8.4.x;32 位 Linux 需要宿主机
|
||||
已经提供可运行的 32 位 Java 17。程序只连接已有数据库,不安装数据库客户端或数据库服务。初始化只需要
|
||||
`bash`、`openssl`、`sha256sum` 和基础 Linux 工具;选择 systemd 在线更新前,另行准备 `curl`、`jq`、`flock`、`gzip`、`runuser`
|
||||
以及与外部 MySQL 兼容的 `mysqldump`。程序不会替你安装这些依赖。
|
||||
以及与外部 MySQL 兼容的 `mysqldump`。安装器和更新器会优先使用 PATH 中的工具,也会探测宝塔常见的
|
||||
`/www/server/mysql/bin/` 路径;如工具安装在其他位置,可分别设置 `KAIDI_MYSQL_CLIENT` 和
|
||||
`KAIDI_MYSQLDUMP_BIN`,程序不会替你安装这些依赖。
|
||||
|
||||
#### 1. 下载、校验、解压
|
||||
|
||||
@@ -124,7 +126,7 @@ Spring Boot 项目。数据库、JDK、Nginx 和宝塔本身都由运维人员
|
||||
必须直接位于 `RELEASE_ROOT`,不能再嵌套一层目录。
|
||||
|
||||
```bash
|
||||
VERSION=1.0.0-preview.31
|
||||
VERSION=1.0.0-preview.34
|
||||
APP_ROOT=/www/wwwroot/kaidi
|
||||
RELEASE_ROOT="$APP_ROOT/releases/$VERSION"
|
||||
sudo install -d -m 0755 "$RELEASE_ROOT"
|
||||
@@ -247,15 +249,16 @@ base64 < release-signing-private.pem | tr -d '\n'
|
||||
将私钥的 Base64 内容保存为 Gitea Actions Secret `RELEASE_SIGNING_KEY_B64`,并将命令输出的公钥 SHA-256
|
||||
保存为 Gitea Actions Variable `KAIDI_RELEASE_PUBLIC_KEY_SHA256`。Gitea 发布 Token 只用于该工作流创建
|
||||
Release;生产服务器从公共 Release 下载,不保存 Token。私钥不得提交到 Git。`.gitea/workflows/release.yml` 要求
|
||||
act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁。
|
||||
act_runner 提供 `ubuntu-latest` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁;如果
|
||||
Actions 页面显示 “No matching online runner”,先启动并注册该标签的 act_runner。
|
||||
|
||||
工作流先建立不可见草稿,再显式上传并核对 10 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的
|
||||
`latest` 会排除 `prerelease=true`,因此即使 tag 名含 `preview`,发布记录的 `prerelease` 也固定为 `false`;
|
||||
Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布:
|
||||
|
||||
```bash
|
||||
git tag v1.0.0-preview.31
|
||||
git push origin v1.0.0-preview.31
|
||||
git tag v1.0.0-preview.34
|
||||
git push origin v1.0.0-preview.34
|
||||
```
|
||||
|
||||
在线更新仍使用独立的 TDesign 页面:系统管理员进入“系统治理 → 系统更新”。更新源由 root 在
|
||||
@@ -307,7 +310,7 @@ cat /var/lib/kaidi-update/status.json
|
||||
```bash
|
||||
KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \
|
||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
||||
./scripts/package-release.sh 1.0.0-preview.31
|
||||
./scripts/package-release.sh 1.0.0-preview.34
|
||||
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
|
||||
./scripts/verify-release.sh dist/release
|
||||
```
|
||||
|
||||
+11
-39
@@ -9,7 +9,6 @@ CONFIG_ROOT=/etc/kaidi
|
||||
STATE_ROOT=/var/lib/kaidi
|
||||
UPDATE_STATE_ROOT=/var/lib/kaidi-update
|
||||
LOG_ROOT=/var/log/kaidi
|
||||
UPDATER_ROOT=/opt/kaidi/bin
|
||||
PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
|
||||
RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest
|
||||
INIT_ARMED=false
|
||||
@@ -29,7 +28,7 @@ rollback_initialization() {
|
||||
rm -f /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
rm -f "$CONFIG_ROOT/kaidi.env" "$CONFIG_ROOT/update.env" \
|
||||
"$CONFIG_ROOT/release-public.pem" "$UPDATER_ROOT/update.sh" \
|
||||
"$CONFIG_ROOT/release-public.pem" \
|
||||
"$UPDATE_STATE_ROOT/status.json" /root/kaidi-first-login.txt
|
||||
rm -f "$INIT_APP_ROOT/current.next"
|
||||
if [ -n "$INIT_RELEASE_ROOT" ] \
|
||||
@@ -95,7 +94,7 @@ main() {
|
||||
[ "$(uname -s)" = Linux ] || die "Baota initialization only supports Linux"
|
||||
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] \
|
||||
|| die "systemd is required for the privileged background updater"
|
||||
for command in find openssl setsid sha256sum; do
|
||||
for command in find openssl sha256sum; do
|
||||
command -v "$command" >/dev/null 2>&1 || die "$command is required"
|
||||
done
|
||||
|
||||
@@ -137,7 +136,7 @@ main() {
|
||||
|
||||
INIT_ARMED=true
|
||||
ensure_service_identity
|
||||
install -d -o root -g "$SERVICE_GROUP" -m 0750 "$app_root" "$releases_root" "$UPDATER_ROOT"
|
||||
install -d -o root -g "$SERVICE_GROUP" -m 0750 "$app_root" "$releases_root"
|
||||
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 \
|
||||
"$STATE_ROOT" "$STATE_ROOT/files" "$STATE_ROOT/tmp" "$LOG_ROOT"
|
||||
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0700 "$STATE_ROOT/setup"
|
||||
@@ -163,9 +162,11 @@ main() {
|
||||
SERVER_ADDRESS 127.0.0.1 \
|
||||
SERVER_PORT "$app_port" \
|
||||
SESSION_COOKIE_SECURE false \
|
||||
DB_URL 'jdbc:mysql://setup.invalid:3306/kaidi_finance' \
|
||||
DB_USERNAME setup_pending \
|
||||
DB_PASSWORD setup_pending \
|
||||
# The setup context has no datasource. Empty values avoid Baota treating
|
||||
# a development placeholder as a real local MySQL dependency.
|
||||
DB_URL '' \
|
||||
DB_USERNAME '' \
|
||||
DB_PASSWORD '' \
|
||||
FIELD_ENCRYPTION_KEY "$field_key" \
|
||||
FILE_STORAGE_ROOT "$STATE_ROOT/files" \
|
||||
FILE_STORAGE_TEMP "$STATE_ROOT/tmp" \
|
||||
@@ -187,39 +188,10 @@ main() {
|
||||
chown root:"$SERVICE_GROUP" "$CONFIG_ROOT/kaidi.env"
|
||||
chmod 0640 "$CONFIG_ROOT/kaidi.env"
|
||||
|
||||
# Baota owns the Java process in this mode. Do not install a second
|
||||
# systemd updater; online update is intentionally systemd-only until the
|
||||
# panel lifecycle can participate in the transaction state machine.
|
||||
install -m 0644 "$release_root/ops/release-public.pem" "$CONFIG_ROOT/release-public.pem"
|
||||
install -m 0755 "$release_root/ops/update.sh" "$UPDATER_ROOT/update.sh"
|
||||
write_env_file "$CONFIG_ROOT/update.env" \
|
||||
UPDATE_RELEASE_BASE_URL '' \
|
||||
UPDATE_RELEASE_API_URL "$RELEASE_API_URL" \
|
||||
UPDATE_RELEASE_TOKEN '' \
|
||||
UPDATE_REQUEST_FILE "$UPDATE_STATE_ROOT/inbox/request.json" \
|
||||
UPDATE_STATUS_FILE "$UPDATE_STATE_ROOT/status.json" \
|
||||
UPDATE_PUBLIC_KEY "$CONFIG_ROOT/release-public.pem" \
|
||||
KAIDI_APP_ROOT "$app_root" \
|
||||
KAIDI_UPDATE_STATE_ROOT "$UPDATE_STATE_ROOT" \
|
||||
KAIDI_PROCESS_MANAGER baota \
|
||||
KAIDI_PID_FILE "$STATE_ROOT/kaidi.pid" \
|
||||
KAIDI_RUNTIME_ENV_FILE "$STATE_ROOT/setup/application.env" \
|
||||
KAIDI_UPDATER_PATH "$UPDATER_ROOT/update.sh" \
|
||||
KAIDI_SERVICE_USER "$SERVICE_USER" \
|
||||
KAIDI_SERVICE_GROUP "$SERVICE_GROUP" \
|
||||
KAIDI_UPDATE_PATH_NAME kaidi-update.path \
|
||||
KAIDI_HEALTH_URL "http://127.0.0.1:$app_port/actuator/health" \
|
||||
KAIDI_APP_INDEX_URL "http://127.0.0.1:$app_port/" \
|
||||
KAIDI_DB_HOST setup.invalid \
|
||||
KAIDI_DB_PORT 3306 \
|
||||
KAIDI_DB_NAME kaidi_finance \
|
||||
KAIDI_DB_USERNAME setup_pending \
|
||||
KAIDI_DB_PASSWORD setup_pending
|
||||
chmod 0600 "$CONFIG_ROOT/update.env"
|
||||
|
||||
install -m 0644 "$release_root/ops/kaidi-update.service" /etc/systemd/system/kaidi-update.service
|
||||
install -m 0644 "$release_root/ops/kaidi-update.path" /etc/systemd/system/kaidi-update.path
|
||||
systemd-analyze verify /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path >/dev/null \
|
||||
|| die "The privileged update units failed validation"
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now kaidi-update.path
|
||||
|
||||
printf '{"state":"CURRENT","message":"Baota release is prepared","targetVersion":"%s","updatedAt":"%s"}\n' \
|
||||
"$version" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$UPDATE_STATE_ROOT/status.json"
|
||||
|
||||
@@ -15,7 +15,7 @@ FILE_SCANNER_ENABLED=true
|
||||
FINANCE_BOOTSTRAP_ENABLED=false
|
||||
FINANCE_BOOTSTRAP_PASSWORD=
|
||||
|
||||
APP_VERSION=1.0.0-preview.31
|
||||
APP_VERSION=1.0.0-preview.34
|
||||
UPDATE_CURRENT_VERSION_FILE=/opt/kaidi/current/VERSION
|
||||
FINANCE_UPDATE_ENABLED=true
|
||||
# Use either a stable direct asset base URL or the public Gitea latest-release API.
|
||||
|
||||
+37
-6
@@ -230,6 +230,31 @@ preflight_runtime_commands() {
|
||||
done
|
||||
}
|
||||
|
||||
mysql_client_bin() {
|
||||
local candidate
|
||||
if [ -n "${KAIDI_MYSQL_CLIENT:-}" ]; then
|
||||
case "$KAIDI_MYSQL_CLIENT" in
|
||||
*/*) [ -x "$KAIDI_MYSQL_CLIENT" ] && printf '%s\n' "$KAIDI_MYSQL_CLIENT" && return 0 ;;
|
||||
*) candidate=$(command -v "$KAIDI_MYSQL_CLIENT" 2>/dev/null || true); [ -n "$candidate" ] && printf '%s\n' "$candidate" && return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
fi
|
||||
candidate=$(command -v mysql 2>/dev/null || command -v mariadb 2>/dev/null || true)
|
||||
if [ -n "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
for candidate in \
|
||||
/www/server/mysql/bin/mysql /www/server/mysql/bin/mariadb \
|
||||
/usr/bin/mysql /usr/bin/mariadb /usr/local/mysql/bin/mysql /opt/mysql/bin/mysql; do
|
||||
if [ -x "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
azul_arch() {
|
||||
case "${HOST_ARCH:-$(normalized_host_arch)}" in
|
||||
x86_64) printf x86 ;;
|
||||
@@ -561,9 +586,11 @@ configure_database() {
|
||||
if [ "$SETUP_WIZARD" = true ]; then
|
||||
[ -z "${KAIDI_DB_URL:-}${KAIDI_DB_USERNAME:-}${KAIDI_DB_PASSWORD:-}" ] \
|
||||
|| die "Do not pass database credentials when KAIDI_SETUP_WIZARD=true; enter them in the browser wizard"
|
||||
DB_URL='jdbc:mysql://setup.invalid:3306/kaidi_finance'
|
||||
DB_USERNAME=setup_pending
|
||||
DB_PASSWORD=setup_pending
|
||||
# First-run mode does not create a datasource. Keep database fields empty
|
||||
# so hosting panels cannot mistake a placeholder for a local MySQL service.
|
||||
DB_URL=
|
||||
DB_USERNAME=
|
||||
DB_PASSWORD=
|
||||
return 0
|
||||
fi
|
||||
if [ "$REINSTALL" = true ]; then
|
||||
@@ -588,6 +615,7 @@ configure_database() {
|
||||
|
||||
database_host() {
|
||||
local endpoint host_port
|
||||
[ -n "$DB_URL" ] || { printf ''; return; }
|
||||
endpoint=${DB_URL#jdbc:mysql://}
|
||||
host_port=${endpoint%%/*}
|
||||
printf '%s' "${KAIDI_DB_HOST:-${host_port%%:*}}"
|
||||
@@ -595,6 +623,7 @@ database_host() {
|
||||
|
||||
database_port() {
|
||||
local endpoint host_port candidate
|
||||
[ -n "$DB_URL" ] || { printf ''; return; }
|
||||
endpoint=${DB_URL#jdbc:mysql://}
|
||||
host_port=${endpoint%%/*}
|
||||
candidate=${host_port##*:}
|
||||
@@ -604,6 +633,7 @@ database_port() {
|
||||
|
||||
database_name() {
|
||||
local endpoint name
|
||||
[ -n "$DB_URL" ] || { printf ''; return; }
|
||||
endpoint=${DB_URL#jdbc:mysql://}
|
||||
name=${endpoint#*/}
|
||||
name=${name%%\?*}
|
||||
@@ -617,8 +647,8 @@ preflight_database() {
|
||||
jdbc:mysql://*) ;;
|
||||
*) die "KAIDI_DB_URL must start with jdbc:mysql://" ;;
|
||||
esac
|
||||
client=$(command -v mysql || command -v mariadb || true)
|
||||
[ -n "$client" ] || die "A MySQL command-line client is required"
|
||||
client=$(mysql_client_bin || true)
|
||||
[ -n "$client" ] || die "A MySQL command-line client is required; set KAIDI_MYSQL_CLIENT or add mysql/mariadb to PATH"
|
||||
host=$(database_host)
|
||||
port=$(database_port)
|
||||
name=$(database_name)
|
||||
@@ -1000,7 +1030,8 @@ write_env_file "$CONFIG_ROOT/update.env" \
|
||||
KAIDI_DB_PORT "$(database_port)" \
|
||||
KAIDI_DB_NAME "$(database_name)" \
|
||||
KAIDI_DB_USERNAME "$DB_USERNAME" \
|
||||
KAIDI_DB_PASSWORD "$DB_PASSWORD"
|
||||
KAIDI_DB_PASSWORD "$DB_PASSWORD" \
|
||||
KAIDI_MYSQLDUMP_BIN "${KAIDI_MYSQLDUMP_BIN:-}"
|
||||
chmod 0600 "$CONFIG_ROOT/update.env"
|
||||
|
||||
install -m 0644 "$RELEASE_DIR/ops/kaidi-finance.service" /etc/systemd/system/kaidi-finance.service
|
||||
|
||||
+28
-3
@@ -575,6 +575,30 @@ application_state() {
|
||||
fi
|
||||
}
|
||||
|
||||
mysqldump_bin() {
|
||||
if [ -n "${KAIDI_MYSQLDUMP_BIN:-}" ]; then
|
||||
case "$KAIDI_MYSQLDUMP_BIN" in
|
||||
*/*) [ -x "$KAIDI_MYSQLDUMP_BIN" ] && printf '%s\n' "$KAIDI_MYSQLDUMP_BIN" && return 0 ;;
|
||||
*) candidate=$(command -v "$KAIDI_MYSQLDUMP_BIN" 2>/dev/null || true); [ -n "$candidate" ] && printf '%s\n' "$candidate" && return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
fi
|
||||
candidate=$(command -v mysqldump 2>/dev/null || true)
|
||||
if [ -n "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
for candidate in \
|
||||
/www/server/mysql/bin/mysqldump /usr/bin/mysqldump \
|
||||
/usr/local/mysql/bin/mysqldump /opt/mysql/bin/mysqldump; do
|
||||
if [ -x "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
backup_database() {
|
||||
DATABASE_BACKUP=
|
||||
[ "${KAIDI_SKIP_DB_BACKUP:-false}" = "true" ] && return
|
||||
@@ -594,11 +618,12 @@ backup_database() {
|
||||
fail "Database backup failed"
|
||||
fi
|
||||
else
|
||||
command -v mysqldump >/dev/null 2>&1 || {
|
||||
dump_bin=$(mysqldump_bin || true)
|
||||
[ -n "$dump_bin" ] || {
|
||||
rm -f "$dump_file" "$backup_tmp"
|
||||
fail "mysqldump is required before installing an update"
|
||||
fail "mysqldump is required before installing an update; set KAIDI_MYSQLDUMP_BIN or install it in a standard MySQL bin directory"
|
||||
}
|
||||
if ! MYSQL_PWD=${KAIDI_DB_PASSWORD:-} mysqldump --single-transaction --routines --triggers \
|
||||
if ! MYSQL_PWD=${KAIDI_DB_PASSWORD:-} "$dump_bin" --single-transaction --routines --triggers \
|
||||
-h "${KAIDI_DB_HOST:-127.0.0.1}" -P "${KAIDI_DB_PORT:-3306}" \
|
||||
-u "${KAIDI_DB_USERNAME:-kaidi}" "${KAIDI_DB_NAME:-kaidi_finance}" > "$dump_file"; then
|
||||
rm -f "$dump_file" "$backup_tmp"
|
||||
|
||||
+1
-1
@@ -2936,7 +2936,7 @@ PAGE-16 全类回归曾暴露测试证据文件路径硬编码与应用 `finance
|
||||
| 安装与页面恢复 | 安装阶段重新验签、备份、原子切换并执行后端直连、Nginx、更新 path unit 和静态首页健康检查;成功后页面倒计时 10 秒自动刷新,安装中刷新或短暂断线会恢复 3 秒轮询;失败自动恢复并复验上一版本 | 10 秒是页面刷新等待,不延迟服务端安装;真实 Linux systemd 主机仍须执行发布后冒烟和备份恢复演练 |
|
||||
| 凭据保护 | Token 只从权限 `0600` 的 root 配置/Token 文件读取,curl 通过临时 Header 文件使用;认证请求不跟随重定向,Token 不进入进程参数、页面、状态 JSON、审计参数或日志 | Token 轮换时必须同步更新 `/etc/kaidi/kaidi.env` 和 `/etc/kaidi/update.env` |
|
||||
| 接口与自动化 | OpenAPI 为 `206 paths / 287 schemas`;后端全量 `211/211`,Vitest `53/53`、完整 Playwright `183/183`、更新专项 Playwright `15/15` 通过;ShellCheck、Actionlint、安装、更新和 Gitea draft/恰好 9 资产/发布 fixture 通过 | 更新专项覆盖下载确认、安装弹窗、10 秒刷新、安装中刷新恢复、禁用状态和失败可见性 |
|
||||
| Gitea 发布原子性 | tag 工作流先创建 `draft=true, prerelease=false` 的 Release,显式上传并核对恰好 9 个签名资产的名称、数量和大小,全部一致后才发布;失败或资产不齐时 `/latest` 不可见 | 还需在 Gitea 配置 `RELEASE_SIGNING_KEY_B64`、`KAIDI_RELEASE_PUBLIC_KEY_SHA256` 和可用的 `ubuntu-24.04` act_runner |
|
||||
| Gitea 发布原子性 | tag 工作流先创建 `draft=true, prerelease=false` 的 Release,显式上传并核对恰好 10 个签名资产的名称、数量和大小,全部一致后才发布;失败或资产不齐时 `/latest` 不可见 | 还需在 Gitea 配置 `RELEASE_SIGNING_KEY_B64`、`KAIDI_RELEASE_PUBLIC_KEY_SHA256` 和可用的 `ubuntu-latest` act_runner |
|
||||
| Preview.2 制品 | `1.0.0-preview.2` 已完成构建及独立验签;应用包 SHA-256=`180168d4481044d3803eef81a88b301642a55cb5519e50401915e1cc131e10c7`,安装器 SHA-256=`6a881dcfd5476e795a6e181f8a8b234184523478f20312a669c6748889be1288`,公钥 SHA-256=`807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9` | 当前清单绑定 revision `47403fd337cfd4544e45d6c851984e4a40bcd8f3`、`source.ref=local`、`source.dirty=true`;正式 tag Release 必须从干净提交重新构建,摘要将以 CI 输出为准 |
|
||||
| 本地 Preview.2 | 签名包内 JAR 在 `http://127.0.0.1:18095` 返回 `UP`,前端 `http://127.0.0.1:3014` 代理真实 API;系统管理员登录后,系统配置页显示当前版本 `1.0.0-preview.2` 和完整四步更新面板,页面无横向溢出,Console warning/error 为 0 | 本地未注入生产只读 Token,因此页面显示“在线更新未配置”;这证明禁用态,不替代真实 Gitea 下载和 Linux 安装冒烟 |
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ mode_of() {
|
||||
sed -n '/^port_is_listening()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^valid_app_port()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^configure_app_port()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^configure_database()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^database_host()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^database_port()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^database_name()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^is_semver()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^write_env_file_preserving_unknown()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^normalized_host_arch()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
@@ -37,6 +41,7 @@ mode_of() {
|
||||
sed -n '/^release_asset_url()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^download_release_asset()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^install_packages()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^mysql_client_bin()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^preflight_database()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^secure_release_tree()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^restore_unit_state()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
@@ -46,6 +51,25 @@ source "$WORK/helpers.sh"
|
||||
|
||||
export SETUP_WIZARD=true
|
||||
preflight_database || fail 'setup wizard database preflight returned a failure status'
|
||||
REINSTALL=false
|
||||
# shellcheck disable=SC2034 # Consumed by the extracted configure_database helper.
|
||||
KAIDI_DB_URL='' KAIDI_DB_USERNAME='' KAIDI_DB_PASSWORD=''
|
||||
configure_database
|
||||
[ -z "$DB_URL$DB_USERNAME$DB_PASSWORD" ] \
|
||||
|| fail 'setup wizard retained a fake database configuration'
|
||||
[ -z "$(database_host)$(database_port)$(database_name)" ] \
|
||||
|| fail 'setup wizard exposed placeholder database coordinates to the updater'
|
||||
|
||||
mkdir -p "$WORK/mysql-bin"
|
||||
cat > "$WORK/mysql-bin/mysql" <<'SH'
|
||||
#!/bin/sh
|
||||
exit 0
|
||||
SH
|
||||
chmod 0755 "$WORK/mysql-bin/mysql"
|
||||
export KAIDI_MYSQL_CLIENT="$WORK/mysql-bin/mysql"
|
||||
[ "$(mysql_client_bin)" = "$WORK/mysql-bin/mysql" ] \
|
||||
|| fail 'installer did not honor the explicit MySQL client path'
|
||||
unset KAIDI_MYSQL_CLIENT
|
||||
|
||||
release_permissions="$WORK/release-permissions"
|
||||
mkdir -p "$release_permissions/public" "$release_permissions/ops"
|
||||
@@ -287,6 +311,17 @@ grep -Fq '[ "$APP_ROOT" = /opt/kaidi ]' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer rejects unsupported custom roots'
|
||||
grep -Fq '8.4.*) ;;' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer enforces MySQL 8.4.x'
|
||||
! grep -Fq 'jdbc:mysql://setup.invalid' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer still writes a fake setup database URL'
|
||||
! grep -Fq 'jdbc:mysql://setup.invalid' "$ROOT/deploy/baota-init.sh" \
|
||||
|| fail 'Baota initializer still writes a fake setup database URL'
|
||||
! grep -Fq 'KAIDI_DB_HOST setup.invalid' "$ROOT/deploy/baota-init.sh" \
|
||||
|| fail 'Baota updater still points at a fake setup database host'
|
||||
! grep -Fq 'systemctl enable --now kaidi-update.path' "$ROOT/deploy/baota-init.sh" \
|
||||
|| fail 'Baota initializer still enables the systemd updater'
|
||||
# shellcheck disable=SC2016 # Match literal initializer source.
|
||||
! grep -Fq 'install -m 0644 "$release_root/ops/kaidi-update.service"' "$ROOT/deploy/baota-init.sh" \
|
||||
|| fail 'Baota initializer still installs the systemd updater unit'
|
||||
grep -Fq '32-bit Linux deployment requires glibc' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer does not reject unsupported musl 32-bit hosts before downloading Java'
|
||||
grep -Fq '32-bit Linux deployment requires the glibc loader' "$ROOT/deploy/install.sh" \
|
||||
@@ -294,6 +329,9 @@ grep -Fq '32-bit Linux deployment requires the glibc loader' "$ROOT/deploy/insta
|
||||
grep -Fq 'preflight_runtime_commands' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer does not validate required runtime commands'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq 'KAIDI_MYSQLDUMP_BIN "${KAIDI_MYSQLDUMP_BIN:-}"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer does not preserve a custom mysqldump path for online updates'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq 'write_env_file_preserving_unknown "$CONFIG_ROOT/kaidi.env"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer uses the reinstall-safe environment writer'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
@@ -409,7 +447,7 @@ grep -Fq 'load_runtime_database_env' "$ROOT/deploy/update.sh" \
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq 'install -d -o root -g "$SERVICE_GROUP" -m 0750 "$UPDATE_STATE_ROOT"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'update state parent is not group-accessible to the application user'
|
||||
grep -Fq 'kaidi-finance-1.0.0-preview.31.tar.gz' "$ROOT/README.md" \
|
||||
grep -Fq 'kaidi-finance-1.0.0-preview.34.tar.gz' "$ROOT/README.md" \
|
||||
|| fail 'README does not document the public manual-deployment artifact'
|
||||
grep -Fq 'ops/baota-init.sh' "$ROOT/README.md" \
|
||||
|| fail 'README does not document the local Baota initialization command'
|
||||
|
||||
Reference in New Issue
Block a user