fix: stabilize external-db deployment and updates
Release / release (push) Canceled after 0s

This commit is contained in:
Qiufeng
2026-08-18 22:51:35 +08:00
parent 227c4b0129
commit 8c6c10eb85
22 changed files with 767 additions and 279 deletions
+1 -1
View File
@@ -146,7 +146,7 @@ if KAIDI_JAVA_BIN="$WORK/java" \
"$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 is missing from the protected Kaidi configuration' "$WORK/missing-db.out" \
grep -Fq '受保护的 Kaidi 配置缺少:DB_URL' "$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'
+1 -1
View File
@@ -85,6 +85,6 @@ if PATH="$FIXTURE/bin:$PATH" \
printf 'Mismatched installer SHA-256 was accepted\n' >&2
exit 1
fi
grep -q 'does not match the trusted SHA-256' "$FIXTURE/hash-mismatch.log"
grep -q '与该 Git 标签的受信摘要不一致' "$FIXTURE/hash-mismatch.log"
printf 'Git checkout installation wrapper fixture passed\n'
+19 -5
View File
@@ -28,6 +28,7 @@ mode_of() {
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 '/^validate_database_configuration()/,/^}/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"
@@ -41,13 +42,15 @@ 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"
} > "$WORK/helpers.sh"
# shellcheck disable=SC1090,SC1091
source "$WORK/helpers.sh"
# Avoid invoking the host's macOS `log` utility while exercising extracted
# installer helpers.
log() { printf '%s\n' "$*" >/dev/null; }
export SETUP_WIZARD=true
preflight_database || fail 'setup wizard database preflight returned a failure status'
@@ -66,10 +69,21 @@ cat > "$WORK/mysql-bin/mysql" <<'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
# Production-mode validation is intentionally side-effect free: it validates
# the JDBC shape but never invokes a MySQL client or emits DDL/DML.
SETUP_WIZARD=false
DB_URL='jdbc:mysql://127.0.0.1:3306/kaidi_finance?useUnicode=true'
DB_USERNAME='fixture-user'
DB_PASSWORD='fixture-password'
mysql_probe_marker="$WORK/mysql-probe-called"
cat > "$WORK/mysql-bin/mysql" <<SH
#!/bin/sh
printf 'called\n' > "$mysql_probe_marker"
exit 99
SH
chmod 0755 "$WORK/mysql-bin/mysql"
preflight_database || fail 'side-effect-free database configuration validation returned a failure status'
[ ! -e "$mysql_probe_marker" ] || fail 'installer invoked a MySQL client during configuration validation'
release_permissions="$WORK/release-permissions"
mkdir -p "$release_permissions/public" "$release_permissions/ops"
+26 -8
View File
@@ -46,7 +46,7 @@ if KAIDI_APP_ROOT="$WORK/bootstrap/app" \
sh "$ROOT/deploy/update.sh" > "$WORK/bootstrap.log" 2>&1; then
fail 'updater accepted a missing service identity during bootstrap'
fi
grep -Fq "Service user $missing_service_user is missing" "$WORK/bootstrap.log" \
grep -Fq '缺少服务用户' "$WORK/bootstrap.log" \
|| fail 'updater bootstrap failure did not preserve its diagnostic'
[ "$(jq -r '.state' "$WORK/bootstrap/state/status.json")" = FAILED ] \
|| fail 'updater bootstrap failure did not persist FAILED'
@@ -330,6 +330,7 @@ run_update() {
local fixture=$1
local health=$2
local skip_backup=${3:-true}
local backup_mode=${4:-}
env \
PATH="$fixture/mock-bin:$PATH" \
REAL_OPENSSL="$REAL_OPENSSL" \
@@ -353,6 +354,7 @@ run_update() {
KAIDI_UPDATER_PATH="$fixture/app/bin/update.sh" \
KAIDI_RUNTIME_ENV_FILE="$fixture/runtime.env" \
KAIDI_SKIP_DB_BACKUP="$skip_backup" \
KAIDI_DB_BACKUP_MODE="$backup_mode" \
KAIDI_UPDATE_HEALTH_ATTEMPTS=1 \
KAIDI_UPDATE_HEALTH_INTERVAL_SECONDS=0 \
UPDATE_RELEASE_BASE_URL="${FIXTURE_RELEASE_BASE_URL-https://release.fixture.invalid}" \
@@ -366,6 +368,20 @@ run_update() {
"$ROOT/deploy/update.sh"
}
assert_database_backup_opt_in_case() {
local fixture="$WORK/database-backup-opt-in"
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"
run_update "$fixture" success false mysqldump
find "$fixture/state/backups" -type f -name 'mysql-*.sql.gz' -print -quit | grep -q . \
|| fail 'explicit mysqldump mode did not create a database backup'
}
assert_private_gitea_release_case() {
local fixture="$WORK/private-gitea"
local version='1.0.0-preview.2'
@@ -476,7 +492,7 @@ assert_update_path_failure_keeps_application_case() {
|| fail 'path watcher failure rolled back a healthy application'
[ "$(jq -r '.state' "$fixture/state/status.json")" = SUCCEEDED ] \
|| fail 'path watcher failure did not preserve successful application state'
grep -Fq 'automatic update watcher could not be started' "$fixture/state/status.json" \
grep -Fq '自动更新监听器未能启动' "$fixture/state/status.json" \
|| fail 'path watcher failure did not preserve its diagnostic'
}
@@ -492,7 +508,7 @@ assert_rollback_case() {
if run_update "$fixture" fail-new > "$fixture/update.log" 2>&1; then
fail 'rollback case unexpectedly succeeded'
fi
grep -q 'previous application release was restored and verified' "$fixture/update.log" \
grep -q '已恢复并验证旧版本' "$fixture/update.log" \
|| fail 'rollback case did not report a complete restoration'
[ "$(readlink "$fixture/app/current")" = "$fixture/app/releases/1.0.0-preview.1" ] \
|| fail 'rollback case did not restore the previous application'
@@ -524,7 +540,7 @@ assert_incomplete_rollback_requires_manual_recovery_case() {
if run_update "$fixture" fail-after-first > "$fixture/update.log" 2>&1; then
fail 'incomplete rollback case unexpectedly succeeded'
fi
grep -Fq 'manual recovery is required' "$fixture/update.log" \
grep -Fq '需要人工恢复' "$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'
@@ -602,7 +618,7 @@ assert_unhealthy_baseline_blocks_restart_case() {
if run_update "$fixture" fail > "$fixture/update.log" 2>&1; then
fail 'unhealthy baseline unexpectedly reached installation'
fi
grep -Fq 'Current release preflight failed' "$fixture/update.log" \
grep -Fq '当前版本预检查失败' "$fixture/update.log" \
|| fail 'unhealthy baseline did not preserve its preflight diagnostic'
! grep -Eq '^(start|restart|stop) kaidi-finance.service$' "$fixture/systemctl.log" \
|| fail 'unhealthy baseline restarted the application'
@@ -633,7 +649,7 @@ EOF
if run_update "$fixture" success > "$fixture/update.log" 2>&1; then
fail 'invalid database URL unexpectedly reached installation'
fi
grep -Fq 'DB_URL does not match the configured MySQL host, port, and database' "$fixture/update.log" \
grep -Fq 'DB_URL 与主机、端口、库名不一致' "$fixture/update.log" \
|| fail 'structurally invalid JDBC URL did not preserve its diagnostic'
! grep -q '^restart kaidi-finance.service$' "$fixture/systemctl.log" \
|| fail 'invalid database URL restarted the application'
@@ -655,7 +671,7 @@ assert_symlink_request_rejected() {
if run_update "$fixture" success > "$fixture/update.log" 2>&1; then
fail 'symlink request case unexpectedly succeeded'
fi
grep -q 'Update request must be a regular file' "$fixture/update.log" \
grep -q '更新请求必须是普通文件' "$fixture/update.log" \
|| fail 'symlink request case did not report the unsafe request'
[ "$(cat "$fixture/sentinel")" = 'operator-owned sentinel' ] \
|| fail 'symlink request case changed the link target'
@@ -679,7 +695,7 @@ assert_install_without_verified_cache_rejected() {
if run_update "$fixture" success > "$fixture/update.log" 2>&1; then
fail 'install without cache unexpectedly succeeded'
fi
grep -q 'Verified release cache is missing' "$fixture/update.log" \
grep -q '缺少已校验的发布缓存' "$fixture/update.log" \
|| fail 'install without cache did not report the missing verified cache'
[ "$(readlink "$fixture/app/current")" = "$fixture/app/releases/1.0.0-preview.1" ] \
|| fail 'install without cache changed the active application'
@@ -699,6 +715,8 @@ printf '[update-fixture] download-failure\n'
assert_download_failure_case
printf '[update-fixture] database-backup-failure\n'
assert_database_failure_case
printf '[update-fixture] database-backup-opt-in\n'
assert_database_backup_opt_in_case
printf '[update-fixture] unhealthy-baseline\n'
assert_unhealthy_baseline_blocks_restart_case
printf '[update-fixture] invalid-database-url\n'