This commit is contained in:
@@ -75,13 +75,24 @@ JAR="$ROOT/backend/target/finance-system-$VERSION.jar"
|
||||
JAR_METADATA_DIR="$WORK/jar-metadata"
|
||||
mkdir -p "$JAR_METADATA_DIR"
|
||||
(cd "$JAR_METADATA_DIR" && jar -xf "$JAR" \
|
||||
META-INF/MANIFEST.MF META-INF/maven/com.kaidi/finance-system/pom.properties)
|
||||
META-INF/MANIFEST.MF META-INF/maven/com.kaidi/finance-system/pom.properties \
|
||||
BOOT-INF/classes/application.yml BOOT-INF/classes/application-local.yml \
|
||||
BOOT-INF/classes/application-production.yml)
|
||||
JAR_POM_VERSION=$(sed -n 's/^version=//p' \
|
||||
"$JAR_METADATA_DIR/META-INF/maven/com.kaidi/finance-system/pom.properties")
|
||||
JAR_IMPLEMENTATION_VERSION=$(sed -n 's/^Implementation-Version: //p' \
|
||||
"$JAR_METADATA_DIR/META-INF/MANIFEST.MF" | tr -d '\r')
|
||||
[ "$JAR_POM_VERSION" = "$VERSION" ] && [ "$JAR_IMPLEMENTATION_VERSION" = "$VERSION" ] \
|
||||
|| { printf 'JAR metadata versions do not match release %s\n' "$VERSION" >&2; exit 1; }
|
||||
grep -Fq 'default: production' "$JAR_METADATA_DIR/BOOT-INF/classes/application.yml" \
|
||||
|| { printf 'Release JAR must default to the production profile\n' >&2; exit 1; }
|
||||
if grep -Eq '127\.0\.0\.1:3307|kaidi_local_2026' \
|
||||
"$JAR_METADATA_DIR/BOOT-INF/classes/application.yml" \
|
||||
"$JAR_METADATA_DIR/BOOT-INF/classes/application-local.yml" \
|
||||
"$JAR_METADATA_DIR/BOOT-INF/classes/application-production.yml"; then
|
||||
printf 'Release JAR contains a development database fallback\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$OUTPUT_DIR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
@@ -102,6 +113,15 @@ cp "$ROOT/deploy/systemd/kaidi-update.service" "$STAGE/ops/kaidi-update.service"
|
||||
cp "$ROOT/deploy/systemd/kaidi-update.path" "$STAGE/ops/kaidi-update.path"
|
||||
chmod 0755 "$STAGE/ops/update.sh" "$STAGE/ops/baota-start.sh" "$STAGE/ops/baota-init.sh"
|
||||
|
||||
if find "$STAGE" -type l -print -quit | grep -q .; then
|
||||
printf 'Release stage contains a symbolic link\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if find "$STAGE" -type f -print | grep -Eq '(^|/)(\.DS_Store|__MACOSX|[^/]*\._[^/]*)$'; then
|
||||
printf 'Release stage contains macOS metadata\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARTIFACT="kaidi-finance-$VERSION.tar.gz"
|
||||
COPYFILE_DISABLE=1 tar --format=ustar -czf "$OUTPUT_DIR/$ARTIFACT" -C "$STAGE" .
|
||||
SHA256=$(sha256_file "$OUTPUT_DIR/$ARTIFACT")
|
||||
|
||||
@@ -51,6 +51,10 @@ EOF
|
||||
cat > "$WORK/java" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
if [ "${1:-}" = -version ]; then
|
||||
printf 'openjdk version "17-fixture"\n' >&2
|
||||
exit 0
|
||||
fi
|
||||
{
|
||||
printf 'cwd=%s\n' "$PWD"
|
||||
printf 'args=%s\n' "$*"
|
||||
@@ -92,6 +96,34 @@ PID_RECORD=$(sed -n 's/^pid-record=//p' "$WORK/java.log")
|
||||
SELF_PID=$(sed -n 's/^self-pid=//p' "$WORK/java.log")
|
||||
[ "$PID_RECORD" = "$SELF_PID 424242" ] || fail 'PID file did not bind the PID to its proc start time'
|
||||
|
||||
# systemd reads the root-only EnvironmentFile before dropping to User=kaidi.
|
||||
# The launcher must therefore be able to start with those files intentionally
|
||||
# unreadable by the service user.
|
||||
chmod 000 "$WORK/base.env" "$WORK/runtime.env"
|
||||
MOCK_JAVA_LOG="$WORK/preloaded.log" \
|
||||
PATH="$WORK/mock-bin:$PATH" \
|
||||
KAIDI_ENV_PRELOADED=true \
|
||||
KAIDI_JAVA_BIN="$WORK/java" \
|
||||
SPRING_PROFILES_ACTIVE=production \
|
||||
SERVER_ADDRESS=127.0.0.1 \
|
||||
SERVER_PORT=21000 \
|
||||
DB_URL='jdbc:mysql://db.fixture/kaidi_finance' \
|
||||
DB_USERNAME=fixture-user \
|
||||
DB_PASSWORD='fixture-password' \
|
||||
FIELD_ENCRYPTION_KEY=fixture-field-key \
|
||||
FILE_STORAGE_ROOT="$WORK/files" \
|
||||
FILE_STORAGE_TEMP="$WORK/tmp" \
|
||||
FINANCE_SETUP_ENABLED=false \
|
||||
FINANCE_UPDATE_ENABLED=true \
|
||||
FILE_SCANNER_ENABLED=false \
|
||||
KAIDI_PID_FILE="$WORK/state/preloaded.pid" \
|
||||
KAIDI_CONFIG_FILE="$WORK/base.env" \
|
||||
KAIDI_RUNTIME_ENV_FILE="$WORK/runtime.env" \
|
||||
"$RELEASE/ops/baota-start.sh"
|
||||
grep -Fqx 'port=21000' "$WORK/preloaded.log" \
|
||||
|| fail 'launcher still depended on the root-only configuration files under systemd'
|
||||
chmod 0644 "$WORK/base.env" "$WORK/runtime.env"
|
||||
|
||||
MOCK_JAVA_LOG="$WORK/explicit.log" \
|
||||
PATH="$WORK/mock-bin:$PATH" \
|
||||
KAIDI_JAVA_BIN="$WORK/java" \
|
||||
|
||||
@@ -29,6 +29,7 @@ mode_of() {
|
||||
sed -n '/^normalized_host_arch()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^azul_arch()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^java_arch_matches_host()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^java_home_from_bin()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^system_java_home()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^sha256_file()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^prepare_java()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
@@ -192,7 +193,7 @@ cat > "$WORK/fake-jre/bin/java" <<'JAVA'
|
||||
if [ "${1:-}" = '-XshowSettings:properties' ]; then
|
||||
printf ' os.arch = x86\n' >&2
|
||||
fi
|
||||
printf 'openjdk version "17-fixture"\n' >&2
|
||||
printf 'openjdk version "17.0.8"\n' >&2
|
||||
JAVA
|
||||
chmod 0755 "$WORK/fake-jre/bin/java"
|
||||
tar -czf "$WORK/java-fixture.tar.gz" -C "$WORK" fake-jre
|
||||
@@ -213,16 +214,22 @@ download() {
|
||||
esac
|
||||
}
|
||||
ARCH_FIXTURE=i686
|
||||
APP_ROOT="$WORK/app"
|
||||
export KAIDI_JAVA_HOME="$WORK/missing-java-home"
|
||||
prepare_java >/dev/null 2>&1
|
||||
[ -x "$JAVA_STAGED_DIR/bin/java" ] || fail 'verified i686 Java runtime was not staged'
|
||||
[ "$JAVA_BIN" = "$APP_ROOT/runtime/java/bin/java" ] \
|
||||
|| fail 'downloaded Java did not select the managed fallback path'
|
||||
rm -rf "$JAVA_STAGED_DIR"
|
||||
JAVA_STAGED_DIR=
|
||||
export KAIDI_JAVA_HOME="$WORK/fake-jre"
|
||||
prepare_java >/dev/null 2>&1
|
||||
[ -d "$JAVA_STAGED_DIR" ] && [ ! -L "$JAVA_STAGED_DIR" ] \
|
||||
|| fail 'existing Java 17 runtime was not copied into managed storage'
|
||||
cmp -s "$WORK/fake-jre/bin/java" "$JAVA_STAGED_DIR/bin/java" \
|
||||
|| fail 'installer staged an unexpected local Java runtime'
|
||||
[ -z "$JAVA_STAGED_DIR" ] \
|
||||
|| fail 'installer copied an existing server Java runtime into application storage'
|
||||
EXPECTED_LOCAL_JAVA=$(readlink -f "$WORK/fake-jre/bin/java" 2>/dev/null \
|
||||
|| printf '%s' "$WORK/fake-jre/bin/java")
|
||||
[ "$JAVA_BIN" = "$EXPECTED_LOCAL_JAVA" ] \
|
||||
|| fail 'installer did not select the existing server Java executable directly'
|
||||
|
||||
export RELEASE_API_URL=https://gitea.fixture.invalid/api/v1/repos/ERP-Team/kaidi/releases/latest
|
||||
RELEASE_TOKEN=fixture-read-only-token
|
||||
@@ -280,6 +287,12 @@ 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 '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" \
|
||||
|| fail 'installer does not reject a 32-bit host without the glibc loader'
|
||||
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 'write_env_file_preserving_unknown "$CONFIG_ROOT/kaidi.env"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer uses the reinstall-safe environment writer'
|
||||
@@ -289,14 +302,14 @@ grep -Fq 'download "$api" "$java_metadata"' "$ROOT/deploy/install.sh" \
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq 'java_sha256=$(jq -er' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer obtains the Java runtime SHA-256'
|
||||
grep -Fq 'Copying existing Java 17 runtime' "$ROOT/deploy/install.sh" \
|
||||
grep -Fq 'Using existing Java 17+ runtime' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer reuses a local Java 17 runtime'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq 'cp -R "$system_home/." "$JAVA_STAGED_DIR/"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer copies a local Java runtime into managed storage'
|
||||
grep -Fq 'JAVA_BIN="$system_home/bin/java"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer records the selected server Java path'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
! grep -Fq 'ln -s "$system_home"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer must not link the service to an externally managed Java directory'
|
||||
! grep -Fq 'cp -R "$system_home/."' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer copied an existing server Java runtime into managed storage'
|
||||
# shellcheck disable=SC2016 # Match the literal installer command.
|
||||
grep -Fq -- '--connect-timeout 1 --max-time 2 "$HEALTH_URL" 2>/dev/null' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer performs a quiet bounded health check'
|
||||
@@ -317,6 +330,10 @@ grep -Fq 'systemctl reset-failed kaidi-finance.service' "$ROOT/deploy/install.sh
|
||||
|| fail 'installer can execute before the complete curl stream is parsed'
|
||||
grep -Fqx 'StartLimitBurst=3' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
||||
|| fail 'application service no longer has a bounded restart burst'
|
||||
grep -Fqx 'ExecStart=/opt/kaidi/current/ops/baota-start.sh' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
||||
|| fail 'application service bypasses the Java-selecting launcher'
|
||||
grep -Fqx 'Environment=KAIDI_ENV_PRELOADED=true' "$ROOT/deploy/systemd/kaidi-finance.service" \
|
||||
|| fail 'systemd service user would need to read the root-only configuration directory'
|
||||
grep -Fqx 'Restart=no' "$ROOT/deploy/systemd/kaidi-update.service" \
|
||||
|| fail 'update service can automatically repeat a failed switching transaction'
|
||||
grep -Fq -- '-/www/wwwroot/kaidi' "$ROOT/deploy/systemd/kaidi-update.service" \
|
||||
@@ -392,9 +409,11 @@ 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.25.tar.gz' "$ROOT/README.md" \
|
||||
grep -Fq 'kaidi-finance-1.0.0-preview.31.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'
|
||||
grep -Fq 'FINANCE_UPDATE_ENABLED false' "$ROOT/deploy/baota-init.sh" \
|
||||
|| fail 'Baota manual mode still exposes the unstable online updater'
|
||||
|
||||
printf 'Install configuration, custom port, public Gitea, optional private token, i686, setup wizard, and MySQL 8.4 fixtures passed\n'
|
||||
|
||||
@@ -221,10 +221,14 @@ build_release() {
|
||||
local unit_prefix=${3:-new}
|
||||
local stage="$fixture/stage"
|
||||
mkdir -p "$fixture/release" "$stage/public" "$stage/ops"
|
||||
"$REAL_OPENSSL" genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
|
||||
-out "$fixture/private.pem" >/dev/null 2>&1
|
||||
"$REAL_OPENSSL" pkey -in "$fixture/private.pem" -pubout \
|
||||
-out "$fixture/release-public.pem" >/dev/null 2>&1
|
||||
if [ ! -s "$WORK/fixture-private.pem" ]; then
|
||||
"$REAL_OPENSSL" genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
|
||||
-out "$WORK/fixture-private.pem" >/dev/null 2>&1
|
||||
"$REAL_OPENSSL" pkey -in "$WORK/fixture-private.pem" -pubout \
|
||||
-out "$WORK/fixture-public.pem" >/dev/null 2>&1
|
||||
fi
|
||||
cp "$WORK/fixture-private.pem" "$fixture/private.pem"
|
||||
cp "$WORK/fixture-public.pem" "$fixture/release-public.pem"
|
||||
printf 'new application\n' > "$stage/app.jar"
|
||||
printf '<!doctype html><title>new</title>\n' > "$stage/public/index.html"
|
||||
printf '%s\n' "$version" > "$stage/VERSION"
|
||||
@@ -681,17 +685,30 @@ assert_install_without_verified_cache_rejected() {
|
||||
|| fail 'install without cache changed the active application'
|
||||
}
|
||||
|
||||
printf '[update-fixture] success\n'
|
||||
assert_success_case
|
||||
printf '[update-fixture] identical-systemd-operations\n'
|
||||
assert_identical_systemd_operations_case
|
||||
printf '[update-fixture] path-watcher-failure\n'
|
||||
assert_update_path_failure_keeps_application_case
|
||||
printf '[update-fixture] rollback\n'
|
||||
assert_rollback_case
|
||||
printf '[update-fixture] incomplete-rollback-recovery-lock\n'
|
||||
assert_incomplete_rollback_requires_manual_recovery_case
|
||||
printf '[update-fixture] download-failure\n'
|
||||
assert_download_failure_case
|
||||
printf '[update-fixture] database-backup-failure\n'
|
||||
assert_database_failure_case
|
||||
printf '[update-fixture] unhealthy-baseline\n'
|
||||
assert_unhealthy_baseline_blocks_restart_case
|
||||
printf '[update-fixture] invalid-database-url\n'
|
||||
assert_invalid_database_url_blocks_restart_case
|
||||
printf '[update-fixture] symlink-request\n'
|
||||
assert_symlink_request_rejected
|
||||
printf '[update-fixture] missing-verified-cache\n'
|
||||
assert_install_without_verified_cache_rejected
|
||||
printf '[update-fixture] private-gitea\n'
|
||||
assert_private_gitea_release_case
|
||||
printf '[update-fixture] cross-origin-gitea\n'
|
||||
assert_cross_origin_gitea_browser_url_ignored
|
||||
printf 'Online update download, confirmation, success, rollback, failure, and unsafe-request fixtures passed\n'
|
||||
|
||||
@@ -153,17 +153,36 @@ if grep -Eq '(^|/)(signing-private|private-key|id_rsa)' <<< "$ARCHIVE_LIST"; the
|
||||
printf 'Release archive contains private key material\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -Eq '(^|/)(\.DS_Store|__MACOSX|[^/]*\._[^/]*)$' <<< "$ARCHIVE_LIST"; then
|
||||
printf 'Release archive contains macOS metadata\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if tar -tvzf "$ARTIFACT" | grep -Eq '^l'; then
|
||||
printf 'Release archive contains a symbolic link\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
[ "$(tar -xOf "$ARTIFACT" ./VERSION)" = "$VERSION" ] \
|
||||
|| { printf 'Release archive version does not match the manifest\n' >&2; exit 1; }
|
||||
tar -xOf "$ARTIFACT" ./app.jar > "$WORK/app.jar"
|
||||
(cd "$WORK" && jar -xf app.jar \
|
||||
META-INF/MANIFEST.MF META-INF/maven/com.kaidi/finance-system/pom.properties)
|
||||
META-INF/MANIFEST.MF META-INF/maven/com.kaidi/finance-system/pom.properties \
|
||||
BOOT-INF/classes/application.yml BOOT-INF/classes/application-local.yml \
|
||||
BOOT-INF/classes/application-production.yml)
|
||||
JAR_POM_VERSION=$(sed -n 's/^version=//p' \
|
||||
"$WORK/META-INF/maven/com.kaidi/finance-system/pom.properties")
|
||||
JAR_IMPLEMENTATION_VERSION=$(sed -n 's/^Implementation-Version: //p' \
|
||||
"$WORK/META-INF/MANIFEST.MF" | tr -d '\r')
|
||||
[ "$JAR_POM_VERSION" = "$VERSION" ] && [ "$JAR_IMPLEMENTATION_VERSION" = "$VERSION" ] \
|
||||
|| { printf 'Release JAR metadata versions do not match the manifest\n' >&2; exit 1; }
|
||||
grep -Fq 'default: production' "$WORK/BOOT-INF/classes/application.yml" \
|
||||
|| { printf 'Release JAR does not default to the production profile\n' >&2; exit 1; }
|
||||
if grep -Eq '127\.0\.0\.1:3307|kaidi_local_2026' \
|
||||
"$WORK/BOOT-INF/classes/application.yml" \
|
||||
"$WORK/BOOT-INF/classes/application-local.yml" \
|
||||
"$WORK/BOOT-INF/classes/application-production.yml"; then
|
||||
printf 'Release JAR contains a development database fallback\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
compare_archive_file() {
|
||||
archive_path=$1
|
||||
|
||||
Reference in New Issue
Block a user