mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-21 12:23:32 +02:00
* 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
229 lines
7.0 KiB
Bash
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"
|