ha/matter: remove completed HAOS migration-seed init containers #74

Merged
k.renner merged 1 commits from drop-haos-migration-seeds into master 2026-06-27 10:37:59 +00:00
7 changed files with 0 additions and 294 deletions
@@ -30,126 +30,6 @@ spec:
operator: Equal
value: pinned
effect: NoSchedule
initContainers:
# Stage the HAOS backup tarball into /config/backups/ and write
# /config/.HA_RESTORE so HA Core's native backup_restore.py performs
# the restore on its own startup. We don't extract anything here —
# HA decrypts + restores during its boot, then writes
# /config/.HA_RESTORE_RESULT (the trusted-proxies sidecar waits
# for that signal).
#
# Lives in the app pod (not a separate Job) so the RWO Ceph-RBD
# PVC is shared via the same kubelet mount — Multi-Attach is
# impossible by construction. Necessary now that production may
# have several edge nodes; the prior seed Job + manual `zone:edge`
# mirror only worked while there was a single edge node guaranteeing
# co-location with the app pod.
#
# Idempotency sentinel under /config/backups/ (the only dir HA's
# restore preserves), with a name HA will never write itself — so
# we can't be fooled by a partial-restore-then-default-config
# write of /config/configuration.yaml.
- name: seed
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
SENTINEL=/config/backups/.k8s-seed-applied
if [ -f "$SENTINEL" ]; then
echo "Sentinel $SENTINEL present — seed already applied."
exit 0
fi
apk add --no-cache aws-cli jq ca-certificates >/dev/null
export HOME=/tmp AWS_ENDPOINT_URL="$S3_ENDPOINT" AWS_DEFAULT_REGION=garage
mkdir -p "$HOME/.aws"
printf '[default]\ns3 =\n addressing_style = path\n' > "$HOME/.aws/config"
# NB: list with $S3_PREFIX (no trailing slash). The HACS
# `s3_compatible` uploader on legacy HA writes newer backups
# as `home-assistantAutomatic_backup_…tar` — concatenated
# prefix+filename without a `/` separator (older uploads
# had the slash, e.g. `home-assistant/Automatic_backup_…`).
# A `home-assistant/` filter would only see the older
# uploads; a `home-assistant` filter sees both. No other
# objects in this bucket start with `home-assistant`, so
# the looser prefix doesn't introduce false matches.
echo "Waiting for HAOS backup under s3://$S3_BUCKET/$S3_PREFIX* ..."
BACKUP_KEY=""
for i in $(seq 1 1080); do
BACKUP_KEY=$(aws s3api list-objects-v2 \
--bucket "$S3_BUCKET" --prefix "$S3_PREFIX" --output json 2>/dev/null \
| jq -r '
(.Contents // [])
| map(select(.Key | endswith(".tar")))
| sort_by(.LastModified)
| (last // {}).Key // ""
')
if [ -n "$BACKUP_KEY" ]; then
echo "Newest backup: $BACKUP_KEY"
break
fi
echo " none yet ($i/1080) — sleeping 10s"; sleep 10
if [ "$i" = "1080" ]; then
echo "ERROR: no backup appeared after 3 hours"; exit 1
fi
done
mkdir -p /config/backups
BACKUP_FILE="/config/backups/$(basename "$BACKUP_KEY")"
if [ -f "$BACKUP_FILE" ]; then
echo "Backup already staged at $BACKUP_FILE (re-run); skipping download."
else
echo "Streaming backup → $BACKUP_FILE ..."
aws s3 cp "s3://$S3_BUCKET/$BACKUP_KEY" "$BACKUP_FILE"
fi
if [ -z "${HA_BACKUP_PASSWORD:-}" ] || [ "${HA_BACKUP_PASSWORD}" = "REPLACE_ME_WITH_HA_BACKUP_PASSWORD" ]; then
echo "ERROR: HA_BACKUP_PASSWORD not set or still the placeholder."
exit 1
fi
echo "Writing /config/.HA_RESTORE manifest ..."
# Schema per homeassistant/backup_restore.py:
# path, password, remove_after_restore, restore_database, restore_homeassistant
jq -n \
--arg path "$BACKUP_FILE" \
--arg pw "$HA_BACKUP_PASSWORD" \
'{path: $path, password: $pw, remove_after_restore: false, restore_database: true, restore_homeassistant: true}' \
> /config/.HA_RESTORE
chmod 600 /config/.HA_RESTORE
touch "$SENTINEL"
echo "Done. HA will perform the restore on next startup."
ls -la /config
env:
- name: S3_ENDPOINT
value: "https://s3.nas-1.renner.uno"
- name: S3_BUCKET
value: "k8s-migration"
- name: S3_PREFIX
value: "home-assistant"
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: garage-migration-creds
key: AWS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-migration-creds
key: AWS_SECRET_ACCESS_KEY
- name: HA_BACKUP_PASSWORD
valueFrom:
secretKeyRef:
name: home-assistant-backup-password
key: backup-password
volumeMounts:
- name: config
mountPath: /config
containers:
- name: home-assistant
image: ghcr.io/home-assistant/home-assistant:2026.6.4 # {"$imagepolicy": "flux-system:home-assistant"}
@@ -1,24 +0,0 @@
apiVersion: ENC[AES256_GCM,data:UCE=,iv:Sb1RKkCPMn9/JGwweTfU+K7vTuPyiHIwRkpFeM3Nmic=,tag:qWr9HyEsVS/Oncm+sppEGw==,type:str]
kind: ENC[AES256_GCM,data:h4LllnCZ,iv:sjf5XV2OD6wlvnlPLGYrfPMllXE6ORB4t/Gw9s/jDGI=,tag:n0ieyCsjp/F4zRU2IuH2dQ==,type:str]
metadata:
name: ENC[AES256_GCM,data:I1tg5Yg8ei78x5T3kzcMlTnyxCNkuw==,iv:IgTYTERWRBmMIHOGGOvwLrf0abSmbnAfKTZQFaLOxeI=,tag:okjjWlCTGIx4f7gibToZkA==,type:str]
namespace: ENC[AES256_GCM,data:6yrtXH8FLSEJhoFQ05M=,iv:XldOjworGBXl6tAN3feodVEDNyf5Cq7dvHWmqlbO4jE=,tag:Mmn+7YJ1czynDAk0rA3G/Q==,type:str]
type: ENC[AES256_GCM,data:FcD8FKHj,iv:k8y8EspY59lA3dfc2EaP0RKwFoSn48rxUqcKuRnQkNU=,tag:5o/7y5OpOeH6aSN/qyXt2w==,type:str]
stringData:
AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:AXrSeWOVcf8Ejejq9TlQQF/SIUcWyRG1lkw=,iv:Ts1ACdmZdU6lmSHwo+o50gI50Nr5FzF3Qav2PH99rBQ=,tag:/Mgfme9+qlSLQTFohSasFQ==,type:str]
AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:kVpzs+fJCNap6BX2xeYCb09Tz5BpOwl+FJ+rdQobqmOXr9WKcXTqVoxqTmEztl70VwnCCprMFYLonemrPc+HKw==,iv:4Zs+OfjcWVJbebgXk4C9HNa0bsw5ouSWgQRhxjA9WaM=,tag:MNivqJSBeaIv7GFxmXsbkA==,type:str]
sops:
age:
- recipient: age1yqjvqkwwwxdj7mr546zfs5ckzuwndp69ya2n2drd4vypwns8w4ys04qfy3
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqMGQ5K2h3ZnlScmVsR1Bo
ODA4UVNhYnkrNzk0K1NwMHZMQWoyUkcyZVJJCkRnTDNLb1hZWVE0R2Fza1pTZFZ6
VGRtTXdYc1VOQnFVSUJnamlDTitrZEkKLS0tIGtvbXU4ekpjZ1RWaW1FamxXOG9a
emdyVmtwZ2Y3aUsvcHg1R0trbTQ4SGMKSbNFTTUX13cepErkmF2J+VqbM6cKFFjj
vWLJeShGziD9TSuzCvFoiGKhLv15FF2bDaI5YakFDdvB1jC6n5rCxA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-04-24T16:13:25Z"
mac: ENC[AES256_GCM,data:1vH6n6wzkoEbGGJlmB9kFi5XaViQHhNlNCx3srkql7DsF3PCrajd/eO5Y+vBkO8LBoTgh4cH53lThhQqpPEkOX1Ek/nz17aTg0YN4ocDOn/K224Ck9bisl6Uz3GzyEuOrPidVzNoKwmWR4Xc33glUN4DH0TRN4HKSIvAh/v/pgo=,iv:R+bu2j/SWaks0n0sFjtzX6DLSy9Sf8o5CtBXfxMBdtk=,tag:TeOBTzjB+5uS29i0SeNzhw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2
@@ -1,7 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- garage-migration-creds.sops.yaml
- backup-password.sops.yaml
- pvcs.yaml
- deployment.yaml
@@ -36,119 +36,6 @@ spec:
operator: Equal
value: pinned
effect: NoSchedule
initContainers:
# One-shot import of matter-server controller state from the HAOS
# supervisor backup that's already in s3://k8s-migration/home-
# assistant/ for HA Core's restore. Same shape as HA's `seed`
# init: sentinel-guarded idempotency, stream-from-S3, alpine +
# apk for tooling. The addon archive (`core_matter_server.tar.gz`)
# is HA-securetar-encrypted; haos-import-script ConfigMap carries
# the small Python helper that the apk-installed alpine doesn't
# have a dedicated CLI for.
- name: seed
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
SENTINEL=/data/.haos-import-applied
if [ -f "$SENTINEL" ]; then
echo "Sentinel $SENTINEL present — already imported."
exit 0
fi
apk add --no-cache aws-cli jq ca-certificates py3-pip tar coreutils >/dev/null
pip install --break-system-packages --quiet securetar
export HOME=/tmp AWS_ENDPOINT_URL="$S3_ENDPOINT" AWS_DEFAULT_REGION=garage
mkdir -p "$HOME/.aws"
printf '[default]\ns3 =\n addressing_style = path\n' > "$HOME/.aws/config"
echo "Looking for HAOS backup under s3://$S3_BUCKET/$S3_PREFIX/ ..."
BACKUP_KEY=$(aws s3api list-objects-v2 \
--bucket "$S3_BUCKET" --prefix "$S3_PREFIX/" --output json 2>/dev/null \
| jq -r '
(.Contents // [])
| map(select(.Key | endswith(".tar")))
| sort_by(.LastModified)
| (last // {}).Key // ""
')
if [ -z "$BACKUP_KEY" ]; then
echo "ERROR: no backup found under s3://$S3_BUCKET/$S3_PREFIX/"
exit 1
fi
echo "Newest backup: $BACKUP_KEY"
# The outer backup tar isn't encrypted — pull just the
# 4.5 MB matter addon archive out of the S3 stream rather
# than staging the full 1 GB tar to disk.
mkdir -p /tmp/work
echo "Streaming core_matter_server.tar.gz out of the backup ..."
aws s3 cp "s3://$S3_BUCKET/$BACKUP_KEY" - \
| tar -xO core_matter_server.tar.gz > /tmp/work/core_matter_server.tar.gz
ls -la /tmp/work/
# The addon archive is securetar-encrypted; decrypt via the
# mounted helper.
mkdir -p /tmp/work/extracted
python3 /scripts/import.py \
/tmp/work/core_matter_server.tar.gz \
/tmp/work/extracted \
"$HA_BACKUP_PASSWORD"
echo "Extracted addon archive contents:"
ls -la /tmp/work/extracted
# HAOS addon backups put the addon's persistent /data
# contents under a `data/` subdirectory; locate it via the
# canonical filename rather than assuming a fixed layout.
SRC=$(find /tmp/work/extracted -name chip.json -type f -print -quit)
if [ -z "$SRC" ]; then
echo "ERROR: chip.json not found in extracted addon archive"
find /tmp/work/extracted -maxdepth 3 -type f
exit 1
fi
SRC_DIR=$(dirname "$SRC")
echo "Found addon /data at: $SRC_DIR"
# Replace the freshly-initialised /data with the imported
# state. The new pod's first boot wrote a baseline
# chip.json + empty fabric file; they must be overwritten
# so the imported fabric and security counters take effect.
cp -aT "$SRC_DIR" /data
echo "Files in /data after import:"
ls -la /data
touch "$SENTINEL"
echo "Done. Sentinel $SENTINEL written."
env:
- name: S3_ENDPOINT
value: "https://s3.nas-1.renner.uno"
- name: S3_BUCKET
value: "k8s-migration"
- name: S3_PREFIX
value: "home-assistant"
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: garage-migration-creds
key: AWS_ACCESS_KEY_ID
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: garage-migration-creds
key: AWS_SECRET_ACCESS_KEY
- name: HA_BACKUP_PASSWORD
valueFrom:
secretKeyRef:
name: home-assistant-backup-password
key: backup-password
volumeMounts:
- name: data
mountPath: /data
- name: import-script
mountPath: /scripts
readOnly: true
containers:
- name: matter-server
# Use the HAOS-published image from `ghcr.io/matter-js/...`,
@@ -193,6 +80,3 @@ spec:
- name: data
persistentVolumeClaim:
claimName: matter-server-data
- name: import-script
configMap:
name: matter-server-haos-import-script
@@ -1,28 +0,0 @@
---
# Decryption helper for the HAOS supervisor-backup matter-server addon
# archive. The outer backup tar is plain — `tar -xO` can pull
# `core_matter_server.tar.gz` straight out of an S3 stream — but each
# inner addon archive is HA's securetar format (magic bytes
# `SecureTar\x03`), which only the `securetar` Python lib can decrypt.
# Mounted into the matter-server seed init container and invoked once,
# guarded by the sentinel at /data/.haos-import-applied.
apiVersion: v1
kind: ConfigMap
metadata:
name: matter-server-haos-import-script
namespace: home-assistant
data:
import.py: |
"""Decrypt a securetar-encrypted addon archive and extract it."""
import sys
from pathlib import Path
from securetar import SecureTarFile
src = Path(sys.argv[1])
dst = Path(sys.argv[2])
password = sys.argv[3]
dst.mkdir(parents=True, exist_ok=True)
with SecureTarFile(src, password=password, gzip=True) as tar:
tar.extractall(dst)
@@ -6,4 +6,3 @@ resources:
- deployment.yaml
- service.yaml
- network-attachment.yaml
- haos-import-script.yaml
@@ -76,10 +76,6 @@ spec:
- name: infrastructure
- name: infrastructure-crds
- name: infrastructure-ceph-cluster
# The seed initContainer mounts `garage-migration-creds`, which is owned
# by the home-assistant Kustomization. Without this dependency the init
# container CreateContainerConfigError-loops until HA's KS lands first.
- name: home-assistant
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization