fix: harden first-run install and clean reinstall
Signed Release / release (push) Failing after 33s

This commit is contained in:
Qiufeng
2026-08-04 12:59:24 +08:00
parent 4d6a9307e5
commit f9545a9d0f
10 changed files with 566 additions and 17 deletions
+174
View File
@@ -56,6 +56,83 @@ test_erp_run_preserves_java_option_arguments() (
grep -Fqx -- "$tmp/current/app/kaidi-erp.jar" <<< "$output"
)
test_erp_run_finalizes_healthy_pending_install() (
local tmp
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-handoff-success.XXXXXX")" || return 1
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state" "$tmp/installer" "$tmp/bin"
: > "$tmp/current/app/kaidi-erp.jar"
: > "$tmp/installer/kaidi-erp-installer.jar"
printf 'pending\n' > "$tmp/state/install.pending"
printf '%s\n' \
'#!/usr/bin/env bash' \
'if [[ "${1:-}" == "-version" ]]; then printf '\''openjdk version "17.0.12"\n'\'' >&2; exit 0; fi' \
'printf '\''formal application output\n'\''' \
'sleep 0.1' \
'exit 0' > "$tmp/java"
printf '%s\n' '#!/usr/bin/env bash' 'exit 0' > "$tmp/bin/curl"
chmod +x "$tmp/java" "$tmp/bin/curl"
{
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
printf 'ERP_JAR_PATH=%q\n' "$tmp/current/app/kaidi-erp.jar"
printf 'ERP_INSTALLER_JAR=%q\n' "$tmp/installer/kaidi-erp-installer.jar"
printf 'ERP_INSTALL_PENDING_FILE=%q\n' "$tmp/state/install.pending"
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
printf 'ERP_INSTALL_LOG_FILE=%q\n' "$tmp/state/install-formal.log"
printf 'ERP_INSTALL_HEALTH_TIMEOUT_SECONDS=3\n'
printf 'ERP_UPDATE_HEALTH_POLL_SECONDS=1\n'
} > "$tmp/erp.env"
PATH="$tmp/bin:$PATH" ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
"$PROJECT_ROOT/distribution/bin/erp-run" > "$tmp/run.log" 2>&1 || return 1
[[ -f "$tmp/state/install.lock" ]] || return 1
[[ ! -e "$tmp/state/install.pending" ]] || return 1
[[ ! -d "$tmp/installer" ]] || return 1
[[ ! -e "$tmp/run/app.pid" ]] || return 1
grep -Fq 'formal application output' "$tmp/state/install-formal.log"
)
test_erp_run_preserves_failed_pending_install() (
local tmp output status=0
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-handoff-failure.XXXXXX")" || return 1
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/current/app" "$tmp/run" "$tmp/state" "$tmp/installer" "$tmp/bin"
: > "$tmp/current/app/kaidi-erp.jar"
: > "$tmp/installer/kaidi-erp-installer.jar"
printf 'pending\n' > "$tmp/state/install.pending"
printf '%s\n' \
'#!/usr/bin/env bash' \
'if [[ "${1:-}" == "-version" ]]; then printf '\''openjdk version "17.0.12"\n'\'' >&2; exit 0; fi' \
'printf '\''formal application exploded\n'\'' >&2' \
'sleep 0.1' \
'exit 23' > "$tmp/java"
printf '%s\n' '#!/usr/bin/env bash' 'exit 1' > "$tmp/bin/curl"
chmod +x "$tmp/java" "$tmp/bin/curl"
{
printf 'ERP_JAVA_BIN=%q\n' "$tmp/java"
printf 'ERP_RUN_DIR=%q\n' "$tmp/run"
printf 'ERP_JAR_PATH=%q\n' "$tmp/current/app/kaidi-erp.jar"
printf 'ERP_INSTALLER_JAR=%q\n' "$tmp/installer/kaidi-erp-installer.jar"
printf 'ERP_INSTALL_PENDING_FILE=%q\n' "$tmp/state/install.pending"
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
printf 'ERP_INSTALL_LOG_FILE=%q\n' "$tmp/state/install-formal.log"
printf 'ERP_INSTALL_HEALTH_TIMEOUT_SECONDS=3\n'
printf 'ERP_UPDATE_HEALTH_POLL_SECONDS=1\n'
} > "$tmp/erp.env"
output="$(PATH="$tmp/bin:$PATH" ERP_INSTALL_ROOT="$tmp" ERP_CONFIG_FILE="$tmp/erp.env" \
"$PROJECT_ROOT/distribution/bin/erp-run" 2>&1)" || status=$?
[[ "$status" -ne 0 ]] || return 1
[[ -f "$tmp/state/install.pending" ]] || return 1
[[ ! -e "$tmp/state/install.lock" ]] || return 1
[[ -d "$tmp/installer" ]] || return 1
grep -Fq 'formal application exploded' "$tmp/state/install-formal.log" || return 1
[[ "$output" == *"Formal application diagnostics: $tmp/state/install-formal.log"* ]]
)
prepare_signed_archive() {
local tmp="$1" unsafe="${2:-0}"
mkdir -p "$tmp/package/kaidi-erp-1.2.3/app"
@@ -483,6 +560,98 @@ test_systemd_unit_uses_unquoted_legacy_paths() (
! grep -Fq 'WorkingDirectory="$INSTALL_ROOT"' <<< "$unit"
)
test_uninstaller_purges_database_before_removing_files() (
local tmp
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-uninstall-test.XXXXXX")" || return 1
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/bin" "$tmp/install" "$tmp/config" "$tmp/state" "$tmp/logs"
: > "$tmp/unit.service"
{
printf 'ERP_PGHOST=127.0.0.1\n'
printf 'ERP_PGPORT=5432\n'
printf 'ERP_PGDATABASE=kaidi_test\n'
printf 'ERP_PGSSLMODE=disable\n'
printf 'OA_DB_USERNAME=kaidi_test\n'
printf 'OA_DB_PASSWORD='\''test-password'\''\n'
printf 'ERP_INSTALL_ROOT=%q\n' "$tmp/install"
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
} > "$tmp/config/erp.env"
printf '%s\n' \
'#!/usr/bin/env bash' \
'printf '\''%s\n'\'' "$*" >> "$MOCK_SYSTEMCTL_LOG"' \
'if [[ "${1:-}" == "is-active" ]]; then exit 3; fi' \
'exit 0' > "$tmp/bin/systemctl"
printf '%s\n' \
'#!/usr/bin/env bash' \
'printf '\''ARGS %s\n'\'' "$*" >> "$MOCK_PSQL_LOG"' \
'if [[ " $* " == *" -c "* ]]; then printf '\''t\n'\''; exit 0; fi' \
'cat >> "$MOCK_PSQL_LOG"' \
'exit 0' > "$tmp/bin/psql"
chmod +x "$tmp/bin/systemctl" "$tmp/bin/psql"
PATH="$tmp/bin:$PATH" \
MOCK_SYSTEMCTL_LOG="$tmp/systemctl.log" \
MOCK_PSQL_LOG="$tmp/psql.log" \
ERP_UNINSTALL_TEST_MODE=1 \
ERP_UNINSTALL_SYSTEMD_UNIT_FILE="$tmp/unit.service" \
"$PROJECT_ROOT/uninstall.sh" --purge-database --yes \
--config-file "$tmp/config/erp.env" \
--install-root "$tmp/install" \
--config-root "$tmp/config" \
--state-root "$tmp/state" \
--log-root "$tmp/logs" > "$tmp/uninstall.log" 2>&1 || return 1
[[ ! -e "$tmp/install" && ! -e "$tmp/config" && ! -e "$tmp/state" && ! -e "$tmp/logs" ]] || return 1
[[ ! -e "$tmp/unit.service" ]] || return 1
grep -Fq 'DROP SCHEMA IF EXISTS public CASCADE;' "$tmp/psql.log" || return 1
grep -Fq 'stop kaidi-erp.service' "$tmp/systemctl.log" || return 1
grep -Fq 'disable kaidi-erp.service' "$tmp/systemctl.log"
)
test_uninstaller_rejects_unsafe_paths() (
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /etc) >/dev/null 2>&1 || return 1
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /etc/ssh) >/dev/null 2>&1 || return 1
! (source "$PROJECT_ROOT/uninstall.sh"; validate_remove_path /opt/kaidi-erp/../../etc) >/dev/null 2>&1
)
test_uninstaller_aborts_when_service_remains_active() (
local tmp output status=0
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-uninstall-active.XXXXXX")" || return 1
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/bin" "$tmp/install" "$tmp/config" "$tmp/state" "$tmp/logs"
{
printf 'ERP_PGHOST=127.0.0.1\n'
printf 'ERP_PGPORT=5432\n'
printf 'ERP_PGDATABASE=kaidi_test\n'
printf 'ERP_PGSSLMODE=disable\n'
printf 'OA_DB_USERNAME=kaidi_test\n'
printf 'OA_DB_PASSWORD=test-password\n'
printf 'ERP_INSTALL_ROOT=%q\n' "$tmp/install"
printf 'ERP_INSTALL_LOCK_FILE=%q\n' "$tmp/state/install.lock"
} > "$tmp/config/erp.env"
printf '%s\n' \
'#!/usr/bin/env bash' \
'if [[ "${1:-}" == "stop" ]]; then exit 1; fi' \
'if [[ "${1:-}" == "show" ]]; then printf '\''loaded\n'\''; exit 0; fi' \
'if [[ "${1:-}" == "is-active" ]]; then exit 0; fi' \
'exit 0' > "$tmp/bin/systemctl"
printf '%s\n' '#!/usr/bin/env bash' 'touch "$MOCK_PSQL_CALLED"' 'exit 0' > "$tmp/bin/psql"
chmod +x "$tmp/bin/systemctl" "$tmp/bin/psql"
output="$(PATH="$tmp/bin:$PATH" MOCK_PSQL_CALLED="$tmp/psql.called" \
ERP_UNINSTALL_TEST_MODE=1 ERP_UNINSTALL_SYSTEMD_UNIT_FILE="$tmp/unit.service" \
"$PROJECT_ROOT/uninstall.sh" --purge-database --yes \
--config-file "$tmp/config/erp.env" \
--install-root "$tmp/install" \
--config-root "$tmp/config" \
--state-root "$tmp/state" \
--log-root "$tmp/logs" 2>&1)" || status=$?
[[ "$status" -ne 0 && "$output" == *'unable to stop kaidi-erp.service'* ]] || return 1
[[ ! -e "$tmp/psql.called" ]] || return 1
[[ -d "$tmp/install" && -d "$tmp/config" && -d "$tmp/state" && -d "$tmp/logs" ]]
)
test_no_service_install_disables_online_update() (
local tmp
tmp="$(mktemp -d "${TMPDIR:-/tmp}/erp-no-service-test.XXXXXX")" || return 1
@@ -524,6 +693,8 @@ test_installer_runs_when_piped_to_bash() (
)
run_test 'erp-run preserves Java option arguments' test_erp_run_preserves_java_option_arguments
run_test 'erp-run finalizes a healthy pending installation' test_erp_run_finalizes_healthy_pending_install
run_test 'erp-run preserves a failed pending installation' test_erp_run_preserves_failed_pending_install
run_test 'installer accepts a correctly signed archive' test_installer_verifies_signed_safe_archive
run_test 'installer rejects symlinks in release archives' test_installer_rejects_archive_symlinks
run_test 'stable update channel rejects prerelease tags' test_stable_channel_rejects_prerelease_tag
@@ -536,6 +707,9 @@ run_test 'installer requires an explicit Gitea URL' test_installer_requires_expl
run_test 'Linux production install requires systemd' test_linux_service_preflight_requires_systemd
run_test 'systemd unit uses compatible protection' test_systemd_unit_uses_compatible_protection
run_test 'systemd unit uses unquoted legacy paths' test_systemd_unit_uses_unquoted_legacy_paths
run_test 'uninstaller purges database before removing files' test_uninstaller_purges_database_before_removing_files
run_test 'uninstaller rejects unsafe paths' test_uninstaller_rejects_unsafe_paths
run_test 'uninstaller aborts while the service remains active' test_uninstaller_aborts_when_service_remains_active
run_test 'no-service install disables online update' test_no_service_install_disables_online_update
run_test 'release workflow uses the scoped Gitea job token' test_release_workflow_uses_scoped_job_token
run_test 'installer starts correctly when piped to bash' test_installer_runs_when_piped_to_bash