Preflight disk space before Pulse updates
Some checks failed
Build and Test / Secret Scan (push) Has been cancelled
Build and Test / Frontend & Backend (push) Has been cancelled
Core E2E Tests / Playwright Core E2E (push) Has been cancelled
Update Integration Tests / Update Flow Integration Tests (push) Has been cancelled

This commit is contained in:
rcourtman 2026-04-15 20:56:58 +01:00
parent 0b836aa3af
commit 4de1c3745a
3 changed files with 162 additions and 1 deletions

View file

@ -31,6 +31,8 @@ CONTAINER_CREATED_FOR_CLEANUP=false
BUILD_FROM_SOURCE_MARKER="$INSTALL_DIR/BUILD_FROM_SOURCE"
DETECTED_CTID=""
STOPPED_PULSE_SERVICE=""
UPDATE_MIN_TEMP_FREE_BYTES=$((900 * 1024 * 1024))
UPDATE_MIN_INSTALL_FREE_BYTES=$((256 * 1024 * 1024))
# Installer version - the major version this script is bundled with
INSTALLER_MAJOR_VERSION=5
@ -42,6 +44,97 @@ AUTO_NODE_REGISTER_ERROR=""
DEBIAN_TEMPLATE_FALLBACK="debian-12-standard_12.12-1_amd64.tar.zst"
DEBIAN_TEMPLATE=""
bytes_to_human() {
local bytes="${1:-0}"
if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then
printf '%s\n' "$bytes"
return 0
fi
local units=("B" "KB" "MB" "GB" "TB")
local value="$bytes"
local unit_index=0
while (( value >= 1024 && unit_index < ${#units[@]} - 1 )); do
value=$((value / 1024))
((unit_index += 1))
done
printf '%s%s\n' "$value" "${units[$unit_index]}"
}
get_available_bytes_for_path() {
local path="$1"
local available_kb=""
available_kb=$(df -Pk "$path" 2>/dev/null | awk 'NR==2 {print $4}')
if [[ ! "$available_kb" =~ ^[0-9]+$ ]]; then
return 1
fi
printf '%s\n' $((available_kb * 1024))
}
get_filesystem_device_for_path() {
local path="$1"
local filesystem=""
filesystem=$(df -Pk "$path" 2>/dev/null | awk 'NR==2 {print $1}')
if [[ -z "$filesystem" ]]; then
return 1
fi
printf '%s\n' "$filesystem"
}
ensure_update_disk_headroom() {
local temp_path="${1:-/tmp}"
local install_path="${2:-$INSTALL_DIR}"
local temp_fs=""
local install_fs=""
local temp_free_bytes=""
local install_free_bytes=""
local combined_required_bytes=$((UPDATE_MIN_TEMP_FREE_BYTES + UPDATE_MIN_INSTALL_FREE_BYTES))
temp_fs=$(get_filesystem_device_for_path "$temp_path" 2>/dev/null || true)
install_fs=$(get_filesystem_device_for_path "$install_path" 2>/dev/null || true)
temp_free_bytes=$(get_available_bytes_for_path "$temp_path" 2>/dev/null || true)
install_free_bytes=$(get_available_bytes_for_path "$install_path" 2>/dev/null || true)
if [[ -z "$temp_free_bytes" || -z "$install_free_bytes" ]]; then
print_warn "Could not determine available disk space for the update preflight; continuing anyway"
return 0
fi
if [[ -n "$temp_fs" && "$temp_fs" == "$install_fs" ]]; then
if (( temp_free_bytes < combined_required_bytes )); then
print_error "Not enough free disk space to stage the Pulse update"
print_info "The same filesystem backs $temp_path and $install_path"
print_info "Available: $(bytes_to_human "$temp_free_bytes"), required: $(bytes_to_human "$combined_required_bytes")"
print_info "Free disk space and retry the update"
return 1
fi
return 0
fi
if (( temp_free_bytes < UPDATE_MIN_TEMP_FREE_BYTES )); then
print_error "Not enough free disk space in $temp_path to stage the Pulse update"
print_info "Available: $(bytes_to_human "$temp_free_bytes"), required: $(bytes_to_human "$UPDATE_MIN_TEMP_FREE_BYTES")"
print_info "Free disk space under $temp_path and retry the update"
return 1
fi
if (( install_free_bytes < UPDATE_MIN_INSTALL_FREE_BYTES )); then
print_error "Not enough free disk space in $install_path to apply the Pulse update"
print_info "Available: $(bytes_to_human "$install_free_bytes"), required: $(bytes_to_human "$UPDATE_MIN_INSTALL_FREE_BYTES")"
print_info "Free disk space under $install_path and retry the update"
return 1
fi
return 0
}
get_latest_release_from_redirect() {
# Follow the GitHub "latest" redirect and extract the tag in a way that
# tolerates intermediate redirects that omit /tag/ (issue #698).
@ -2604,6 +2697,10 @@ download_pulse() {
rm -f "$BUILD_FROM_SOURCE_MARKER"
if ! ensure_update_disk_headroom "/tmp" "$INSTALL_DIR"; then
exit 1
fi
EXISTING_SERVICE=$(detect_service_name)
stop_pulse_service_for_update "$EXISTING_SERVICE" || true

View file

@ -59,7 +59,7 @@ func (a *InstallShAdapter) PrepareUpdate(ctx context.Context, request UpdateRequ
Prerequisites: []string{
"Root access (sudo)",
"Internet connection",
"At least 100MB free disk space",
"About 1.2GB free disk space for update staging",
},
}

View file

@ -42,6 +42,68 @@ test_infer_release_from_archive_name_supports_prerelease() {
)
}
test_ensure_update_disk_headroom_fails_when_tmp_and_install_share_full_filesystem() {
(
load_installer
UPDATE_MIN_TEMP_FREE_BYTES=$((100 * 1024))
UPDATE_MIN_INSTALL_FREE_BYTES=$((80 * 1024))
print_error() { :; }
print_info() { :; }
print_warn() { :; }
df() {
if [[ "$1" == "-Pk" ]]; then
case "$2" in
/tmp|/opt/pulse)
printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n'
printf '/dev/shared 1000 0 150 0%% /\n'
return 0
;;
esac
fi
command df "$@"
}
if ensure_update_disk_headroom /tmp /opt/pulse; then
echo "ensure_update_disk_headroom unexpectedly passed on a shared full filesystem" >&2
return 1
fi
)
}
test_ensure_update_disk_headroom_accepts_separate_filesystems_with_sufficient_space() {
(
load_installer
UPDATE_MIN_TEMP_FREE_BYTES=$((100 * 1024))
UPDATE_MIN_INSTALL_FREE_BYTES=$((80 * 1024))
print_error() { :; }
print_info() { :; }
print_warn() { :; }
df() {
if [[ "$1" == "-Pk" ]]; then
case "$2" in
/tmp)
printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n'
printf '/dev/tmp 1000 0 120 0%% /tmp\n'
return 0
;;
/opt/pulse)
printf 'Filesystem 1024-blocks Used Available Capacity Mounted on\n'
printf '/dev/root 1000 0 90 0%% /\n'
return 0
;;
esac
fi
command df "$@"
}
ensure_update_disk_headroom /tmp /opt/pulse
)
}
test_download_pulse_installs_from_local_archive_without_network() {
(
load_installer
@ -266,6 +328,8 @@ test_install_additional_agent_binaries_skips_network_when_local_extras_are_missi
main() {
assert_success "infer_release_from_archive_name parses prerelease tarballs" test_infer_release_from_archive_name_supports_prerelease
assert_success "update disk preflight fails on shared low-space filesystems" test_ensure_update_disk_headroom_fails_when_tmp_and_install_share_full_filesystem
assert_success "update disk preflight passes on separate filesystems with enough headroom" test_ensure_update_disk_headroom_accepts_separate_filesystems_with_sufficient_space
assert_success "download_pulse installs from local archive without network" test_download_pulse_installs_from_local_archive_without_network
assert_success "prefetch helper writes archive path via output variable" test_prefetch_pulse_archive_for_container_sets_output_var
assert_success "wrong-arch archives fail before replacing the installed binary" test_install_pulse_archive_rejects_mismatched_arch_without_replacing_existing_binary