Files
hassio-addons/scrutiny/rootfs/usr/local/bin/scrutiny-ha-influxdb-preflight
Alexandre bbaa0900a4 Fix Scrutiny 1.67 startup and InfluxDB migration (#2880)
* scrutiny: back up InfluxDB before 2.9 upgrade

* scrutiny: prepare InfluxDB 2.9 migration safely

* scrutiny: restore upstream s6 supervision

* scrutiny-fa: restore upstream s6 supervision

* scrutiny: validate backup contents before migration

* scrutiny: keep backup validation bounded

* scrutiny: document startup and migration fix

* scrutiny-fa: document startup and migration fix

* scrutiny: bump version to v1.67.0-3

* scrutiny-fa: bump version to v1.67.0-3

* scrutiny-fa: include migration helper in build context

* scrutiny-fa: copy migration helper explicitly

* scrutiny-fa: document build-context fix

* scrutiny-fa: bump version to v1.67.0-4

* scrutiny-fa: materialize Home Assistant rootfs overlay

* scrutiny-fa: use materialized rootfs overlay

* scrutiny-fa: bump version with changelog

* scrutiny: verify complete InfluxDB backup state

* scrutiny-fa: verify complete InfluxDB backup state

* scrutiny: support custom day intervals

* scrutiny-fa: support custom day intervals

* scrutiny: support absolute TLS paths

* scrutiny-fa: support absolute TLS paths

* scrutiny: harden collector configuration migration

* scrutiny-fa: harden collector configuration migration

* scrutiny: preserve legacy migration backups

* scrutiny-fa: preserve legacy migration backups

* scrutiny: restore initialization-only s6 hook

* scrutiny-fa: restore initialization-only s6 hook

* scrutiny: trigger reviewed release build

* scrutiny: finalize reviewed release

* scrutiny: finalize combined release metadata

* scrutiny: mark review-complete release

* scrutiny: normalize release metadata

* scrutiny: settle release metadata

* scrutiny: finalize metadata formatting

* scrutiny: synchronize reviewed release metadata

* scrutiny: run isolated reviewed build

* scrutiny-fa: run isolated reviewed build

* scrutiny: verify backup contents before migration

* scrutiny-fa: verify backup contents before migration

* scrutiny: test content-verified atomic backups

* scrutiny-fa: test content-verified atomic backups

* scrutiny: validate content-level backup integrity

* scrutiny: remove blocking FIFO test fixture

* scrutiny-fa: remove blocking FIFO test fixture

* scrutiny: run final content-integrity build

* scrutiny-fa: run final content-integrity build

* scrutiny: correct nginx readiness comment

* scrutiny-fa: correct nginx readiness comment

* scrutiny: skip collector symlinks during migration

* scrutiny-fa: skip collector symlinks during migration
2026-07-18 21:51:14 +02:00

229 lines
7.0 KiB
Bash

#!/usr/bin/env bash
# shellcheck shell=bash
set -euo pipefail
addon_slug="${SCRUTINY_HA_ADDON_SLUG:-scrutiny}"
data_dir="${SCRUTINY_INFLUXDB_DATA_DIR:-/data/influxdb}"
backup_dir="${SCRUTINY_INFLUXDB_BACKUP_DIR:-/share/${addon_slug}}"
marker_file="${data_dir}/.scrutiny-influxdb-2.9-preflight-complete"
marker_tmp="${marker_file}.tmp"
backup_file="${backup_dir}/influxdb-pre-2.9.tar.gz"
temporary_backup="${backup_file}.tmp"
archive_root="$(basename "$data_dir")"
validation_method="content-sha256-v1"
source_manifest=""
archive_manifest=""
log() {
local level="$1"
local message="$2"
printf 'time="%s" level=%s msg="%s" type=ha-influxdb-upgrade-preflight\n' \
"$(date -u +"%Y-%m-%dT%H:%M:%SZ")" "$level" "$message"
}
cleanup() {
rm -f "$temporary_backup" "$marker_tmp"
if [[ -n "$source_manifest" ]]; then rm -f "$source_manifest"; fi
if [[ -n "$archive_manifest" ]]; then rm -f "$archive_manifest"; fi
}
trap cleanup EXIT
has_existing_data() {
if [[ -e "${data_dir}/influxd.bolt" || -e "${data_dir}/influxd.sqlite" ]]; then
return 0
fi
if [[ -d "${data_dir}/engine" ]] && find "${data_dir}/engine" -mindepth 1 -print -quit | grep -q .; then
return 0
fi
return 1
}
validate_archive() {
local archive="$1"
[[ -s "$archive" ]] || return 1
gzip -t "$archive" >/dev/null 2>&1 || return 1
tar -tzf "$archive" >/dev/null 2>&1
}
reset_manifests() {
rm -f "$source_manifest" "$archive_manifest"
source_manifest=""
archive_manifest=""
}
validate_backup_against_source() {
local archive="$1"
local relative_path
local source_path
local archive_member
local listing
local source_checksum
local archive_checksum
local source_target
local archive_target
validate_archive "$archive" || return 1
source_manifest="$(mktemp)"
archive_manifest="$(mktemp)"
(
cd "$data_dir"
find . -mindepth 1 -print | sed -e 's#^\./##' -e 's#/$##' | LC_ALL=C sort
) > "$source_manifest"
tar -tzf "$archive" | \
sed -e "s#^${archive_root}/##" -e '/^$/d' -e 's#/$##' | \
LC_ALL=C sort > "$archive_manifest"
if ! cmp -s "$source_manifest" "$archive_manifest"; then
reset_manifests
return 1
fi
while IFS= read -r relative_path; do
source_path="${data_dir}/${relative_path}"
archive_member="${archive_root}/${relative_path}"
listing="$(tar -tvzf "$archive" -- "$archive_member" 2>/dev/null)" || {
reset_manifests
return 1
}
if [[ -L "$source_path" ]]; then
[[ "${listing:0:1}" == "l" ]] || {
reset_manifests
return 1
}
source_target="$(readlink "$source_path")"
archive_target="${listing##* -> }"
[[ "$source_target" == "$archive_target" ]] || {
reset_manifests
return 1
}
elif [[ -f "$source_path" ]]; then
[[ "${listing:0:1}" == "-" || "${listing:0:1}" == "h" ]] || {
reset_manifests
return 1
}
source_checksum="$(sha256sum "$source_path" | awk '{print $1}')"
archive_checksum="$(tar -xOzf "$archive" -- "$archive_member" | sha256sum | awk '{print $1}')"
[[ "$source_checksum" == "$archive_checksum" ]] || {
reset_manifests
return 1
}
elif [[ -d "$source_path" ]]; then
[[ "${listing:0:1}" == "d" ]] || {
reset_manifests
return 1
}
else
reset_manifests
return 1
fi
done < "$source_manifest"
reset_manifests
}
marker_value() {
local key="$1"
awk -F= -v key="$key" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' "$marker_file"
}
write_marker() {
local state="$1"
local checksum="${2:-}"
local validation="${3:-}"
{
printf 'state=%s\n' "$state"
if [[ -n "$validation" ]]; then printf 'validation=%s\n' "$validation"; fi
if [[ -n "$checksum" ]]; then printf 'backup_sha256=%s\n' "$checksum"; fi
} > "$marker_tmp"
mv "$marker_tmp" "$marker_file"
}
validate_existing_marker() {
local state
local validation
local expected_checksum
local actual_checksum
state="$(marker_value state)"
validation="$(marker_value validation)"
case "$state" in
no-legacy-data)
if [[ -n "$validation" && "$validation" != "no-data-v1" ]]; then
log error "no-data migration marker uses an unknown validation method"
return 1
fi
if [[ -z "$validation" ]]; then
write_marker no-legacy-data "" no-data-v1
log warning "upgraded no-data migration marker with explicit validation state"
fi
return 0
;;
legacy-backup)
if [[ "$validation" != "$validation_method" ]]; then
log error "legacy backup marker lacks content-level validation proof"
return 1
fi
expected_checksum="$(marker_value backup_sha256)"
if [[ -z "$expected_checksum" ]] || ! validate_archive "$backup_file"; then
log error "legacy backup marker exists but its archive cannot be verified"
return 1
fi
actual_checksum="$(sha256sum "$backup_file" | awk '{print $1}')"
if [[ "$actual_checksum" != "$expected_checksum" ]]; then
log error "legacy backup checksum does not match the migration marker"
return 1
fi
return 0
;;
*)
log error "legacy migration marker lacks content-level validation proof"
return 1
;;
esac
}
mkdir -p "$data_dir"
if [[ -f "$marker_file" ]]; then
if validate_existing_marker; then exit 0; fi
exit 1
fi
if ! has_existing_data; then
write_marker no-legacy-data "" no-data-v1
log info "no existing InfluxDB data detected; recorded no-data migration state"
exit 0
fi
mkdir -p "$backup_dir"
if [[ -e "$backup_file" ]] && validate_backup_against_source "$backup_file"; then
log info "using existing content-verified InfluxDB backup at ${backup_file}"
else
if [[ -e "$backup_file" ]]; then
log warning "existing InfluxDB backup is stale, incomplete, or invalid and will be atomically replaced"
fi
log warning "creating offline InfluxDB backup at ${backup_file}"
tar -C "$(dirname "$data_dir")" -czf "$temporary_backup" "$archive_root"
if ! validate_backup_against_source "$temporary_backup"; then
log error "created InfluxDB backup failed content-level validation"
exit 1
fi
mv -f "$temporary_backup" "$backup_file"
log info "InfluxDB backup created and content-verified at ${backup_file}"
fi
backup_checksum="$(sha256sum "$backup_file" | awk '{print $1}')"
write_marker legacy-backup "$backup_checksum" "$validation_method"
log info "InfluxDB 2.9 migration marker created with content-validation method and backup checksum"