mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Verify service restart in unattended auto-update; roll back if down (#1323)
Back-port the pulse-auto-update.sh half of v5 fix 0f2982ce3 to v6. After a
verified update, if Pulse was running beforehand, wait_for_service_active
polls up to 20s, tries one explicit start, and if it still does not come
back up restores the backup binary/VERSION, restarts, and returns failure
instead of leaving Pulse stopped (common on unprivileged LXC where the
installer's restart silently fails). Adds a BASH_SOURCE guard so the
script can be sourced, plus scripts/tests/test-pulse-auto-update.sh.
Note: the install.sh start_pulse STOPPED_PULSE_SERVICE guard (interactive
installer path) still needs the stop-tracking infrastructure wired into
v6's update flow; tracked as a follow-up.
This commit is contained in:
parent
5134b39800
commit
672e819850
2 changed files with 224 additions and 4 deletions
|
|
@ -259,12 +259,36 @@ resolve_install_script_url() {
|
|||
}
|
||||
|
||||
# Perform the update
|
||||
wait_for_service_active() {
|
||||
local service_name=$1
|
||||
local timeout_seconds="${2:-20}"
|
||||
local elapsed=0
|
||||
|
||||
while (( elapsed < timeout_seconds )); do
|
||||
if systemctl is-active --quiet "$service_name" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
((elapsed += 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
perform_update() {
|
||||
local new_version=$1
|
||||
local service_name=$(detect_service_name)
|
||||
local installer_tmp=""
|
||||
local signature_tmp=""
|
||||
|
||||
# Capture whether Pulse was running before the update so we can guarantee it
|
||||
# comes back up afterwards (#1323: auto-update could leave it stopped on
|
||||
# unprivileged LXC where the installer's restart silently fails).
|
||||
local service_was_active="false"
|
||||
if systemctl is-active --quiet "$service_name" 2>/dev/null; then
|
||||
service_was_active="true"
|
||||
fi
|
||||
|
||||
# Refuse to install a prerelease via the unattended updater. The stable
|
||||
# channel must never cross onto a tag like v6.0.0-rc.2, even if every
|
||||
# caller above this point thought it was safe.
|
||||
|
|
@ -341,10 +365,40 @@ perform_update() {
|
|||
local installed_version=$(get_current_version)
|
||||
if [[ "$installed_version" == "$new_version" ]]; then
|
||||
log info "Version verified: $installed_version"
|
||||
|
||||
|
||||
# If Pulse was running before the update, make sure it is running
|
||||
# again. On unprivileged LXC the installer's restart can silently
|
||||
# fail, leaving Pulse stopped (#1323).
|
||||
if [[ "$service_was_active" == "true" ]]; then
|
||||
if ! wait_for_service_active "$service_name" 20; then
|
||||
log warn "Pulse service is not active after update, attempting one explicit start"
|
||||
systemctl start "$service_name" || true
|
||||
fi
|
||||
|
||||
if ! wait_for_service_active "$service_name" 20; then
|
||||
log error "Pulse service did not come back up after update"
|
||||
|
||||
log info "Restoring from backup"
|
||||
if [[ -f "$backup_dir/pulse" ]]; then
|
||||
if [[ -f "$INSTALL_DIR/bin/pulse" ]]; then
|
||||
cp -f "$backup_dir/pulse" "$INSTALL_DIR/bin/pulse"
|
||||
else
|
||||
cp -f "$backup_dir/pulse" "$INSTALL_DIR/pulse"
|
||||
fi
|
||||
fi
|
||||
if [[ -f "$backup_dir/VERSION" ]]; then
|
||||
cp -f "$backup_dir/VERSION" "$INSTALL_DIR/VERSION"
|
||||
fi
|
||||
|
||||
systemctl restart "$service_name" || true
|
||||
rm -rf "$backup_dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean up backup
|
||||
rm -rf "$backup_dir"
|
||||
|
||||
|
||||
return 0
|
||||
else
|
||||
log error "Version mismatch after update. Expected: $new_version, Got: $installed_version"
|
||||
|
|
@ -452,5 +506,7 @@ main() {
|
|||
log info "Auto-update check completed"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
# Run main function (skipped when the script is sourced, e.g. by tests).
|
||||
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
|
|
|
|||
164
scripts/tests/test-pulse-auto-update.sh
Executable file
164
scripts/tests/test-pulse-auto-update.sh
Executable file
|
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Smoke tests for scripts/pulse-auto-update.sh helper behavior (#1323).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
AUTO_UPDATE_SCRIPT="${ROOT_DIR}/scripts/pulse-auto-update.sh"
|
||||
|
||||
if [[ ! -f "${AUTO_UPDATE_SCRIPT}" ]]; then
|
||||
echo "pulse-auto-update.sh not found at ${AUTO_UPDATE_SCRIPT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sourcing relies on the BASH_SOURCE guard so main() does not run on import.
|
||||
# shellcheck disable=SC1090
|
||||
source "${AUTO_UPDATE_SCRIPT}"
|
||||
|
||||
failures=0
|
||||
|
||||
assert_success() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
echo "[PASS] ${desc}"
|
||||
return 0
|
||||
else
|
||||
echo "[FAIL] ${desc}" >&2
|
||||
((failures++))
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_wait_for_service_active_succeeds_after_retry() {
|
||||
local calls=0
|
||||
|
||||
systemctl() {
|
||||
if [[ "$1" == "is-active" ]]; then
|
||||
((calls += 1))
|
||||
if (( calls >= 3 )); then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
sleep() { :; }
|
||||
|
||||
wait_for_service_active pulse 5
|
||||
}
|
||||
|
||||
test_wait_for_service_active_times_out_when_never_active() {
|
||||
systemctl() { return 1; }
|
||||
sleep() { :; }
|
||||
|
||||
if wait_for_service_active pulse 3; then
|
||||
echo "expected wait_for_service_active to fail when service never becomes active" >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
test_perform_update_restores_backup_when_service_stays_down() {
|
||||
local tmpdir
|
||||
tmpdir="$(mktemp -d)"
|
||||
local status=0
|
||||
# perform_update installs a RETURN trap referencing these; declare them here
|
||||
# so the trap is safe under set -u if it surfaces in this calling scope.
|
||||
local installer_tmp="" signature_tmp=""
|
||||
|
||||
INSTALL_DIR="${tmpdir}/opt/pulse"
|
||||
CONFIG_DIR="${tmpdir}/etc/pulse"
|
||||
mkdir -p "${INSTALL_DIR}/bin" "${CONFIG_DIR}"
|
||||
|
||||
printf 'v5.1.24\n' > "${INSTALL_DIR}/VERSION"
|
||||
cat > "${INSTALL_DIR}/bin/pulse" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
echo "v5.1.24"
|
||||
EOF
|
||||
chmod +x "${INSTALL_DIR}/bin/pulse"
|
||||
|
||||
export INSTALL_DIR
|
||||
export FAKE_NEW_VERSION="v5.1.25"
|
||||
|
||||
# Stub out the v6 download/verify pipeline so perform_update reaches the
|
||||
# post-install service check deterministically.
|
||||
is_prerelease_tag() { return 1; }
|
||||
detect_service_name() { echo "pulse"; }
|
||||
resolve_install_script_url() { echo "http://localhost/install.sh"; }
|
||||
verify_release_signature() { return 0; }
|
||||
get_current_version() { tr -d '\r\n' < "${INSTALL_DIR}/VERSION"; }
|
||||
|
||||
# curl writes the installer / signature to the -o target. The fake installer
|
||||
# bumps VERSION to the new version, simulating a successful install.
|
||||
curl() {
|
||||
local out="" prev=""
|
||||
local arg
|
||||
for arg in "$@"; do
|
||||
if [[ "$prev" == "-o" ]]; then out="$arg"; fi
|
||||
prev="$arg"
|
||||
done
|
||||
if [[ -n "$out" ]]; then
|
||||
case "$out" in
|
||||
*.sig.*) printf 'dummy-signature\n' > "$out" ;;
|
||||
*)
|
||||
cat > "$out" <<'INSTALLER'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "${FAKE_NEW_VERSION}" > "${PULSE_INSTALL_DIR}/VERSION"
|
||||
exit 0
|
||||
INSTALLER
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Service was running before the update (first is-active call true), then
|
||||
# never comes back up; start also fails -> perform_update must restore + fail.
|
||||
local is_active_calls=0
|
||||
systemctl() {
|
||||
if [[ "$1" == "is-active" ]]; then
|
||||
((is_active_calls += 1))
|
||||
if (( is_active_calls == 1 )); then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
sleep() { :; }
|
||||
|
||||
if perform_update "v5.1.25"; then
|
||||
echo "perform_update unexpectedly succeeded while service stayed down" >&2
|
||||
status=1
|
||||
fi
|
||||
# perform_update installs a RETURN trap; clear it so it does not leak into
|
||||
# subsequent function returns in this sourced test harness.
|
||||
trap - RETURN 2>/dev/null || true
|
||||
|
||||
if [[ "${status}" -eq 0 ]] && [[ "$(tr -d '\r\n' < "${INSTALL_DIR}/VERSION")" != "v5.1.24" ]]; then
|
||||
echo "expected VERSION to be restored to v5.1.24 after failed restart" >&2
|
||||
status=1
|
||||
fi
|
||||
|
||||
rm -rf "${tmpdir}"
|
||||
return "${status}"
|
||||
}
|
||||
|
||||
main() {
|
||||
assert_success "wait_for_service_active retries until active" test_wait_for_service_active_succeeds_after_retry
|
||||
assert_success "wait_for_service_active times out when never active" test_wait_for_service_active_times_out_when_never_active
|
||||
assert_success "perform_update restores backup when service stays down" test_perform_update_restores_backup_when_service_stays_down
|
||||
|
||||
if (( failures > 0 )); then
|
||||
echo "Total failures: ${failures}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "All pulse-auto-update smoke tests passed."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue