Compare commits

..
Author SHA1 Message Date
Qiufeng 36f1a8b00d fix: keep setup database config empty and isolate Baota mode
Release / release (push) Successful in 19m31s
2026-08-18 19:50:14 +08:00
Qiufeng 69f738465e ci: tolerate checkout line-ending normalization
Release / release (push) Failing after 19m3s
2026-08-18 19:40:28 +08:00
Qiufeng 2712c5b8c1 ci: resolve release tag from checked out refs
Release / release (push) Failing after 7s
2026-08-18 19:33:06 +08:00
Qiufeng 5a48993d26 ci: use registered release runner label
Release / release (push) Failing after 10s
2026-08-18 19:30:25 +08:00
Qiufeng 0b91e1fc5f ci: isolate release runner and derive tag metadata
Release / release (push) Canceled after 0s
2026-08-18 17:54:23 +08:00
Qiufeng 95cd1daeb8 ci: use valid release token secret name
Release / release (push) Canceled after 0s
2026-08-18 17:38:40 +08:00
4 changed files with 83 additions and 62 deletions
+40 -20
View File
@@ -10,9 +10,10 @@ permissions:
jobs: jobs:
release: release:
# The repository runner advertises Gitea's default ubuntu-latest label. # The temporary release runner is isolated from stale queued jobs that use
# Using an unavailable custom label leaves every release permanently queued. # the old ubuntu-24.04 label. Restore ubuntu-latest after this release when
runs-on: ubuntu-latest # the repository's normal Linux runner is online again.
runs-on: kaidi-release
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -20,16 +21,25 @@ jobs:
- name: Validate release tag - name: Validate release tag
id: release_meta id: release_meta
env:
GITEA_REF_NAME: ${{ github.ref_name }}
GITEA_SHA: ${{ github.sha }}
run: | 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} VERSION=${GITEA_REF_NAME#v}
test "$GITEA_REF_NAME" = "v$VERSION" test "$GITEA_REF_NAME" = "v$VERSION"
./scripts/check-semver.sh "$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 "$(git rev-parse "refs/tags/$GITEA_REF_NAME^{commit}")" = "$GITEA_SHA"
test -z "$(git status --porcelain --untracked-files=all)" test -z "$(git ls-files --others --exclude-standard)"
printf 'version=%s\n' "$VERSION" >> "$GITHUB_OUTPUT" 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 - uses: actions/setup-java@v4
with: with:
@@ -57,7 +67,11 @@ jobs:
npm test npm test
npm run audit:dependencies npm run audit:dependencies
npm run build 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:e2e
npm run test:dist npm run test:dist
@@ -85,14 +99,18 @@ jobs:
RELEASE_SIGNING_KEY_B64: ${{ secrets.RELEASE_SIGNING_KEY_B64 }} RELEASE_SIGNING_KEY_B64: ${{ secrets.RELEASE_SIGNING_KEY_B64 }}
RELEASE_VERSION: ${{ steps.release_meta.outputs.version }} RELEASE_VERSION: ${{ steps.release_meta.outputs.version }}
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }} KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }}
KAIDI_RELEASE_NOTES: Kaidi Finance ${{ github.ref_name }} KAIDI_RELEASE_NOTES: Kaidi Finance ${{ steps.release_meta.outputs.ref }}
KAIDI_SOURCE_REVISION: ${{ github.sha }} KAIDI_SOURCE_REVISION: ${{ steps.release_meta.outputs.revision }}
KAIDI_SOURCE_REF: ${{ github.ref_name }} KAIDI_SOURCE_REF: ${{ steps.release_meta.outputs.ref }}
KAIDI_SOURCE_DIRTY: 'false' KAIDI_SOURCE_DIRTY: 'false'
run: | run: |
test -n "$RELEASE_SIGNING_KEY_B64" 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 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" chmod 600 "$RUNNER_TEMP/release-key.pem"
KAIDI_RELEASE_SIGNING_KEY="$RUNNER_TEMP/release-key.pem" \ KAIDI_RELEASE_SIGNING_KEY="$RUNNER_TEMP/release-key.pem" \
./scripts/package-release.sh "$RELEASE_VERSION" ./scripts/package-release.sh "$RELEASE_VERSION"
@@ -100,18 +118,20 @@ jobs:
- name: Verify signed release assets - name: Verify signed release assets
env: env:
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }} KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256: ${{ vars.KAIDI_RELEASE_PUBLIC_KEY_SHA256 }}
KAIDI_EXPECTED_SOURCE_REVISION: ${{ github.sha }} KAIDI_EXPECTED_SOURCE_REVISION: ${{ steps.release_meta.outputs.revision }}
KAIDI_EXPECTED_SOURCE_REF: ${{ github.ref_name }} KAIDI_EXPECTED_SOURCE_REF: ${{ steps.release_meta.outputs.ref }}
KAIDI_EXPECTED_SOURCE_DIRTY: 'false' KAIDI_EXPECTED_SOURCE_DIRTY: 'false'
run: ./scripts/verify-release.sh dist/release run: ./scripts/verify-release.sh dist/release
- name: Publish Gitea release - name: Publish Gitea release
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.RELEASE_GITEA_TOKEN }}
GITEA_SERVER_URL: ${{ github.server_url }} # Gitea's runner leaves github.server_url/repository empty on some
GITEA_REPOSITORY: ${{ github.repository }} # host-mode tag events; keep the release destination explicit.
GITEA_REF_NAME: ${{ github.ref_name }} GITEA_SERVER_URL: https://git.awaioi.com
GITEA_SHA: ${{ github.sha }} 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 run: ./scripts/publish-gitea-release.sh
- name: Upload Playwright report after failure - name: Upload Playwright report after failure
+11 -39
View File
@@ -9,7 +9,6 @@ CONFIG_ROOT=/etc/kaidi
STATE_ROOT=/var/lib/kaidi STATE_ROOT=/var/lib/kaidi
UPDATE_STATE_ROOT=/var/lib/kaidi-update UPDATE_STATE_ROOT=/var/lib/kaidi-update
LOG_ROOT=/var/log/kaidi LOG_ROOT=/var/log/kaidi
UPDATER_ROOT=/opt/kaidi/bin
PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest
INIT_ARMED=false INIT_ARMED=false
@@ -29,7 +28,7 @@ rollback_initialization() {
rm -f /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path rm -f /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path
systemctl daemon-reload >/dev/null 2>&1 || true systemctl daemon-reload >/dev/null 2>&1 || true
rm -f "$CONFIG_ROOT/kaidi.env" "$CONFIG_ROOT/update.env" \ 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 "$UPDATE_STATE_ROOT/status.json" /root/kaidi-first-login.txt
rm -f "$INIT_APP_ROOT/current.next" rm -f "$INIT_APP_ROOT/current.next"
if [ -n "$INIT_RELEASE_ROOT" ] \ if [ -n "$INIT_RELEASE_ROOT" ] \
@@ -95,7 +94,7 @@ main() {
[ "$(uname -s)" = Linux ] || die "Baota initialization only supports Linux" [ "$(uname -s)" = Linux ] || die "Baota initialization only supports Linux"
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] \ command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] \
|| die "systemd is required for the privileged background updater" || 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" command -v "$command" >/dev/null 2>&1 || die "$command is required"
done done
@@ -137,7 +136,7 @@ main() {
INIT_ARMED=true INIT_ARMED=true
ensure_service_identity 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 \ install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 \
"$STATE_ROOT" "$STATE_ROOT/files" "$STATE_ROOT/tmp" "$LOG_ROOT" "$STATE_ROOT" "$STATE_ROOT/files" "$STATE_ROOT/tmp" "$LOG_ROOT"
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0700 "$STATE_ROOT/setup" 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_ADDRESS 127.0.0.1 \
SERVER_PORT "$app_port" \ SERVER_PORT "$app_port" \
SESSION_COOKIE_SECURE false \ SESSION_COOKIE_SECURE false \
DB_URL 'jdbc:mysql://setup.invalid:3306/kaidi_finance' \ # The setup context has no datasource. Empty values avoid Baota treating
DB_USERNAME setup_pending \ # a development placeholder as a real local MySQL dependency.
DB_PASSWORD setup_pending \ DB_URL '' \
DB_USERNAME '' \
DB_PASSWORD '' \
FIELD_ENCRYPTION_KEY "$field_key" \ FIELD_ENCRYPTION_KEY "$field_key" \
FILE_STORAGE_ROOT "$STATE_ROOT/files" \ FILE_STORAGE_ROOT "$STATE_ROOT/files" \
FILE_STORAGE_TEMP "$STATE_ROOT/tmp" \ FILE_STORAGE_TEMP "$STATE_ROOT/tmp" \
@@ -187,39 +188,10 @@ main() {
chown root:"$SERVICE_GROUP" "$CONFIG_ROOT/kaidi.env" chown root:"$SERVICE_GROUP" "$CONFIG_ROOT/kaidi.env"
chmod 0640 "$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 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' \ 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" "$version" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$UPDATE_STATE_ROOT/status.json"
+8 -3
View File
@@ -586,9 +586,11 @@ configure_database() {
if [ "$SETUP_WIZARD" = true ]; then if [ "$SETUP_WIZARD" = true ]; then
[ -z "${KAIDI_DB_URL:-}${KAIDI_DB_USERNAME:-}${KAIDI_DB_PASSWORD:-}" ] \ [ -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" || 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' # First-run mode does not create a datasource. Keep database fields empty
DB_USERNAME=setup_pending # so hosting panels cannot mistake a placeholder for a local MySQL service.
DB_PASSWORD=setup_pending DB_URL=
DB_USERNAME=
DB_PASSWORD=
return 0 return 0
fi fi
if [ "$REINSTALL" = true ]; then if [ "$REINSTALL" = true ]; then
@@ -613,6 +615,7 @@ configure_database() {
database_host() { database_host() {
local endpoint host_port local endpoint host_port
[ -n "$DB_URL" ] || { printf ''; return; }
endpoint=${DB_URL#jdbc:mysql://} endpoint=${DB_URL#jdbc:mysql://}
host_port=${endpoint%%/*} host_port=${endpoint%%/*}
printf '%s' "${KAIDI_DB_HOST:-${host_port%%:*}}" printf '%s' "${KAIDI_DB_HOST:-${host_port%%:*}}"
@@ -620,6 +623,7 @@ database_host() {
database_port() { database_port() {
local endpoint host_port candidate local endpoint host_port candidate
[ -n "$DB_URL" ] || { printf ''; return; }
endpoint=${DB_URL#jdbc:mysql://} endpoint=${DB_URL#jdbc:mysql://}
host_port=${endpoint%%/*} host_port=${endpoint%%/*}
candidate=${host_port##*:} candidate=${host_port##*:}
@@ -629,6 +633,7 @@ database_port() {
database_name() { database_name() {
local endpoint name local endpoint name
[ -n "$DB_URL" ] || { printf ''; return; }
endpoint=${DB_URL#jdbc:mysql://} endpoint=${DB_URL#jdbc:mysql://}
name=${endpoint#*/} name=${endpoint#*/}
name=${name%%\?*} name=${name%%\?*}
+24
View File
@@ -24,6 +24,10 @@ mode_of() {
sed -n '/^port_is_listening()/,/^}/p' "$ROOT/deploy/install.sh" sed -n '/^port_is_listening()/,/^}/p' "$ROOT/deploy/install.sh"
sed -n '/^valid_app_port()/,/^}/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_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 '/^is_semver()/,/^}/p' "$ROOT/deploy/install.sh"
sed -n '/^write_env_file_preserving_unknown()/,/^}/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" sed -n '/^normalized_host_arch()/,/^}/p' "$ROOT/deploy/install.sh"
@@ -47,6 +51,14 @@ source "$WORK/helpers.sh"
export SETUP_WIZARD=true export SETUP_WIZARD=true
preflight_database || fail 'setup wizard database preflight returned a failure status' 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" mkdir -p "$WORK/mysql-bin"
cat > "$WORK/mysql-bin/mysql" <<'SH' cat > "$WORK/mysql-bin/mysql" <<'SH'
@@ -299,12 +311,24 @@ grep -Fq '[ "$APP_ROOT" = /opt/kaidi ]' "$ROOT/deploy/install.sh" \
|| fail 'installer no longer rejects unsupported custom roots' || fail 'installer no longer rejects unsupported custom roots'
grep -Fq '8.4.*) ;;' "$ROOT/deploy/install.sh" \ grep -Fq '8.4.*) ;;' "$ROOT/deploy/install.sh" \
|| fail 'installer no longer enforces MySQL 8.4.x' || 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" \ 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' || 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" \ 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' || fail 'installer does not reject a 32-bit host without the glibc loader'
grep -Fq 'preflight_runtime_commands' "$ROOT/deploy/install.sh" \ grep -Fq 'preflight_runtime_commands' "$ROOT/deploy/install.sh" \
|| fail 'installer does not validate required runtime commands' || 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" \ grep -Fq 'KAIDI_MYSQLDUMP_BIN "${KAIDI_MYSQLDUMP_BIN:-}"' "$ROOT/deploy/install.sh" \
|| fail 'installer does not preserve a custom mysqldump path for online updates' || fail 'installer does not preserve a custom mysqldump path for online updates'
# shellcheck disable=SC2016 # Match literal installer source. # shellcheck disable=SC2016 # Match literal installer source.