This commit is contained in:
Executable
+221
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
fail() {
|
||||
printf 'Install fixture failed: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Load only pure helper functions. The installer itself must never run in this fixture.
|
||||
{
|
||||
sed -n '/^decode_env_value()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^read_env_file()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^read_existing_env()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^read_setup_env()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^read_reinstall_env()/,/^}/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 '/^azul_arch()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^sha256_file()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^prepare_java()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^download_release_url()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^release_asset_url()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
sed -n '/^download_release_asset()/,/^}/p' "$ROOT/deploy/install.sh"
|
||||
} > "$WORK/helpers.sh"
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
source "$WORK/helpers.sh"
|
||||
|
||||
# shellcheck disable=SC2034 # Referenced by the extracted installer helper.
|
||||
REINSTALL=true
|
||||
# shellcheck disable=SC2034 # Referenced by the extracted installer helper.
|
||||
WORK_DIR=$WORK
|
||||
# shellcheck disable=SC2034 # Referenced by the extracted installer helper.
|
||||
CONFIG_ROOT=$WORK
|
||||
STATE_ROOT=$WORK/state
|
||||
mkdir -p "$STATE_ROOT/setup"
|
||||
env_file="$WORK/kaidi.env"
|
||||
cat > "$env_file" <<'ENV'
|
||||
# operator-owned values must survive a reinstall
|
||||
FINANCE_SESSION_ABSOLUTE_TIMEOUT="45m"
|
||||
FILE_SCANNER_HOST="scanner.internal"
|
||||
FILE_SCANNER_PORT="3310"
|
||||
DB_URL="jdbc:mysql://old/kaidi_finance"
|
||||
APP_VERSION="old"
|
||||
ENV
|
||||
|
||||
# shellcheck disable=SC2016 # These shell characters must remain literal data.
|
||||
literal_password='pw$HOME-$(touch PLACEHOLDER)-`id`-back\slash-"quote"'
|
||||
literal_password=${literal_password/PLACEHOLDER/$WORK\/executed}
|
||||
|
||||
write_env_file_preserving_unknown "$env_file" \
|
||||
DB_URL 'jdbc:mysql://new/kaidi_finance' \
|
||||
DB_PASSWORD "$literal_password" \
|
||||
APP_VERSION '1.0.0-preview.1'
|
||||
|
||||
grep -qx 'FINANCE_SESSION_ABSOLUTE_TIMEOUT="45m"' "$env_file" \
|
||||
|| fail 'reinstall removed the custom session timeout'
|
||||
grep -qx 'FILE_SCANNER_HOST="scanner.internal"' "$env_file" \
|
||||
|| fail 'reinstall removed the custom scanner host'
|
||||
grep -qx 'DB_URL="jdbc:mysql://new/kaidi_finance"' "$env_file" \
|
||||
|| fail 'reinstall did not replace the managed database URL'
|
||||
[ "$(grep -c '^DB_URL=' "$env_file")" -eq 1 ] || fail 'reinstall duplicated a managed key'
|
||||
[ "$(grep -c '^APP_VERSION=' "$env_file")" -eq 1 ] || fail 'reinstall duplicated the application version'
|
||||
[ "$(read_existing_env DB_PASSWORD)" = "$literal_password" ] \
|
||||
|| fail 'reinstall changed literal shell characters in the database password'
|
||||
[ ! -e "$WORK/executed" ] || fail 'reinstall executed database password content'
|
||||
|
||||
cat > "$STATE_ROOT/setup/application.env" <<'ENV'
|
||||
DB_URL="jdbc:mysql://runtime/kaidi_finance"
|
||||
DB_USERNAME="runtime-user"
|
||||
DB_PASSWORD="runtime-password"
|
||||
ENV
|
||||
grep -qx 'jdbc:mysql://runtime/kaidi_finance' <(read_reinstall_env DB_URL) \
|
||||
|| fail 'reinstall did not prefer the completed setup runtime database URL'
|
||||
grep -qx 'runtime-user' <(read_reinstall_env DB_USERNAME) \
|
||||
|| fail 'reinstall did not prefer the completed setup runtime database user'
|
||||
|
||||
for version in 0.0.0 1.2.3-alpha- 1.2.3--alpha 1.2.3-alpha+build.07; do
|
||||
is_semver "$version" || fail "installer rejected valid SemVer $version"
|
||||
"$ROOT/scripts/check-semver.sh" "$version" || fail "release workflow rejected valid SemVer $version"
|
||||
done
|
||||
for version in 01.2.3 1.02.3 1.2.03 1.2.3-01 1.2.3-alpha..1; do
|
||||
! is_semver "$version" || fail "installer accepted invalid SemVer $version"
|
||||
! "$ROOT/scripts/check-semver.sh" "$version" >/dev/null 2>&1 \
|
||||
|| fail "release workflow accepted invalid SemVer $version"
|
||||
done
|
||||
|
||||
ARCH_FIXTURE=
|
||||
uname() {
|
||||
if [ "${1:-}" = -m ]; then
|
||||
printf '%s\n' "$ARCH_FIXTURE"
|
||||
else
|
||||
command uname "$@"
|
||||
fi
|
||||
}
|
||||
die() {
|
||||
return 1
|
||||
}
|
||||
for arch in i386 i486 i586 i686; do
|
||||
ARCH_FIXTURE=$arch
|
||||
[ "$(azul_arch)" = i686 ] || fail "$arch did not map to the Azul i686 runtime"
|
||||
done
|
||||
|
||||
mkdir -p "$WORK/fake-jre/bin"
|
||||
cat > "$WORK/fake-jre/bin/java" <<'JAVA'
|
||||
#!/usr/bin/env sh
|
||||
printf 'openjdk version "17-fixture"\n' >&2
|
||||
JAVA
|
||||
chmod 0755 "$WORK/fake-jre/bin/java"
|
||||
tar -czf "$WORK/java-fixture.tar.gz" -C "$WORK" fake-jre
|
||||
java_fixture_sha=$(sha256sum "$WORK/java-fixture.tar.gz" | awk '{print $1}')
|
||||
cat > "$WORK/java-list.json" <<'JSON'
|
||||
[{"name":"zulu17-fixture-linux_i686.tar.gz","package_uuid":"fixture-package"}]
|
||||
JSON
|
||||
cat > "$WORK/java-detail.json" <<JSON
|
||||
{"download_url":"https://cdn.azul.com/zulu/bin/zulu17-fixture-linux_i686.tar.gz",
|
||||
"sha256_hash":"$java_fixture_sha"}
|
||||
JSON
|
||||
download() {
|
||||
case "$1" in
|
||||
*'/packages/?'*) cp "$WORK/java-list.json" "$2" ;;
|
||||
*/packages/fixture-package) cp "$WORK/java-detail.json" "$2" ;;
|
||||
https://cdn.azul.com/*) cp "$WORK/java-fixture.tar.gz" "$2" ;;
|
||||
*) fail "unexpected Java fixture URL: $1" ;;
|
||||
esac
|
||||
}
|
||||
ARCH_FIXTURE=i686
|
||||
prepare_java >/dev/null 2>&1
|
||||
[ -x "$JAVA_STAGED_DIR/bin/java" ] || fail 'verified i686 Java runtime was not staged'
|
||||
|
||||
export RELEASE_API_URL=https://gitea.fixture.invalid/api/v1/repos/ERP-Team/kaidi/releases/latest
|
||||
RELEASE_TOKEN=fixture-read-only-token
|
||||
RELEASE_AUTH_HEADER_FILE=$WORK/release-auth-header
|
||||
printf 'Authorization: token %s\n' "$RELEASE_TOKEN" > "$RELEASE_AUTH_HEADER_FILE"
|
||||
chmod 0600 "$RELEASE_AUTH_HEADER_FILE"
|
||||
cat > "$WORK/release-api.json" <<'JSON'
|
||||
{"assets":[
|
||||
{"name":"release-manifest.json","browser_download_url":"https://gitea.fixture.invalid/assets/release-manifest.json"}
|
||||
]}
|
||||
JSON
|
||||
printf 'signed manifest fixture\n' > "$WORK/release-manifest.fixture"
|
||||
: > "$WORK/release-curl.log"
|
||||
curl() {
|
||||
local output='' url='' header_file='' argument=''
|
||||
printf '%s\n' "$*" >> "$WORK/release-curl.log"
|
||||
for argument in "$@"; do
|
||||
case "$argument" in
|
||||
@*) header_file=${argument#@} ;;
|
||||
https://*) url=$argument ;;
|
||||
esac
|
||||
done
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-o) shift; output=$1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
[ "$header_file" = "$RELEASE_AUTH_HEADER_FILE" ] || return 90
|
||||
grep -Fqx "Authorization: token $RELEASE_TOKEN" "$header_file" || return 91
|
||||
[ "$url" = https://gitea.fixture.invalid/assets/release-manifest.json ] || return 92
|
||||
cp "$WORK/release-manifest.fixture" "$output"
|
||||
}
|
||||
[ "$(release_asset_url release-manifest.json)" = \
|
||||
https://gitea.fixture.invalid/assets/release-manifest.json ] \
|
||||
|| fail 'installer did not resolve the private Gitea release asset'
|
||||
download_release_asset release-manifest.json "$WORK/downloaded-manifest.json"
|
||||
cmp -s "$WORK/release-manifest.fixture" "$WORK/downloaded-manifest.json" \
|
||||
|| fail 'installer did not download the private Gitea release asset'
|
||||
! grep -Fq "$RELEASE_TOKEN" "$WORK/release-curl.log" \
|
||||
|| fail 'installer leaked the private Gitea token into curl process arguments'
|
||||
! grep -Fq -- '--location' "$WORK/release-curl.log" \
|
||||
|| fail 'installer allowed authenticated Gitea redirects'
|
||||
|
||||
jq '.assets[0].browser_download_url = "https://assets.fixture.invalid/release-manifest.json"' \
|
||||
"$WORK/release-api.json" > "$WORK/release-api.cross-origin.json"
|
||||
mv "$WORK/release-api.cross-origin.json" "$WORK/release-api.json"
|
||||
if release_asset_url release-manifest.json >/dev/null 2>&1; then
|
||||
fail 'installer accepted a cross-origin Gitea release asset'
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
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'
|
||||
# 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.
|
||||
grep -Fq 'download "$api" "$java_metadata"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer applies the restricted downloader to Java metadata'
|
||||
# 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'
|
||||
# shellcheck disable=SC2016 # Match literal installer source.
|
||||
grep -Fq '[ "$actual_sha256" = "$java_sha256" ]' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer verifies the Java runtime SHA-256'
|
||||
grep -Fq 'https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer default release source is not the private Gitea latest API'
|
||||
grep -Fq 'KAIDI_RELEASE_TOKEN_FILE' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer supports a protected initial Gitea token file'
|
||||
grep -Fq 'KAIDI_SETUP_WIZARD' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer supports first-run setup mode'
|
||||
grep -Fq 'FINANCE_SETUP_TOKEN_SHA256' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'installer no longer writes the one-time setup-code hash'
|
||||
grep -Fq 'EnvironmentFile=-/var/lib/kaidi/setup/application.env' \
|
||||
"$ROOT/deploy/systemd/kaidi-finance.service" \
|
||||
|| fail 'application service no longer loads the setup-completion environment'
|
||||
grep -Fq 'EnvironmentFile=-/var/lib/kaidi/setup/application.env' \
|
||||
"$ROOT/deploy/systemd/kaidi-update.service" \
|
||||
|| fail 'update service no longer loads setup database overrides'
|
||||
# shellcheck disable=SC2016 # Match the literal installer source.
|
||||
grep -Fq 'chown root:kaidi "$UPDATE_STATE_ROOT"' "$ROOT/deploy/install.sh" \
|
||||
|| fail 'update state parent is not group-accessible to the application user'
|
||||
grep -Fq 'KAIDI_SETUP_WIZARD=true' "$ROOT/README.md" \
|
||||
|| fail 'README does not document the setup-wizard install path'
|
||||
|
||||
printf 'Install configuration, private Gitea, i686, setup wizard, and MySQL 8.4 fixtures passed\n'
|
||||
Reference in New Issue
Block a user