Fail closed instead of auto-downgrading, and back up config to a writable path under the hardened update unit

Customer report against v6.0.5 (2026-07-13):
- resolve_target_release fell back to hardcoded v4.5.1 when the GitHub API
  and the /releases/latest redirect both failed, silently downgrading a
  v5.0.17 install two major versions. Now: fail with --version guidance,
  and refuse any auto-resolved target older than the installed version.
- backup_existing wrote /etc/pulse.backup.<ts>, which is read-only under
  pulse-update.service (ProtectSystem=strict, ReadWritePaths=/opt/pulse
  /etc/pulse /tmp), so every unattended update on systemd/LXC failed at
  the backup step. Now: fall back to $INSTALL_DIR/config-backups when the
  config parent is not writable.
This commit is contained in:
rcourtman
2026-07-13 16:13:29 +01:00
parent f9f90a1a27
commit c5bf3adebf
+32 -7
View File
@@ -266,7 +266,7 @@ ensure_update_disk_headroom() {
ensure_config_backup_headroom() {
local config_path="${1:-$CONFIG_DIR}"
local backup_parent=""
local backup_parent="${2:-}"
local free_bytes=""
local config_bytes=""
local required_bytes=""
@@ -275,7 +275,9 @@ ensure_config_backup_headroom() {
return 0
fi
backup_parent="$(dirname "$config_path")"
if [[ -z "$backup_parent" ]]; then
backup_parent="$(dirname "$config_path")"
fi
free_bytes=$(get_available_bytes_for_path "$backup_parent" 2>/dev/null || true)
config_bytes=$(get_directory_size_bytes "$config_path" 2>/dev/null || true)
@@ -289,7 +291,7 @@ ensure_config_backup_headroom() {
print_error "Not enough free disk space to back up the existing Pulse configuration"
print_info "Configuration size: $(bytes_to_human "$config_bytes")"
print_info "Available in $backup_parent: $(bytes_to_human "$free_bytes"), required: $(bytes_to_human "$required_bytes")"
print_info "Free disk space or remove old ${config_path}.backup.* directories, then retry the update"
print_info "Free disk space or remove old configuration backups under $backup_parent, then retry the update"
return 1
fi
@@ -2846,8 +2848,17 @@ create_user() {
backup_existing() {
if [[ -d "$CONFIG_DIR" ]]; then
print_info "Backing up existing configuration..."
ensure_config_backup_headroom "$CONFIG_DIR" || return 1
local backup_dir="${CONFIG_DIR}.backup.$(date +%Y%m%d-%H%M%S)"
local backup_parent backup_dir
backup_parent="$(dirname "$CONFIG_DIR")"
if [[ ! -w "$backup_parent" ]]; then
# The hardened pulse-update.service unit mounts /etc read-only
# (ProtectSystem=strict; only /opt/pulse, /etc/pulse and /tmp are
# writable), so keep the safety copy under the install dir there.
backup_parent="$INSTALL_DIR/config-backups"
mkdir -p "$backup_parent" || return 1
fi
ensure_config_backup_headroom "$CONFIG_DIR" "$backup_parent" || return 1
backup_dir="$backup_parent/$(basename "$CONFIG_DIR").backup.$(date +%Y%m%d-%H%M%S)"
if ! cp -a "$CONFIG_DIR" "$backup_dir"; then
rm -rf "$backup_dir"
return 1
@@ -3096,8 +3107,22 @@ resolve_target_release() {
fi
if [[ -z "$LATEST_RELEASE" ]]; then
print_warn "Could not determine latest release from GitHub, using fallback version"
LATEST_RELEASE="v4.5.1"
print_error "Could not determine the latest Pulse release from GitHub"
print_info "GitHub may be unreachable or rate limiting. Retry later, or pin the release explicitly:"
print_info " bash install.sh --version vX.Y.Z"
exit 1
fi
# Never move an existing install backwards from auto-resolution. Explicit
# downgrades stay available via --version.
if [[ -n "${CURRENT_VERSION:-}" && "$CURRENT_VERSION" != "unknown" ]]; then
local resolve_cmp=0
compare_versions "$LATEST_RELEASE" "$CURRENT_VERSION" || resolve_cmp=$?
if [[ "$resolve_cmp" -eq 2 ]]; then
print_error "Resolved release $LATEST_RELEASE is older than the installed $CURRENT_VERSION; refusing to downgrade automatically"
print_info "To downgrade on purpose, run: bash install.sh --version $LATEST_RELEASE"
exit 1
fi
fi
print_info "Latest version: $LATEST_RELEASE"