feat: support Baota manual deployment and managed updates
Release / release (push) Canceled after 0s
Release / release (push) Canceled after 0s
This commit is contained in:
Executable
+241
@@ -0,0 +1,241 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
umask 077
|
||||
|
||||
SERVICE_USER=kaidi
|
||||
SERVICE_GROUP=kaidi
|
||||
CONFIG_ROOT=/etc/kaidi
|
||||
STATE_ROOT=/var/lib/kaidi
|
||||
UPDATE_STATE_ROOT=/var/lib/kaidi-update
|
||||
LOG_ROOT=/var/log/kaidi
|
||||
UPDATER_ROOT=/opt/kaidi/bin
|
||||
PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
|
||||
RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest
|
||||
INIT_ARMED=false
|
||||
INIT_COMMITTED=false
|
||||
INIT_RELEASE_ROOT=
|
||||
INIT_APP_ROOT=
|
||||
|
||||
log() { printf '[kaidi-baota-init] %s\n' "$*"; }
|
||||
die() { printf '[kaidi-baota-init] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
rollback_initialization() {
|
||||
local rc=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [ "$rc" -ne 0 ] && [ "$INIT_ARMED" = true ] && [ "$INIT_COMMITTED" != true ]; then
|
||||
systemctl disable --now kaidi-update.path >/dev/null 2>&1 || true
|
||||
systemctl stop kaidi-update.service >/dev/null 2>&1 || true
|
||||
rm -f /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
rm -f "$CONFIG_ROOT/kaidi.env" "$CONFIG_ROOT/update.env" \
|
||||
"$CONFIG_ROOT/release-public.pem" "$UPDATER_ROOT/update.sh" \
|
||||
"$UPDATE_STATE_ROOT/status.json" /root/kaidi-first-login.txt
|
||||
rm -f "$INIT_APP_ROOT/current.next"
|
||||
if [ -n "$INIT_RELEASE_ROOT" ] \
|
||||
&& [ "$(readlink "$INIT_APP_ROOT/current" 2>/dev/null || true)" = "$INIT_RELEASE_ROOT" ]; then
|
||||
rm -f "$INIT_APP_ROOT/current"
|
||||
fi
|
||||
log "Initialization failed; files created by this attempt were rolled back and the command can be retried"
|
||||
fi
|
||||
exit "$rc"
|
||||
}
|
||||
trap rollback_initialization EXIT
|
||||
trap 'exit 129' HUP
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
|
||||
sha256_file() {
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
random_secret() {
|
||||
openssl rand -base64 36 | tr -d '\n/+=' | cut -c1-36
|
||||
}
|
||||
|
||||
write_env_file() {
|
||||
local output=$1 temporary name value escaped
|
||||
shift
|
||||
temporary="${output}.next.$$"
|
||||
: > "$temporary"
|
||||
while [ "$#" -gt 0 ]; do
|
||||
name=$1
|
||||
value=$2
|
||||
shift 2
|
||||
case "$value" in *$'\n'*|*$'\r'*) die "$name contains a line break" ;; esac
|
||||
escaped=${value//\\/\\\\}
|
||||
escaped=${escaped//\"/\\\"}
|
||||
printf '%s="%s"\n' "$name" "$escaped" >> "$temporary"
|
||||
done
|
||||
mv -f "$temporary" "$output"
|
||||
}
|
||||
|
||||
ensure_service_identity() {
|
||||
local existing_home nologin_path
|
||||
if ! getent group "$SERVICE_GROUP" >/dev/null 2>&1; then
|
||||
groupadd --system "$SERVICE_GROUP"
|
||||
fi
|
||||
if id "$SERVICE_USER" >/dev/null 2>&1; then
|
||||
existing_home=$(getent passwd "$SERVICE_USER" | awk -F: '{print $6}')
|
||||
[ "$existing_home" = "$STATE_ROOT" ] \
|
||||
|| die "Existing user $SERVICE_USER has unexpected home directory $existing_home"
|
||||
id -nG "$SERVICE_USER" | tr ' ' '\n' | grep -Fxq "$SERVICE_GROUP" \
|
||||
|| die "Existing user $SERVICE_USER is not a member of group $SERVICE_GROUP"
|
||||
return 0
|
||||
fi
|
||||
nologin_path=$(command -v nologin 2>/dev/null || true)
|
||||
[ -n "$nologin_path" ] || nologin_path=/usr/sbin/nologin
|
||||
[ -x "$nologin_path" ] || die "A nologin shell is required"
|
||||
useradd --system --gid "$SERVICE_GROUP" --home-dir "$STATE_ROOT" --shell "$nologin_path" "$SERVICE_USER"
|
||||
}
|
||||
|
||||
main() {
|
||||
local script_dir release_root releases_root app_root version app_port field_key setup_code setup_hash
|
||||
[ "$(id -u)" -eq 0 ] || die "Run with sudo or as root"
|
||||
[ "$(uname -s)" = Linux ] || die "Baota initialization only supports Linux"
|
||||
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] \
|
||||
|| die "systemd is required for the privileged background updater"
|
||||
for command in find openssl sha256sum; do
|
||||
command -v "$command" >/dev/null 2>&1 || die "$command is required"
|
||||
done
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
release_root=$(cd "$script_dir/.." && pwd -P)
|
||||
releases_root=$(dirname "$release_root")
|
||||
app_root=$(dirname "$releases_root")
|
||||
[ "$(basename "$releases_root")" = releases ] \
|
||||
|| die "Extract the release into APP_ROOT/releases/VERSION before initialization"
|
||||
[ "$app_root" = /www/wwwroot/kaidi ] \
|
||||
|| die "The supported Baota project root is /www/wwwroot/kaidi"
|
||||
INIT_RELEASE_ROOT=$release_root
|
||||
INIT_APP_ROOT=$app_root
|
||||
version=$(tr -d '\r\n' < "$release_root/VERSION")
|
||||
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.+-]+)?$ ]] \
|
||||
&& [ -s "$release_root/app.jar" ] && [ -s "$release_root/public/index.html" ] \
|
||||
|| die "The extracted release is incomplete"
|
||||
[ -x "$release_root/ops/update.sh" ] && [ -x "$release_root/ops/baota-start.sh" ] \
|
||||
&& [ -x "$release_root/ops/baota-init.sh" ] \
|
||||
|| die "The extracted operations scripts are incomplete"
|
||||
[ -s "$release_root/ops/release-public.pem" ] \
|
||||
|| die "The release public key is missing"
|
||||
[ "$(sha256_file "$release_root/ops/release-public.pem")" = "$PUBLIC_KEY_SHA256" ] \
|
||||
|| die "The release public-key fingerprint is invalid"
|
||||
[ ! -e "$CONFIG_ROOT/kaidi.env" ] && [ ! -e "$CONFIG_ROOT/update.env" ] \
|
||||
&& [ ! -e "$STATE_ROOT/setup/locked" ] \
|
||||
|| die "Kaidi is already initialized; run the published purge workflow before a fresh installation"
|
||||
! systemctl cat kaidi-finance.service >/dev/null 2>&1 \
|
||||
|| die "The old kaidi-finance.service still exists; run the published purge workflow first"
|
||||
[ ! -e /etc/systemd/system/kaidi-update.service ] \
|
||||
&& [ ! -e /etc/systemd/system/kaidi-update.path ] \
|
||||
|| die "Old Kaidi update units still exist; run the published purge workflow first"
|
||||
[ ! -e "$app_root/current" ] && [ ! -L "$app_root/current" ] \
|
||||
|| die "The Baota current release link already exists; run the published purge workflow first"
|
||||
|
||||
app_port=${1:-18080}
|
||||
[[ "$app_port" =~ ^[0-9]{1,5}$ ]] && [ "$app_port" -ge 1024 ] && [ "$app_port" -le 65535 ] \
|
||||
|| die "Port must be an integer between 1024 and 65535"
|
||||
|
||||
INIT_ARMED=true
|
||||
ensure_service_identity
|
||||
install -d -o root -g "$SERVICE_GROUP" -m 0750 "$app_root" "$releases_root" "$UPDATER_ROOT"
|
||||
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 \
|
||||
"$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 root -g "$SERVICE_GROUP" -m 0750 "$UPDATE_STATE_ROOT"
|
||||
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 "$UPDATE_STATE_ROOT/inbox"
|
||||
install -d -o root -g "$SERVICE_GROUP" -m 0750 "$CONFIG_ROOT"
|
||||
|
||||
chown -R root:"$SERVICE_GROUP" "$release_root"
|
||||
find "$release_root" -type d -exec chmod 0750 {} +
|
||||
find "$release_root" -type f -exec chmod 0640 {} +
|
||||
chmod 0750 "$release_root/ops/update.sh" "$release_root/ops/baota-start.sh" "$release_root/ops/baota-init.sh"
|
||||
ln -sfn "$release_root" "$app_root/current.next"
|
||||
mv -Tf "$app_root/current.next" "$app_root/current"
|
||||
|
||||
field_key=$(openssl rand -base64 32 | tr -d '\n')
|
||||
setup_code="KD-$(random_secret)"
|
||||
setup_hash=$(printf '%s' "$setup_code" | sha256sum | awk '{print $1}')
|
||||
write_env_file "$CONFIG_ROOT/kaidi.env" \
|
||||
SPRING_PROFILES_ACTIVE production \
|
||||
SERVER_ADDRESS 127.0.0.1 \
|
||||
SERVER_PORT "$app_port" \
|
||||
SESSION_COOKIE_SECURE false \
|
||||
DB_URL 'jdbc:mysql://setup.invalid:3306/kaidi_finance' \
|
||||
DB_USERNAME setup_pending \
|
||||
DB_PASSWORD setup_pending \
|
||||
FIELD_ENCRYPTION_KEY "$field_key" \
|
||||
FILE_STORAGE_ROOT "$STATE_ROOT/files" \
|
||||
FILE_STORAGE_TEMP "$STATE_ROOT/tmp" \
|
||||
FILE_SCANNER_ENABLED false \
|
||||
FINANCE_BOOTSTRAP_ENABLED false \
|
||||
FINANCE_BOOTSTRAP_PASSWORD '' \
|
||||
FINANCE_SETUP_ENABLED true \
|
||||
FINANCE_SETUP_TOKEN_SHA256 "$setup_hash" \
|
||||
FINANCE_SETUP_ENV_FILE "$STATE_ROOT/setup/application.env" \
|
||||
FINANCE_SETUP_MARKER_FILE "$STATE_ROOT/setup/locked" \
|
||||
FINANCE_SETUP_RESTART_AFTER_COMPLETE true \
|
||||
FINANCE_UPDATE_ENABLED true \
|
||||
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" \
|
||||
KAIDI_PID_FILE "$STATE_ROOT/kaidi.pid"
|
||||
chown root:"$SERVICE_GROUP" "$CONFIG_ROOT/kaidi.env"
|
||||
chmod 0640 "$CONFIG_ROOT/kaidi.env"
|
||||
|
||||
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' \
|
||||
"$version" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$UPDATE_STATE_ROOT/status.json"
|
||||
chmod 0644 "$UPDATE_STATE_ROOT/status.json"
|
||||
cat > /root/kaidi-first-login.txt <<EOF
|
||||
Project path: $app_root/current
|
||||
Start command: $app_root/current/ops/baota-start.sh
|
||||
Reverse proxy target: http://127.0.0.1:$app_port
|
||||
Setup URL: /setup
|
||||
Setup code: $setup_code
|
||||
Version: $version
|
||||
EOF
|
||||
chmod 0600 /root/kaidi-first-login.txt
|
||||
|
||||
INIT_COMMITTED=true
|
||||
log "Manual deployment is initialized for Kaidi Finance $version"
|
||||
log "Create the Baota Spring Boot project with $app_root/current"
|
||||
log "Baota environment variables: leave empty"
|
||||
log "Setup code: /root/kaidi-first-login.txt"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
+91
-4
@@ -1,21 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
umask 027
|
||||
|
||||
die() {
|
||||
printf '[kaidi-baota] ERROR: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
RELEASE_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
|
||||
RELEASE_ROOT=$(cd "$SCRIPT_DIR/.." && pwd -P)
|
||||
APP_JAR="$RELEASE_ROOT/app.jar"
|
||||
PUBLIC_INDEX="$RELEASE_ROOT/public/index.html"
|
||||
VERSION_FILE="$RELEASE_ROOT/VERSION"
|
||||
CONFIG_FILE=${KAIDI_CONFIG_FILE:-/etc/kaidi/kaidi.env}
|
||||
RUNTIME_ENV_FILE=${KAIDI_RUNTIME_ENV_FILE:-/var/lib/kaidi/setup/application.env}
|
||||
|
||||
decode_env_value() {
|
||||
local raw=$1 result='' char next index=0 length
|
||||
if [[ "$raw" == \"*\" && ${#raw} -ge 2 ]]; then
|
||||
raw=${raw:1:${#raw}-2}
|
||||
length=${#raw}
|
||||
while [ "$index" -lt "$length" ]; do
|
||||
char=${raw:index:1}
|
||||
if [ "$char" = "\\" ] && [ $((index + 1)) -lt "$length" ]; then
|
||||
next=${raw:index+1:1}
|
||||
if [ "$next" = "\\" ] || [ "$next" = '"' ]; then
|
||||
result+="$next"
|
||||
index=$((index + 2))
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
result+="$char"
|
||||
index=$((index + 1))
|
||||
done
|
||||
printf '%s' "$result"
|
||||
elif [[ "$raw" == \'*\' && ${#raw} -ge 2 ]]; then
|
||||
printf '%s' "${raw:1:${#raw}-2}"
|
||||
else
|
||||
printf '%s' "$raw"
|
||||
fi
|
||||
}
|
||||
|
||||
is_managed_env_name() {
|
||||
case "$1" in
|
||||
SPRING_PROFILES_ACTIVE|SERVER_ADDRESS|SERVER_PORT|SESSION_COOKIE_SECURE|\
|
||||
DB_URL|DB_USERNAME|DB_PASSWORD|FIELD_ENCRYPTION_KEY|\
|
||||
FILE_STORAGE_ROOT|FILE_STORAGE_TEMP|FILE_SCANNER_ENABLED|FILE_SCANNER_HOST|FILE_SCANNER_PORT|\
|
||||
FINANCE_BOOTSTRAP_ENABLED|FINANCE_BOOTSTRAP_PASSWORD|FINANCE_SETUP_ENABLED|\
|
||||
FINANCE_SETUP_TOKEN_SHA256|FINANCE_SETUP_ENV_FILE|FINANCE_SETUP_MARKER_FILE|\
|
||||
FINANCE_SETUP_RESTART_AFTER_COMPLETE|FINANCE_UPDATE_ENABLED|\
|
||||
UPDATE_RELEASE_BASE_URL|UPDATE_RELEASE_API_URL|UPDATE_RELEASE_TOKEN|\
|
||||
UPDATE_REQUEST_FILE|UPDATE_STATUS_FILE|UPDATE_CURRENT_VERSION_FILE|APP_VERSION|KAIDI_PID_FILE)
|
||||
return 0
|
||||
;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_env_file() {
|
||||
local file=$1 line name raw value size
|
||||
[ -e "$file" ] || return 0
|
||||
[ -f "$file" ] && [ ! -L "$file" ] || die "Configuration file is not a regular file: $file"
|
||||
[ -r "$file" ] || die "Startup user cannot read configuration file: $file"
|
||||
size=$(wc -c < "$file" | tr -d '[:space:]')
|
||||
[ "$size" -le 65536 ] || die "Configuration file is too large: $file"
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
case "$line" in ''|'#'*) continue ;; esac
|
||||
[[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] \
|
||||
|| die "Configuration file contains an invalid line: $file"
|
||||
name=${BASH_REMATCH[1]}
|
||||
raw=${BASH_REMATCH[2]}
|
||||
is_managed_env_name "$name" || continue
|
||||
[ -n "${!name:-}" ] && continue
|
||||
value=$(decode_env_value "$raw")
|
||||
printf -v "$name" '%s' "$value"
|
||||
export "${name?}"
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
[ -s "$APP_JAR" ] || die "app.jar is missing from $RELEASE_ROOT"
|
||||
[ -s "$PUBLIC_INDEX" ] || die "public/index.html is missing from $RELEASE_ROOT"
|
||||
[ -s "$VERSION_FILE" ] || die "VERSION is missing from $RELEASE_ROOT"
|
||||
|
||||
# The setup-generated runtime file has precedence over the base file. Non-empty
|
||||
# variables supplied by Baota have precedence over both; empty panel fields do not
|
||||
# mask the protected configuration written by the setup wizard.
|
||||
load_env_file "$RUNTIME_ENV_FILE"
|
||||
load_env_file "$CONFIG_FILE"
|
||||
|
||||
JAVA_BIN=${KAIDI_JAVA_BIN:-}
|
||||
if [ -z "$JAVA_BIN" ] && [ -n "${JAVA_HOME:-}" ]; then
|
||||
JAVA_BIN="$JAVA_HOME/bin/java"
|
||||
@@ -35,15 +108,16 @@ export SERVER_ADDRESS=${SERVER_ADDRESS:-127.0.0.1}
|
||||
export SERVER_PORT=${SERVER_PORT:-18080}
|
||||
export FINANCE_SETUP_ENABLED=${FINANCE_SETUP_ENABLED:-false}
|
||||
export FINANCE_BOOTSTRAP_ENABLED=${FINANCE_BOOTSTRAP_ENABLED:-false}
|
||||
export FINANCE_UPDATE_ENABLED=${FINANCE_UPDATE_ENABLED:-false}
|
||||
export FINANCE_UPDATE_ENABLED=${FINANCE_UPDATE_ENABLED:-true}
|
||||
export FILE_SCANNER_ENABLED=${FILE_SCANNER_ENABLED:-false}
|
||||
export FINANCE_STATIC_LOCATIONS=${FINANCE_STATIC_LOCATIONS:-file:$RELEASE_ROOT/public/}
|
||||
export UPDATE_CURRENT_VERSION_FILE=${UPDATE_CURRENT_VERSION_FILE:-$VERSION_FILE}
|
||||
export APP_VERSION=${APP_VERSION:-$(tr -d '\r\n' < "$VERSION_FILE")}
|
||||
export KAIDI_PID_FILE=${KAIDI_PID_FILE:-/var/lib/kaidi/kaidi.pid}
|
||||
|
||||
if [ "$FINANCE_SETUP_ENABLED" != true ]; then
|
||||
for name in DB_URL DB_USERNAME DB_PASSWORD FIELD_ENCRYPTION_KEY FILE_STORAGE_ROOT FILE_STORAGE_TEMP; do
|
||||
[ -n "${!name:-}" ] || die "$name must be configured in the Baota project environment"
|
||||
[ -n "${!name:-}" ] || die "$name is missing from the protected Kaidi configuration"
|
||||
done
|
||||
for directory in "$FILE_STORAGE_ROOT" "$FILE_STORAGE_TEMP"; do
|
||||
[ -d "$directory" ] || die "Storage directory $directory does not exist"
|
||||
@@ -51,5 +125,18 @@ if [ "$FINANCE_SETUP_ENABLED" != true ]; then
|
||||
done
|
||||
fi
|
||||
|
||||
case "$KAIDI_PID_FILE" in /*) ;; *) die "KAIDI_PID_FILE must be an absolute path" ;; esac
|
||||
[ -d "$(dirname "$KAIDI_PID_FILE")" ] || die "PID directory does not exist"
|
||||
[ -w "$(dirname "$KAIDI_PID_FILE")" ] || die "Startup user cannot write the PID directory"
|
||||
[ ! -L "$KAIDI_PID_FILE" ] || die "PID file must not be a symbolic link"
|
||||
PID_START_TIME=$(awk '{print $22}' "/proc/$$/stat" 2>/dev/null || true)
|
||||
[[ "$PID_START_TIME" =~ ^[0-9]+$ ]] || die "Process start time could not be read from /proc"
|
||||
PID_TEMP="${KAIDI_PID_FILE}.next.$$"
|
||||
printf '%s %s\n' "$$" "$PID_START_TIME" > "$PID_TEMP"
|
||||
chmod 0640 "$PID_TEMP"
|
||||
mv -f "$PID_TEMP" "$KAIDI_PID_FILE"
|
||||
|
||||
cd "$RELEASE_ROOT"
|
||||
exec "$JAVA_BIN" -XX:MaxRAMPercentage=70 -Dfile.encoding=UTF-8 -jar "$APP_JAR"
|
||||
exec "$JAVA_BIN" -XX:MaxRAMPercentage=70 -Dfile.encoding=UTF-8 \
|
||||
"-Dkaidi.release.path=$RELEASE_ROOT" "-Dkaidi.release.version=$APP_VERSION" \
|
||||
-jar "$APP_JAR"
|
||||
|
||||
+11
-14
@@ -1,14 +1,11 @@
|
||||
SPRING_PROFILES_ACTIVE=production
|
||||
SERVER_ADDRESS=127.0.0.1
|
||||
SERVER_PORT=18080
|
||||
SESSION_COOKIE_SECURE=false
|
||||
DB_URL=jdbc:mysql://MYSQL_HOST:3306/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC
|
||||
DB_USERNAME=MYSQL_USERNAME
|
||||
DB_PASSWORD=MYSQL_PASSWORD
|
||||
FIELD_ENCRYPTION_KEY=EXISTING_FIELD_ENCRYPTION_KEY
|
||||
FILE_STORAGE_ROOT=/var/lib/kaidi/files
|
||||
FILE_STORAGE_TEMP=/var/lib/kaidi/tmp
|
||||
FILE_SCANNER_ENABLED=false
|
||||
FINANCE_BOOTSTRAP_ENABLED=false
|
||||
FINANCE_SETUP_ENABLED=false
|
||||
FINANCE_UPDATE_ENABLED=false
|
||||
# Baota project environment variables are intentionally empty.
|
||||
# Do not paste database passwords or placeholder values into the panel.
|
||||
# ops/baota-start.sh loads the protected files created by ops/baota-init.sh:
|
||||
# /etc/kaidi/kaidi.env
|
||||
# /var/lib/kaidi/setup/application.env (after the setup wizard completes)
|
||||
#
|
||||
# Optional non-secret overrides may be added deliberately, for example:
|
||||
# SERVER_PORT=18080
|
||||
# SESSION_COOKIE_SECURE=true
|
||||
# Never override DB_URL, DB_USERNAME, DB_PASSWORD, FIELD_ENCRYPTION_KEY,
|
||||
# FINANCE_SETUP_TOKEN_SHA256, or the update paths from the panel.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwnPqkRpYUTBYbBaSnxBs
|
||||
LqDHFPrxX6DINIiCyRJ/0f1c0noRr1X0bJSpTAR+yUwzWYTqIKAhZ4NSouYjaxtE
|
||||
zuoW4cC6v+S/C310xM8qy+lE52GyFHpr7H6GqdQgxK2LmfS0jjCR9i8MEmi6QMk4
|
||||
zdDtI1NOl+D50vk1nrMryplyfvqDpyPA+fiqXVo2NrqX9iaamTVkq55INk3X451I
|
||||
u96/HBlSMw2EvoDPurzcjxV16swyJcuQQpBUbBmgp8i2enNyWONoBq/myYReTDem
|
||||
CbrD+DTvqa+tDMQ+DAL2wyPa8JdoGOLhcftYWsyfpgTq6olNSnqgUV+Gp+NYpWuk
|
||||
AxMi6TTDX4ByMkS65UGH2Opg483bxtl9bGCmjFfqFGO2NjxAsh6LaBApS2PNBKpu
|
||||
06+Qni/Eq7u847egw89bL1xwtHIZBWDiG4tQMqyqW3M5WqoYoaB7PcZnfyRVChA4
|
||||
pb+2yJll0cJ4RvKWeVrOAzb5JOn0BWjllm/csfwv9IeTAgMBAAE=
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -10,7 +10,6 @@ Type=oneshot
|
||||
User=root
|
||||
Group=root
|
||||
EnvironmentFile=/etc/kaidi/update.env
|
||||
EnvironmentFile=-/var/lib/kaidi/setup/application.env
|
||||
ExecStart=/opt/kaidi/bin/update.sh
|
||||
Restart=no
|
||||
Nice=10
|
||||
@@ -19,5 +18,5 @@ IOSchedulingPriority=6
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
ReadWritePaths=/opt/kaidi /var/lib/kaidi /var/lib/kaidi-update /var/log/kaidi /etc/systemd/system
|
||||
ReadWritePaths=/opt/kaidi /www/wwwroot/kaidi /var/lib/kaidi /var/lib/kaidi-update /var/log/kaidi /etc/systemd/system
|
||||
UMask=0077
|
||||
|
||||
+205
-21
@@ -19,6 +19,9 @@ RELEASE_TOKEN=${UPDATE_RELEASE_TOKEN:-}
|
||||
CACHE_ROOT=${KAIDI_UPDATE_CACHE_ROOT:-$STATE_ROOT/cache}
|
||||
SERVICE_NAME=${KAIDI_SERVICE_NAME:-kaidi-finance.service}
|
||||
UPDATE_PATH_NAME=${KAIDI_UPDATE_PATH_NAME:-kaidi-update.path}
|
||||
PROCESS_MANAGER=${KAIDI_PROCESS_MANAGER:-systemd}
|
||||
PID_FILE=${KAIDI_PID_FILE:-/var/lib/kaidi/kaidi.pid}
|
||||
RUNTIME_ENV_FILE=${KAIDI_RUNTIME_ENV_FILE:-/var/lib/kaidi/setup/application.env}
|
||||
SERVICE_USER=${KAIDI_SERVICE_USER:-kaidi}
|
||||
SERVICE_GROUP=${KAIDI_SERVICE_GROUP:-kaidi}
|
||||
HEALTH_URL=${KAIDI_HEALTH_URL:-http://127.0.0.1:18080/actuator/health}
|
||||
@@ -30,6 +33,7 @@ SYSTEMD_ROOT=${KAIDI_SYSTEMD_ROOT:-/etc/systemd/system}
|
||||
LOG_ROOT=${KAIDI_LOG_ROOT:-/var/log/kaidi}
|
||||
HEALTH_ATTEMPTS=${KAIDI_UPDATE_HEALTH_ATTEMPTS:-60}
|
||||
HEALTH_INTERVAL_SECONDS=${KAIDI_UPDATE_HEALTH_INTERVAL_SECONDS:-2}
|
||||
SHUTDOWN_ATTEMPTS=${KAIDI_UPDATE_SHUTDOWN_ATTEMPTS:-45}
|
||||
WORK_DIR=
|
||||
CACHE_TEMP=
|
||||
RELEASE_AUTH_HEADER_FILE=
|
||||
@@ -40,12 +44,85 @@ case "$HEALTH_ATTEMPTS:$HEALTH_INTERVAL_SECONDS" in
|
||||
*[!0-9:]* | :* | *:) printf '%s\n' "Update health-check settings must be non-negative integers" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$HEALTH_ATTEMPTS" -ge 1 ] || { printf '%s\n' "Update health-check attempts must be at least 1" >&2; exit 1; }
|
||||
case "$PROCESS_MANAGER" in
|
||||
systemd|baota) ;;
|
||||
*) printf '%s\n' "KAIDI_PROCESS_MANAGER must be systemd or baota" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$SHUTDOWN_ATTEMPTS" in
|
||||
''|*[!0-9]*) printf '%s\n' "KAIDI_UPDATE_SHUTDOWN_ATTEMPTS must be a positive integer" >&2; exit 1 ;;
|
||||
esac
|
||||
[ "$SHUTDOWN_ATTEMPTS" -ge 1 ] || { printf '%s\n' "KAIDI_UPDATE_SHUTDOWN_ATTEMPTS must be at least 1" >&2; exit 1; }
|
||||
case "$PID_FILE" in
|
||||
/*) ;;
|
||||
*) printf '%s\n' "KAIDI_PID_FILE must be an absolute path" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$RUNTIME_ENV_FILE" in
|
||||
/*) ;;
|
||||
*) printf '%s\n' "KAIDI_RUNTIME_ENV_FILE must be an absolute path" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
bootstrap_die() {
|
||||
printf '%s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
decode_runtime_env_value() {
|
||||
encoded_value=$1
|
||||
case "$encoded_value" in
|
||||
\"*\")
|
||||
encoded_value=${encoded_value#\"}
|
||||
encoded_value=${encoded_value%\"}
|
||||
printf '%s' "$encoded_value" | sed 's/\\\"/"/g; s/\\\\/\\/g'
|
||||
;;
|
||||
\'*\')
|
||||
encoded_value=${encoded_value#\'}
|
||||
encoded_value=${encoded_value%\'}
|
||||
printf '%s' "$encoded_value"
|
||||
;;
|
||||
*) printf '%s' "$encoded_value" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
load_runtime_database_env() {
|
||||
[ -e "$RUNTIME_ENV_FILE" ] || return 0
|
||||
[ -f "$RUNTIME_ENV_FILE" ] && [ ! -L "$RUNTIME_ENV_FILE" ] \
|
||||
|| bootstrap_die "Setup runtime environment must be a regular file"
|
||||
[ -r "$RUNTIME_ENV_FILE" ] \
|
||||
|| bootstrap_die "Setup runtime environment is not readable"
|
||||
runtime_size=$(wc -c < "$RUNTIME_ENV_FILE" | tr -d '[:space:]')
|
||||
[ "$runtime_size" -le 65536 ] \
|
||||
|| bootstrap_die "Setup runtime environment is too large"
|
||||
runtime_owner=$(stat -c '%u' "$RUNTIME_ENV_FILE" 2>/dev/null || true)
|
||||
service_uid=$(id -u "$SERVICE_USER" 2>/dev/null || true)
|
||||
[ "$runtime_owner" = 0 ] || [ "$runtime_owner" = "$service_uid" ] \
|
||||
|| bootstrap_die "Setup runtime environment has an unexpected owner"
|
||||
if find "$RUNTIME_ENV_FILE" -prune -perm /022 -print -quit | grep -q .; then
|
||||
bootstrap_die "Setup runtime environment must not be writable by group or other users"
|
||||
fi
|
||||
|
||||
while IFS= read -r runtime_line || [ -n "$runtime_line" ]; do
|
||||
case "$runtime_line" in ''|'#'*) continue ;; esac
|
||||
runtime_name=${runtime_line%%=*}
|
||||
[ "$runtime_name" != "$runtime_line" ] \
|
||||
|| bootstrap_die "Setup runtime environment contains an invalid line"
|
||||
runtime_raw=${runtime_line#*=}
|
||||
runtime_value=$(decode_runtime_env_value "$runtime_raw")
|
||||
case "$runtime_name" in
|
||||
KAIDI_DB_HOST) KAIDI_DB_HOST=$runtime_value; export KAIDI_DB_HOST ;;
|
||||
KAIDI_DB_PORT) KAIDI_DB_PORT=$runtime_value; export KAIDI_DB_PORT ;;
|
||||
KAIDI_DB_NAME) KAIDI_DB_NAME=$runtime_value; export KAIDI_DB_NAME ;;
|
||||
KAIDI_DB_USERNAME) KAIDI_DB_USERNAME=$runtime_value; export KAIDI_DB_USERNAME ;;
|
||||
KAIDI_DB_PASSWORD) KAIDI_DB_PASSWORD=$runtime_value; export KAIDI_DB_PASSWORD ;;
|
||||
esac
|
||||
done < "$RUNTIME_ENV_FILE"
|
||||
|
||||
case "${KAIDI_DB_PORT:-}" in
|
||||
''|*[!0-9]*) bootstrap_die "Setup runtime database port is invalid" ;;
|
||||
esac
|
||||
[ "$KAIDI_DB_PORT" -ge 1 ] && [ "$KAIDI_DB_PORT" -le 65535 ] \
|
||||
|| bootstrap_die "Setup runtime database port is invalid"
|
||||
}
|
||||
|
||||
status() {
|
||||
state=$1
|
||||
message=$2
|
||||
@@ -103,6 +180,7 @@ secure_release_tree() {
|
||||
find "$release_dir" -type d -exec chmod 0750 {} + || return 1
|
||||
find "$release_dir" -type f -exec chmod 0640 {} + || return 1
|
||||
chmod 0750 "$release_dir/ops/update.sh" "$release_dir/ops/baota-start.sh"
|
||||
[ ! -f "$release_dir/ops/baota-init.sh" ] || chmod 0750 "$release_dir/ops/baota-init.sh"
|
||||
}
|
||||
|
||||
verify_release_access() {
|
||||
@@ -111,12 +189,16 @@ verify_release_access() {
|
||||
runuser -u "$SERVICE_USER" -- sh -c \
|
||||
'cd "$1" && test -r app.jar && test -r public/index.html' sh "$release_dir" \
|
||||
|| return 1
|
||||
runuser -u "$SERVICE_USER" -- test -x "$APP_ROOT/runtime/java/bin/java" \
|
||||
|| return 1
|
||||
runuser -u "$SERVICE_USER" -- "$APP_ROOT/runtime/java/bin/java" -version >/dev/null 2>&1
|
||||
if [ "$PROCESS_MANAGER" = baota ]; then
|
||||
runuser -u "$SERVICE_USER" -- test -x "$release_dir/ops/baota-start.sh"
|
||||
else
|
||||
runuser -u "$SERVICE_USER" -- test -x "$APP_ROOT/runtime/java/bin/java" \
|
||||
&& runuser -u "$SERVICE_USER" -- "$APP_ROOT/runtime/java/bin/java" -version >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_update_layout
|
||||
load_runtime_database_env
|
||||
|
||||
# shellcheck disable=SC2329 # Invoked by the EXIT trap below.
|
||||
cleanup() {
|
||||
@@ -204,18 +286,103 @@ wait_for_health() {
|
||||
}
|
||||
|
||||
verify_app_surface() {
|
||||
expected_release=${1:-}
|
||||
wait_for_health "$HEALTH_URL" || return 1
|
||||
index_file=$(mktemp "$STATE_ROOT/work/public-index.XXXXXX")
|
||||
if curl -fsS "$APP_INDEX_URL" -o "$index_file" \
|
||||
&& grep -Eiq '<!doctype|<html' "$index_file"; then
|
||||
rm -f "$index_file"
|
||||
if [ "$PROCESS_MANAGER" = baota ] && [ -n "$expected_release" ]; then
|
||||
running_release=$(baota_process_release) || return 1
|
||||
[ "$running_release" = "$expected_release" ] || return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
rm -f "$index_file"
|
||||
return 1
|
||||
}
|
||||
|
||||
baota_process_record() {
|
||||
[ -f "$PID_FILE" ] && [ ! -L "$PID_FILE" ] || return 1
|
||||
pid_record=$(cat "$PID_FILE" 2>/dev/null || true)
|
||||
pid=${pid_record%% *}
|
||||
start_time=${pid_record#* }
|
||||
[ "$pid" != "$pid_record" ] || return 1
|
||||
case "$pid:$start_time" in
|
||||
*[!0-9:]*) return 1 ;;
|
||||
esac
|
||||
[ "$pid" -ne "$$" ] && [ -r "/proc/$pid/cmdline" ] || return 1
|
||||
actual_start_time=$(awk '{print $22}' "/proc/$pid/stat" 2>/dev/null || true)
|
||||
[ -n "$actual_start_time" ] && [ "$actual_start_time" = "$start_time" ] || return 1
|
||||
owner_uid=$(stat -c '%u' "/proc/$pid" 2>/dev/null || true)
|
||||
service_uid=$(id -u "$SERVICE_USER" 2>/dev/null || true)
|
||||
[ -n "$owner_uid" ] && [ "$owner_uid" = "$service_uid" ] || return 1
|
||||
process_args=$(mktemp "$STATE_ROOT/work/process-args.XXXXXX") || return 1
|
||||
if ! tr '\000' '\n' < "/proc/$pid/cmdline" > "$process_args" 2>/dev/null; then
|
||||
rm -f "$process_args"
|
||||
return 1
|
||||
fi
|
||||
release_path=$(sed -n 's/^-Dkaidi\.release\.path=//p' "$process_args")
|
||||
release_path_count=$(sed -n '/^-Dkaidi\.release\.path=/p' "$process_args" | wc -l | tr -d '[:space:]')
|
||||
jar_path=$(awk 'previous == "-jar" { print; exit } { previous = $0 }' "$process_args")
|
||||
rm -f "$process_args"
|
||||
[ "$release_path_count" -eq 1 ] || return 1
|
||||
case "$release_path" in
|
||||
"$APP_ROOT"/releases/*) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
[ -d "$release_path" ] && [ ! -L "$release_path" ] \
|
||||
&& [ "$jar_path" = "$release_path/app.jar" ] && [ -f "$jar_path" ] \
|
||||
|| return 1
|
||||
printf '%s\t%s\n' "$pid" "$release_path"
|
||||
}
|
||||
|
||||
baota_process_pid() {
|
||||
baota_process_record | cut -f1
|
||||
}
|
||||
|
||||
baota_process_release() {
|
||||
baota_process_record | cut -f2-
|
||||
}
|
||||
|
||||
restart_application() {
|
||||
allow_missing=${1:-false}
|
||||
if [ "$PROCESS_MANAGER" = systemd ]; then
|
||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||
systemctl restart "$SERVICE_NAME" || systemctl start "$SERVICE_NAME"
|
||||
return
|
||||
fi
|
||||
|
||||
old_pid=$(baota_process_pid) || {
|
||||
[ "$allow_missing" = true ] && return 0
|
||||
return 1
|
||||
}
|
||||
kill -TERM "$old_pid" 2>/dev/null || return 1
|
||||
attempts=0
|
||||
while [ "$attempts" -lt "$SHUTDOWN_ATTEMPTS" ]; do
|
||||
if ! kill -0 "$old_pid" 2>/dev/null; then
|
||||
current_pid_record=$(cat "$PID_FILE" 2>/dev/null || true)
|
||||
case "$current_pid_record" in "$old_pid "*) rm -f "$PID_FILE" ;; esac
|
||||
return 0
|
||||
fi
|
||||
attempts=$((attempts + 1))
|
||||
sleep 1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
application_state() {
|
||||
if [ "$PROCESS_MANAGER" = systemd ]; then
|
||||
systemctl is-active "$SERVICE_NAME" 2>/dev/null || printf unknown
|
||||
elif baota_process_pid >/dev/null 2>&1; then
|
||||
printf active
|
||||
else
|
||||
printf stopped
|
||||
fi
|
||||
}
|
||||
|
||||
backup_database() {
|
||||
DATABASE_BACKUP=
|
||||
[ "${KAIDI_SKIP_DB_BACKUP:-false}" = "true" ] && return
|
||||
mkdir -p "$BACKUP_ROOT"
|
||||
chmod 0700 "$BACKUP_ROOT"
|
||||
@@ -255,6 +422,7 @@ backup_database() {
|
||||
fi
|
||||
chmod 0600 "$backup_tmp"
|
||||
mv -f "$backup_tmp" "$backup"
|
||||
DATABASE_BACKUP=$backup
|
||||
rm -f "$dump_file"
|
||||
find "$BACKUP_ROOT" -type f -name 'mysql-*.sql.gz' -printf '%T@ %p\n' \
|
||||
| sort -nr | awk 'NR > 5 {sub(/^[^ ]+ /, ""); print}' | xargs -r rm -f
|
||||
@@ -321,18 +489,22 @@ restore_managed_file() {
|
||||
backup_operations() {
|
||||
OPS_BACKUP="$ACTIVE_TRANSACTION/operations-backup"
|
||||
mkdir -p "$OPS_BACKUP"
|
||||
backup_managed_file "$UPDATER_PATH" update.sh \
|
||||
&& backup_managed_file "$SYSTEMD_ROOT/kaidi-finance.service" kaidi-finance.service \
|
||||
&& backup_managed_file "$SYSTEMD_ROOT/kaidi-update.service" kaidi-update.service \
|
||||
backup_managed_file "$UPDATER_PATH" update.sh || return 1
|
||||
if [ "$PROCESS_MANAGER" = systemd ]; then
|
||||
backup_managed_file "$SYSTEMD_ROOT/kaidi-finance.service" kaidi-finance.service || return 1
|
||||
fi
|
||||
backup_managed_file "$SYSTEMD_ROOT/kaidi-update.service" kaidi-update.service \
|
||||
&& backup_managed_file "$SYSTEMD_ROOT/kaidi-update.path" kaidi-update.path
|
||||
}
|
||||
|
||||
apply_operations() {
|
||||
SYSTEMD_OPERATIONS_CHANGED=false
|
||||
install_operation "$RELEASE_DIR/ops/update.sh" "$UPDATER_PATH" 0755 false \
|
||||
&& install_operation "$RELEASE_DIR/ops/kaidi-finance.service" \
|
||||
"$SYSTEMD_ROOT/kaidi-finance.service" 0644 true \
|
||||
&& install_operation "$RELEASE_DIR/ops/kaidi-update.service" \
|
||||
install_operation "$RELEASE_DIR/ops/update.sh" "$UPDATER_PATH" 0755 false || return 1
|
||||
if [ "$PROCESS_MANAGER" = systemd ]; then
|
||||
install_operation "$RELEASE_DIR/ops/kaidi-finance.service" \
|
||||
"$SYSTEMD_ROOT/kaidi-finance.service" 0644 true || return 1
|
||||
fi
|
||||
install_operation "$RELEASE_DIR/ops/kaidi-update.service" \
|
||||
"$SYSTEMD_ROOT/kaidi-update.service" 0644 true \
|
||||
&& install_operation "$RELEASE_DIR/ops/kaidi-update.path" \
|
||||
"$SYSTEMD_ROOT/kaidi-update.path" 0644 true \
|
||||
@@ -343,7 +515,9 @@ apply_operations() {
|
||||
restore_operations() {
|
||||
restore_failed=0
|
||||
restore_managed_file "$UPDATER_PATH" update.sh || restore_failed=1
|
||||
restore_managed_file "$SYSTEMD_ROOT/kaidi-finance.service" kaidi-finance.service || restore_failed=1
|
||||
if [ "$PROCESS_MANAGER" = systemd ]; then
|
||||
restore_managed_file "$SYSTEMD_ROOT/kaidi-finance.service" kaidi-finance.service || restore_failed=1
|
||||
fi
|
||||
restore_managed_file "$SYSTEMD_ROOT/kaidi-update.service" kaidi-update.service || restore_failed=1
|
||||
restore_managed_file "$SYSTEMD_ROOT/kaidi-update.path" kaidi-update.path || restore_failed=1
|
||||
systemctl daemon-reload || restore_failed=1
|
||||
@@ -378,6 +552,7 @@ rollback_active_transaction() {
|
||||
OPS_BACKUP="$ACTIVE_TRANSACTION/operations-backup"
|
||||
link_restored=true
|
||||
operations_restored=true
|
||||
application_restarted=true
|
||||
|
||||
if [ -n "$previous_target" ] && [ -d "$previous_target" ]; then
|
||||
if ! ln -sfn "$previous_target" "$APP_ROOT/current.next" \
|
||||
@@ -388,15 +563,15 @@ rollback_active_transaction() {
|
||||
link_restored=false
|
||||
fi
|
||||
restore_operations || operations_restored=false
|
||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||
systemctl restart "$SERVICE_NAME" || systemctl start "$SERVICE_NAME" || true
|
||||
if [ "$link_restored" = true ] && verify_app_surface; then
|
||||
restart_application true || application_restarted=false
|
||||
if [ "$link_restored" = true ] && [ "$application_restarted" = true ] \
|
||||
&& verify_app_surface "$previous_target"; then
|
||||
remove_failed_release "$failed_release"
|
||||
rm -rf "$ACTIVE_TRANSACTION"
|
||||
if [ "$operations_restored" = true ]; then
|
||||
fail "$reason; previous release was restored and verified"
|
||||
rm -rf "$ACTIVE_TRANSACTION"
|
||||
fail "$reason; previous application release was restored and verified; database backup was retained"
|
||||
fi
|
||||
fail "$reason; previous release is running, but operations restoration requires manual review"
|
||||
fail "$reason; previous application release is running, but operations restoration requires manual review; transaction evidence was retained"
|
||||
fi
|
||||
TERMINAL_STATUS_WRITTEN=true
|
||||
status FAILED "$reason; rollback is incomplete and manual recovery is required" "${TARGET_VERSION:-}"
|
||||
@@ -430,6 +605,10 @@ recover_interrupted_transaction() {
|
||||
validate_release_operations() {
|
||||
sh -n "$WORK_DIR/extracted/ops/update.sh" || return 1
|
||||
bash -n "$WORK_DIR/extracted/ops/baota-start.sh" || return 1
|
||||
bash -n "$WORK_DIR/extracted/ops/baota-init.sh" || return 1
|
||||
[ -s "$WORK_DIR/extracted/ops/release-public.pem" ] \
|
||||
&& cmp -s "$WORK_DIR/extracted/ops/release-public.pem" "$PUBLIC_KEY" \
|
||||
|| return 1
|
||||
command -v systemd-analyze >/dev/null 2>&1 || return 1
|
||||
systemd-analyze verify \
|
||||
"$WORK_DIR/extracted/ops/kaidi-finance.service" \
|
||||
@@ -555,6 +734,9 @@ fi
|
||||
[ -s "$WORK_DIR/extracted/public/index.html" ] || fail "Release frontend is missing"
|
||||
[ "$(cat "$WORK_DIR/extracted/VERSION" 2>/dev/null)" = "$TARGET_VERSION" ] || fail "Release version file mismatch"
|
||||
[ -x "$WORK_DIR/extracted/ops/update.sh" ] || fail "Release updater is missing"
|
||||
[ -x "$WORK_DIR/extracted/ops/baota-start.sh" ] || fail "Release Baota launcher is missing"
|
||||
[ -x "$WORK_DIR/extracted/ops/baota-init.sh" ] || fail "Release Baota initializer is missing"
|
||||
[ -s "$WORK_DIR/extracted/ops/release-public.pem" ] || fail "Release public key is missing"
|
||||
[ -s "$WORK_DIR/extracted/ops/kaidi-finance.service" ] || fail "Release application unit is missing"
|
||||
[ -s "$WORK_DIR/extracted/ops/kaidi-update.service" ] || fail "Release updater unit is missing"
|
||||
[ -s "$WORK_DIR/extracted/ops/kaidi-update.path" ] || fail "Release updater path unit is missing"
|
||||
@@ -605,6 +787,7 @@ write_transaction_value phase PREPARED
|
||||
|
||||
status BACKING_UP "Backing up database before installing $TARGET_VERSION" "$TARGET_VERSION"
|
||||
backup_database
|
||||
[ -z "${DATABASE_BACKUP:-}" ] || write_transaction_value database-backup "$DATABASE_BACKUP"
|
||||
if ! backup_operations; then
|
||||
remove_failed_release "$RELEASE_DIR"
|
||||
rm -rf "$ACTIVE_TRANSACTION"
|
||||
@@ -624,9 +807,10 @@ write_transaction_value phase APP_SWITCHED
|
||||
|
||||
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
||||
write_transaction_value phase HEALTH_CHECKING
|
||||
systemctl reset-failed "$SERVICE_NAME" >/dev/null 2>&1 || true
|
||||
systemctl restart "$SERVICE_NAME" || systemctl start "$SERVICE_NAME" || true
|
||||
if verify_app_surface; then
|
||||
if ! restart_application; then
|
||||
rollback_active_transaction "Release application restart failed"
|
||||
fi
|
||||
if verify_app_surface "$RELEASE_DIR"; then
|
||||
success_message="Release $TARGET_VERSION is running"
|
||||
if ! systemctl start "$UPDATE_PATH_NAME" >/dev/null 2>&1; then
|
||||
success_message="$success_message; automatic update watcher could not be started"
|
||||
@@ -639,7 +823,7 @@ if verify_app_surface; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
service_state=$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)
|
||||
service_state=$(application_state 2>/dev/null || true)
|
||||
[ -n "$service_state" ] || service_state=unknown
|
||||
rollback_active_transaction \
|
||||
"Release health or application-surface verification failed (service state: $service_state)"
|
||||
|
||||
Reference in New Issue
Block a user