From 0f2982ce3f9dee414300c16ef89f30e0448e851f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 25 Mar 2026 23:49:25 +0000 Subject: [PATCH] Fix auto-update leaving Pulse stopped in LXC installs (#1323) --- install.sh | 12 ++ scripts/pulse-auto-update.sh | 47 +++++++- scripts/tests/test-pulse-auto-update.sh | 143 ++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100755 scripts/tests/test-pulse-auto-update.sh diff --git a/install.sh b/install.sh index f80001965..d59b006ff 100755 --- a/install.sh +++ b/install.sh @@ -3098,6 +3098,18 @@ start_pulse() { if ! safe_systemctl start $SERVICE_NAME; then print_info "Note: systemctl start failed (common in unprivileged containers)" + sleep 3 + if timeout 5 systemctl is-active --quiet $SERVICE_NAME 2>/dev/null; then + print_success "Pulse started successfully" + STOPPED_PULSE_SERVICE="" + return 0 + fi + + if [[ -n "$STOPPED_PULSE_SERVICE" ]]; then + print_error "Pulse was running before the update but did not come back up" + return 1 + fi + print_info "The service will start automatically when the container starts" STOPPED_PULSE_SERVICE="" return 0 diff --git a/scripts/pulse-auto-update.sh b/scripts/pulse-auto-update.sh index 64d6fe8ad..258f90617 100755 --- a/scripts/pulse-auto-update.sh +++ b/scripts/pulse-auto-update.sh @@ -150,6 +150,22 @@ restart_service_if_needed() { fi } +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 the update perform_update() { local new_version=$1 @@ -205,6 +221,33 @@ perform_update() { local installed_version=$(get_current_version) if [[ "$installed_version" == "$new_version" ]]; then log info "Version verified: $installed_version" + + 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 + + restart_service_if_needed "$service_name" "$service_was_active" + rm -rf "$backup_dir" + return 1 + fi + fi # Clean up backup rm -rf "$backup_dir" @@ -318,4 +361,6 @@ main() { } # Run main function -main "$@" +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi diff --git a/scripts/tests/test-pulse-auto-update.sh b/scripts/tests/test-pulse-auto-update.sh new file mode 100755 index 000000000..86c8876c4 --- /dev/null +++ b/scripts/tests/test-pulse-auto-update.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# +# Smoke tests for scripts/pulse-auto-update.sh helper behavior. + +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 + +# 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_perform_update_restores_backup_when_service_stays_down() { + local tmpdir + tmpdir="$(mktemp -d)" + local status=0 + + 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" + + get_current_version() { + tr -d '\r\n' < "${INSTALL_DIR}/VERSION" + } + + detect_service_name() { + echo "pulse" + } + + local is_active_calls=0 + local restart_called=0 + + systemctl() { + if [[ "$1" == "is-active" ]]; then + ((is_active_calls += 1)) + if (( is_active_calls == 1 )); then + return 0 + fi + return 1 + fi + if [[ "$1" == "start" ]]; then + return 1 + fi + return 0 + } + + restart_service_if_needed() { + restart_called=1 + return 0 + } + + sleep() { :; } + + curl() { + cat <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$FAKE_NEW_VERSION" > "$INSTALL_DIR/VERSION" +exit 0 +EOF + } + + if perform_update "v5.1.25"; then + echo "perform_update unexpectedly succeeded" >&2 + status=1 + fi + + if [[ "${status}" -eq 0 ]] && [[ "$(tr -d '\r\n' < "${INSTALL_DIR}/VERSION")" != "v5.1.24" ]]; then + echo "expected VERSION to be restored after failed restart" >&2 + status=1 + fi + + if [[ "${status}" -eq 0 ]] && (( restart_called != 1 )); then + echo "expected restart_service_if_needed to be called" >&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 "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 "$@"