This commit is contained in:
@@ -95,7 +95,7 @@ main() {
|
||||
[ "$(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
|
||||
for command in find openssl setsid sha256sum; do
|
||||
command -v "$command" >/dev/null 2>&1 || die "$command is required"
|
||||
done
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ Description=Watch for Kaidi Finance update requests
|
||||
[Path]
|
||||
PathChanged=/var/lib/kaidi-update/inbox
|
||||
PathExists=/var/lib/kaidi-update/inbox/request.json
|
||||
PathExists=/var/lib/kaidi-update/processing/request.json
|
||||
PathExists=/var/lib/kaidi-update/transactions/active
|
||||
Unit=kaidi-update.service
|
||||
|
||||
[Install]
|
||||
|
||||
+241
-11
@@ -12,6 +12,7 @@ FAILED_REQUEST_ROOT=${KAIDI_UPDATE_FAILED_ROOT:-$STATE_ROOT/failed}
|
||||
TRANSACTION_ROOT=${KAIDI_UPDATE_TRANSACTION_ROOT:-$STATE_ROOT/transactions}
|
||||
ACTIVE_TRANSACTION=$TRANSACTION_ROOT/active
|
||||
STATUS_FILE=${UPDATE_STATUS_FILE:-$STATE_ROOT/status.json}
|
||||
EVENT_LOG_FILE=${UPDATE_EVENT_LOG_FILE:-$STATE_ROOT/events.jsonl}
|
||||
PUBLIC_KEY=${UPDATE_PUBLIC_KEY:-/etc/kaidi/release-public.pem}
|
||||
RELEASE_BASE_URL=${UPDATE_RELEASE_BASE_URL:-}
|
||||
RELEASE_API_URL=${UPDATE_RELEASE_API_URL:-}
|
||||
@@ -39,6 +40,10 @@ CACHE_TEMP=
|
||||
RELEASE_AUTH_HEADER_FILE=
|
||||
TARGET_VERSION=
|
||||
TERMINAL_STATUS_WRITTEN=false
|
||||
DOWNLOAD_PID=
|
||||
DOWNLOAD_PGID=
|
||||
LAST_DOWNLOAD_SPEED=0
|
||||
SURFACE_FAILURE=
|
||||
|
||||
case "$HEALTH_ATTEMPTS:$HEALTH_INTERVAL_SECONDS" in
|
||||
*[!0-9:]* | :* | *:) printf '%s\n' "Update health-check settings must be non-negative integers" >&2; exit 1 ;;
|
||||
@@ -95,7 +100,8 @@ load_runtime_database_env() {
|
||||
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)
|
||||
runtime_owner=$(stat -c '%u' "$RUNTIME_ENV_FILE" 2>/dev/null \
|
||||
|| stat -f '%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"
|
||||
@@ -116,6 +122,7 @@ load_runtime_database_env() {
|
||||
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 ;;
|
||||
DB_URL) KAIDI_DB_URL=$runtime_value; export KAIDI_DB_URL ;;
|
||||
esac
|
||||
done < "$RUNTIME_ENV_FILE"
|
||||
|
||||
@@ -126,20 +133,84 @@ load_runtime_database_env() {
|
||||
|| bootstrap_die "Setup runtime database port is invalid"
|
||||
}
|
||||
|
||||
validate_runtime_database_config() {
|
||||
[ -e "$RUNTIME_ENV_FILE" ] || return 0
|
||||
[ -n "${KAIDI_DB_URL:-}" ] \
|
||||
|| fail "Database configuration is invalid: DB_URL is missing; application was not restarted"
|
||||
[ -n "${KAIDI_DB_HOST:-}" ] && [ -n "${KAIDI_DB_NAME:-}" ] \
|
||||
|| fail "Database configuration is invalid: database host and name are missing; application was not restarted"
|
||||
expected_url_prefix="jdbc:mysql://${KAIDI_DB_HOST}:${KAIDI_DB_PORT}/${KAIDI_DB_NAME}"
|
||||
case "$KAIDI_DB_URL" in
|
||||
"$expected_url_prefix"|"$expected_url_prefix"\?*) ;;
|
||||
*) fail "Database configuration is invalid: DB_URL does not match the configured MySQL host, port, and database; application was not restarted" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
status() {
|
||||
state=$1
|
||||
message=$2
|
||||
version=${3:-}
|
||||
downloaded_bytes=${4:-}
|
||||
total_bytes=${5:-}
|
||||
bytes_per_second=${6:-}
|
||||
download_percent=${7:-}
|
||||
restart_expected_seconds=${8:-}
|
||||
previous_state=
|
||||
previous_message=
|
||||
if [ -f "$STATUS_FILE" ] && [ ! -L "$STATUS_FILE" ]; then
|
||||
previous_state=$(jq -r '.state // empty' "$STATUS_FILE" 2>/dev/null || true)
|
||||
previous_message=$(jq -r '.message // empty' "$STATUS_FILE" 2>/dev/null || true)
|
||||
fi
|
||||
tmp="$STATUS_FILE.tmp.$$"
|
||||
jq -n \
|
||||
--arg state "$state" \
|
||||
--arg message "$message" \
|
||||
--arg version "$version" \
|
||||
--arg downloadedBytes "$downloaded_bytes" \
|
||||
--arg totalBytes "$total_bytes" \
|
||||
--arg bytesPerSecond "$bytes_per_second" \
|
||||
--arg downloadPercent "$download_percent" \
|
||||
--arg restartExpectedSeconds "$restart_expected_seconds" \
|
||||
--arg updatedAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
'{state:$state,message:$message,updatedAt:$updatedAt}
|
||||
+ (if ($version | length) > 0 then {targetVersion:$version} else {} end)' > "$tmp"
|
||||
+ (if ($version | length) > 0 then {targetVersion:$version} else {} end)
|
||||
+ (if ($downloadedBytes | test("^[0-9]+$")) then {downloadedBytes:($downloadedBytes | tonumber)} else {} end)
|
||||
+ (if ($totalBytes | test("^[0-9]+$")) then {totalBytes:($totalBytes | tonumber)} else {} end)
|
||||
+ (if ($bytesPerSecond | test("^[0-9]+$")) then {bytesPerSecond:($bytesPerSecond | tonumber)} else {} end)
|
||||
+ (if ($downloadPercent | test("^[0-9]+$")) then {downloadPercent:($downloadPercent | tonumber)} else {} end)
|
||||
+ (if ($restartExpectedSeconds | test("^[0-9]+$"))
|
||||
then {restartExpectedSeconds:($restartExpectedSeconds | tonumber)} else {} end)' > "$tmp"
|
||||
chmod 0644 "$tmp"
|
||||
mv -f "$tmp" "$STATUS_FILE"
|
||||
if [ "$state" != "$previous_state" ] || [ "$message" != "$previous_message" ]; then
|
||||
case "$state" in
|
||||
FAILED|RECOVERY_REQUIRED) event ERROR "$state" "$message" || true ;;
|
||||
*) event INFO "$state" "$message" || true ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
event() {
|
||||
level=$1
|
||||
stage=$2
|
||||
message=$3
|
||||
event_tmp="$EVENT_LOG_FILE.tmp.$$"
|
||||
jq -cn --arg occurredAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--arg level "$level" --arg stage "$stage" --arg message "$message" \
|
||||
'{occurredAt:$occurredAt,level:$level,stage:$stage,message:$message}' >> "$EVENT_LOG_FILE"
|
||||
chmod 0644 "$EVENT_LOG_FILE"
|
||||
event_size=$(wc -c < "$EVENT_LOG_FILE" | tr -d '[:space:]')
|
||||
if [ "$event_size" -gt 262144 ]; then
|
||||
tail -n 160 "$EVENT_LOG_FILE" > "$event_tmp"
|
||||
chmod 0644 "$event_tmp"
|
||||
mv -f "$event_tmp" "$EVENT_LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
reset_event_log() {
|
||||
rm -f "$EVENT_LOG_FILE"
|
||||
touch "$EVENT_LOG_FILE"
|
||||
chmod 0644 "$EVENT_LOG_FILE"
|
||||
}
|
||||
|
||||
archive_processing_request() {
|
||||
@@ -166,6 +237,7 @@ prepare_update_layout() {
|
||||
id -nG "$SERVICE_USER" | tr ' ' '\n' | grep -Fxq "$SERVICE_GROUP" \
|
||||
|| bootstrap_die "Service user $SERVICE_USER is not a member of group $SERVICE_GROUP"
|
||||
command -v runuser >/dev/null 2>&1 || bootstrap_die "runuser is required"
|
||||
command -v setsid >/dev/null 2>&1 || bootstrap_die "setsid is required"
|
||||
install -d -o root -g "$SERVICE_GROUP" -m 0750 \
|
||||
"$APP_ROOT" "$APP_ROOT/releases" "$APP_ROOT/runtime" "$APP_ROOT/bin" "$STATE_ROOT" \
|
||||
|| bootstrap_die "Managed application or update directories could not be prepared"
|
||||
@@ -213,6 +285,14 @@ verify_release_access() {
|
||||
cleanup() {
|
||||
rc=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [ -n "$DOWNLOAD_PID" ]; then
|
||||
if [ -n "$DOWNLOAD_PGID" ]; then
|
||||
kill -TERM -- "-$DOWNLOAD_PGID" 2>/dev/null || true
|
||||
else
|
||||
kill -TERM "$DOWNLOAD_PID" 2>/dev/null || true
|
||||
fi
|
||||
wait "$DOWNLOAD_PID" 2>/dev/null || true
|
||||
fi
|
||||
[ -z "$WORK_DIR" ] || rm -rf "$WORK_DIR"
|
||||
[ -z "$CACHE_TEMP" ] || rm -rf "$CACHE_TEMP"
|
||||
if [ "$rc" -ne 0 ] && [ "$TERMINAL_STATUS_WRITTEN" != true ]; then
|
||||
@@ -253,6 +333,83 @@ download() {
|
||||
esac
|
||||
}
|
||||
|
||||
download_with_progress() {
|
||||
progress_url=$1
|
||||
progress_output=$2
|
||||
progress_total=${3:-0}
|
||||
progress_started=$(date +%s)
|
||||
progress_previous_time=$progress_started
|
||||
progress_previous_bytes=0
|
||||
|
||||
rm -f "$progress_output"
|
||||
touch "$progress_output"
|
||||
chmod 0600 "$progress_output"
|
||||
case "$progress_url" in
|
||||
https://*)
|
||||
if [ -n "$RELEASE_TOKEN" ]; then
|
||||
[ -s "$RELEASE_AUTH_HEADER_FILE" ] || return 2
|
||||
setsid curl --fail --silent --show-error --proto '=https' --tlsv1.2 \
|
||||
--header "@$RELEASE_AUTH_HEADER_FILE" "$progress_url" -o "$progress_output" &
|
||||
else
|
||||
setsid curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' \
|
||||
--tlsv1.2 "$progress_url" -o "$progress_output" &
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
[ "${KAIDI_ALLOW_INSECURE_UPDATE:-false}" = "true" ] || return 2
|
||||
if [ -n "$RELEASE_TOKEN" ]; then
|
||||
[ -s "$RELEASE_AUTH_HEADER_FILE" ] || return 2
|
||||
setsid curl --fail --silent --show-error --proto '=http,https' \
|
||||
--header "@$RELEASE_AUTH_HEADER_FILE" "$progress_url" -o "$progress_output" &
|
||||
else
|
||||
setsid curl --fail --silent --show-error --location --proto '=http,https' \
|
||||
--proto-redir '=http,https' "$progress_url" -o "$progress_output" &
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
DOWNLOAD_PID=$!
|
||||
DOWNLOAD_PGID=$DOWNLOAD_PID
|
||||
while kill -0 "$DOWNLOAD_PID" 2>/dev/null; do
|
||||
progress_now=$(date +%s)
|
||||
progress_bytes=$(wc -c < "$progress_output" 2>/dev/null | tr -d '[:space:]' || printf 0)
|
||||
case "$progress_bytes:$progress_total" in *[!0-9:]*) progress_bytes=0; progress_total=0 ;; esac
|
||||
progress_delta_seconds=$((progress_now - progress_previous_time))
|
||||
[ "$progress_delta_seconds" -ge 1 ] || progress_delta_seconds=1
|
||||
progress_delta_bytes=$((progress_bytes - progress_previous_bytes))
|
||||
[ "$progress_delta_bytes" -ge 0 ] || progress_delta_bytes=0
|
||||
progress_speed=$((progress_delta_bytes / progress_delta_seconds))
|
||||
if [ "$progress_total" -gt 0 ]; then
|
||||
progress_percent=$((progress_bytes * 100 / progress_total))
|
||||
[ "$progress_percent" -le 99 ] || progress_percent=99
|
||||
else
|
||||
progress_percent=0
|
||||
fi
|
||||
status DOWNLOADING "Downloading signed release $TARGET_VERSION" "$TARGET_VERSION" \
|
||||
"$progress_bytes" "$progress_total" "$progress_speed" "$progress_percent"
|
||||
progress_previous_time=$progress_now
|
||||
progress_previous_bytes=$progress_bytes
|
||||
sleep 1
|
||||
done
|
||||
if wait "$DOWNLOAD_PID"; then
|
||||
DOWNLOAD_PID=
|
||||
DOWNLOAD_PGID=
|
||||
else
|
||||
DOWNLOAD_PID=
|
||||
DOWNLOAD_PGID=
|
||||
return 1
|
||||
fi
|
||||
|
||||
progress_bytes=$(wc -c < "$progress_output" | tr -d '[:space:]')
|
||||
[ "$progress_total" -gt 0 ] || progress_total=$progress_bytes
|
||||
progress_finished=$(date +%s)
|
||||
progress_elapsed=$((progress_finished - progress_started))
|
||||
[ "$progress_elapsed" -ge 1 ] || progress_elapsed=1
|
||||
progress_speed=$((progress_bytes / progress_elapsed))
|
||||
LAST_DOWNLOAD_SPEED=$progress_speed
|
||||
status DOWNLOADING "Release $TARGET_VERSION download completed" "$TARGET_VERSION" \
|
||||
"$progress_bytes" "$progress_total" "$progress_speed" 100
|
||||
}
|
||||
|
||||
release_asset_url() {
|
||||
asset_name=$1
|
||||
if [ -n "$RELEASE_API_URL" ]; then
|
||||
@@ -284,30 +441,56 @@ EOF
|
||||
wait_for_health() {
|
||||
health_url=$1
|
||||
attempts=0
|
||||
health_file=$(mktemp "$STATE_ROOT/work/health-response.XXXXXX")
|
||||
while [ "$attempts" -lt "$HEALTH_ATTEMPTS" ]; do
|
||||
if curl -fsS "$health_url" | jq -e '.status == "UP"' >/dev/null 2>&1; then
|
||||
health_http=$(curl --silent --show-error --connect-timeout 2 --max-time 4 \
|
||||
--output "$health_file" --write-out '%{http_code}' "$health_url" 2>/dev/null || true)
|
||||
if [ "$health_http" = 200 ] && jq -e '.status == "UP"' "$health_file" >/dev/null 2>&1; then
|
||||
rm -f "$health_file"
|
||||
return 0
|
||||
fi
|
||||
if [ "$health_http" = 200 ]; then
|
||||
SURFACE_FAILURE=HEALTH_DOWN
|
||||
elif [ -n "$health_http" ] && [ "$health_http" != 000 ]; then
|
||||
SURFACE_FAILURE="HEALTH_HTTP_$health_http"
|
||||
else
|
||||
SURFACE_FAILURE=HEALTH_UNREACHABLE
|
||||
fi
|
||||
attempts=$((attempts + 1))
|
||||
sleep "$HEALTH_INTERVAL_SECONDS"
|
||||
done
|
||||
rm -f "$health_file"
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_app_surface() {
|
||||
expected_release=${1:-}
|
||||
SURFACE_FAILURE=
|
||||
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" \
|
||||
index_http=$(curl --silent --show-error --connect-timeout 2 --max-time 5 \
|
||||
--output "$index_file" --write-out '%{http_code}' "$APP_INDEX_URL" 2>/dev/null || true)
|
||||
if [ "$index_http" = 200 ] \
|
||||
&& 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
|
||||
running_release=$(baota_process_release) || {
|
||||
SURFACE_FAILURE=PROCESS_IDENTITY_INVALID
|
||||
return 1
|
||||
}
|
||||
if [ "$running_release" != "$expected_release" ]; then
|
||||
SURFACE_FAILURE=RELEASE_MISMATCH
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
rm -f "$index_file"
|
||||
if [ "$index_http" != 200 ]; then
|
||||
SURFACE_FAILURE="INDEX_HTTP_${index_http:-000}"
|
||||
else
|
||||
SURFACE_FAILURE=INDEX_CONTENT_INVALID
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -554,6 +737,15 @@ remove_failed_release() {
|
||||
esac
|
||||
}
|
||||
|
||||
quarantine_active_transaction() {
|
||||
[ -d "$ACTIVE_TRANSACTION" ] || return 0
|
||||
recovery_transaction="$TRANSACTION_ROOT/recovery-required"
|
||||
if [ -e "$recovery_transaction" ] || [ -L "$recovery_transaction" ]; then
|
||||
recovery_transaction="$TRANSACTION_ROOT/recovery-required-$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
||||
fi
|
||||
mv "$ACTIVE_TRANSACTION" "$recovery_transaction"
|
||||
}
|
||||
|
||||
rollback_active_transaction() {
|
||||
reason=$1
|
||||
previous_target=$(transaction_value previous-target)
|
||||
@@ -583,10 +775,19 @@ rollback_active_transaction() {
|
||||
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:-}"
|
||||
recovery_guard_failed=false
|
||||
if ! archive_processing_request; then
|
||||
printf '%s\n' "Update request could not be archived after an incomplete rollback" >&2
|
||||
recovery_guard_failed=true
|
||||
fi
|
||||
if ! quarantine_active_transaction; then
|
||||
printf '%s\n' "Update transaction evidence could not be quarantined after an incomplete rollback" >&2
|
||||
recovery_guard_failed=true
|
||||
fi
|
||||
if [ "$recovery_guard_failed" = true ]; then
|
||||
systemctl stop "$UPDATE_PATH_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
status RECOVERY_REQUIRED "$reason; rollback is incomplete and manual recovery is required" "${TARGET_VERSION:-}"
|
||||
printf '%s\n' "$reason; rollback is incomplete and manual recovery is required" >&2
|
||||
exit 1
|
||||
}
|
||||
@@ -651,6 +852,16 @@ else
|
||||
fi
|
||||
chmod 0600 "$PROCESSING_FILE"
|
||||
|
||||
if [ -f "$STATUS_FILE" ] && [ ! -L "$STATUS_FILE" ] \
|
||||
&& jq -e '.state == "RECOVERY_REQUIRED"' "$STATUS_FILE" >/dev/null 2>&1; then
|
||||
TERMINAL_STATUS_WRITTEN=true
|
||||
if ! archive_processing_request; then
|
||||
systemctl stop "$UPDATE_PATH_NAME" >/dev/null 2>&1 || true
|
||||
fi
|
||||
printf '%s\n' "Update service is locked for manual recovery; new requests are rejected" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! REQUESTED_VERSION=$(jq -er '.version | strings | select(length > 0 and length <= 128)' \
|
||||
"$PROCESSING_FILE"); then
|
||||
fail "Update request version is invalid"
|
||||
@@ -661,7 +872,11 @@ REQUEST_ACTION=$(jq -er '(.action // "INSTALL") | strings | ascii_upcase
|
||||
| select(. == "DOWNLOAD" or . == "INSTALL")' "$PROCESSING_FILE") \
|
||||
|| fail "Update request action is invalid"
|
||||
prepare_update_layout
|
||||
reset_event_log
|
||||
load_runtime_database_env
|
||||
if [ "$REQUEST_ACTION" = INSTALL ]; then
|
||||
validate_runtime_database_config
|
||||
fi
|
||||
[ -z "$RELEASE_TOKEN" ] || { [ "${#RELEASE_TOKEN}" -le 512 ] \
|
||||
&& ! printf '%s' "$RELEASE_TOKEN" | grep -q '[[:cntrl:]]'; } \
|
||||
|| fail "UPDATE_RELEASE_TOKEN is invalid"
|
||||
@@ -714,6 +929,8 @@ ARTIFACT=$(jq -er '.artifact | strings | select(test("^[A-Za-z0-9][A-Za-z0-9._+-
|
||||
"$WORK_DIR/release-manifest.json") || fail "Release artifact name is invalid"
|
||||
EXPECTED_SHA=$(jq -er '.sha256 | strings | ascii_downcase | select(test("^[0-9a-f]{64}$"))' \
|
||||
"$WORK_DIR/release-manifest.json") || fail "Release SHA-256 is invalid"
|
||||
EXPECTED_SIZE=$(jq -er '(.artifactSizeBytes // 0) | numbers | floor | select(. >= 0)' \
|
||||
"$WORK_DIR/release-manifest.json") || fail "Release artifact size is invalid"
|
||||
|
||||
CURRENT_VERSION=$(cat "$APP_ROOT/current/VERSION" 2>/dev/null || true)
|
||||
if [ "$CURRENT_VERSION" = "$TARGET_VERSION" ]; then
|
||||
@@ -726,12 +943,16 @@ fi
|
||||
if [ "$REQUEST_ACTION" = DOWNLOAD ]; then
|
||||
status DOWNLOADING "Downloading signed release $TARGET_VERSION" "$TARGET_VERSION"
|
||||
ARTIFACT_URL=$(release_asset_url "$ARTIFACT") || fail "Release artifact asset is missing"
|
||||
download "$ARTIFACT_URL" "$WORK_DIR/release.tar.gz" || fail "Release artifact download failed"
|
||||
download_with_progress "$ARTIFACT_URL" "$WORK_DIR/release.tar.gz" "$EXPECTED_SIZE" \
|
||||
|| fail "Release artifact download failed"
|
||||
else
|
||||
status VERIFYING "Revalidating cached release $TARGET_VERSION" "$TARGET_VERSION"
|
||||
fi
|
||||
ACTUAL_SHA=$(sha256sum "$WORK_DIR/release.tar.gz" | awk '{print $1}')
|
||||
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || fail "Release artifact SHA-256 verification failed"
|
||||
ACTUAL_SIZE=$(wc -c < "$WORK_DIR/release.tar.gz" | tr -d '[:space:]')
|
||||
[ "$EXPECTED_SIZE" -eq 0 ] || [ "$ACTUAL_SIZE" -eq "$EXPECTED_SIZE" ] \
|
||||
|| fail "Release artifact size verification failed"
|
||||
|
||||
tar -tzf "$WORK_DIR/release.tar.gz" > "$WORK_DIR/archive.list" || fail "Release archive could not be listed"
|
||||
if grep -Eq '(^/|(^|/)\.\.(/|$))' "$WORK_DIR/archive.list"; then
|
||||
@@ -768,7 +989,8 @@ if [ "$REQUEST_ACTION" = DOWNLOAD ]; then
|
||||
fi
|
||||
mv "$CACHE_TEMP" "$CACHE_DIR" || fail "Verified release cache could not be activated"
|
||||
CACHE_TEMP=
|
||||
status READY "Release $TARGET_VERSION is downloaded and verified; confirm installation" "$TARGET_VERSION"
|
||||
status READY "Release $TARGET_VERSION is downloaded and verified; confirm installation" "$TARGET_VERSION" \
|
||||
"$ACTUAL_SIZE" "$ACTUAL_SIZE" "$LAST_DOWNLOAD_SPEED" 100
|
||||
TERMINAL_STATUS_WRITTEN=true
|
||||
complete_request
|
||||
exit 0
|
||||
@@ -791,6 +1013,13 @@ if ! verify_release_access "$RELEASE_DIR"; then
|
||||
fi
|
||||
|
||||
PREVIOUS_TARGET=$(readlink "$APP_ROOT/current" 2>/dev/null || true)
|
||||
status PRECHECKING "Validating current release before switching to $TARGET_VERSION" "$TARGET_VERSION"
|
||||
if [ -z "$PREVIOUS_TARGET" ] || [ ! -d "$PREVIOUS_TARGET" ]; then
|
||||
fail "Current release path is invalid; application was not restarted"
|
||||
fi
|
||||
if ! verify_app_surface "$PREVIOUS_TARGET"; then
|
||||
fail "Current release preflight failed (${SURFACE_FAILURE:-UNKNOWN}); application was not restarted"
|
||||
fi
|
||||
mkdir "$ACTIVE_TRANSACTION"
|
||||
write_transaction_value target-version "$TARGET_VERSION"
|
||||
write_transaction_value previous-target "$PREVIOUS_TARGET"
|
||||
@@ -817,7 +1046,7 @@ if ! ln -sfn "$RELEASE_DIR" "$APP_ROOT/current.next" \
|
||||
fi
|
||||
write_transaction_value phase APP_SWITCHED
|
||||
|
||||
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION"
|
||||
status RUNNING "Starting and verifying release $TARGET_VERSION" "$TARGET_VERSION" "" "" "" "" 10
|
||||
write_transaction_value phase HEALTH_CHECKING
|
||||
if ! restart_application; then
|
||||
rollback_active_transaction "Release application restart failed"
|
||||
@@ -837,5 +1066,6 @@ fi
|
||||
|
||||
service_state=$(application_state 2>/dev/null || true)
|
||||
[ -n "$service_state" ] || service_state=unknown
|
||||
surface_failure=${SURFACE_FAILURE:-UNKNOWN}
|
||||
rollback_active_transaction \
|
||||
"Release health or application-surface verification failed (service state: $service_state)"
|
||||
"Release application verification failed ($surface_failure; service state: $service_state)"
|
||||
|
||||
Reference in New Issue
Block a user