HA: git config-sync sidecar (auto-deploy /config from main) #87
@@ -0,0 +1,23 @@
|
||||
apiVersion: ENC[AES256_GCM,data:Vww=,iv:XwauTH7o+2kV/rzEOpolM5dOY2K0eoafjLR/ad1L6v8=,tag:l+lBQ0hvCZ8Yd8xl9oGYAw==,type:str]
|
||||
kind: ENC[AES256_GCM,data:K/2bDqfT,iv:255tg0Sb6Ag6jAdfXrwSecrdg2LqIxXeC1icAm5nDUg=,tag:aYzZOFFcNcjTFDR4zWevpw==,type:str]
|
||||
metadata:
|
||||
name: ENC[AES256_GCM,data:Odt/OY/8RKH2OnMAMz1K65KRjJK5MC7fSIbJ3o7fajs=,iv:UwV65TsXkpgEOyY4CJ7J6dSVTTCAD3+MB8MMWCZ91nU=,tag:35M1V8CqjHeSIdraET/xpQ==,type:str]
|
||||
namespace: ENC[AES256_GCM,data:FbEGPjnxsjbuATia3qs=,iv:eXFQ/c05zjkKp0JaFXDhZyUlybUCYaarpSvuEoXzr4o=,tag:vk35CyJ7m9Fw3CyZtyZtIg==,type:str]
|
||||
type: ENC[AES256_GCM,data:cYQiWwL+,iv:Unemp5U44QLA6yfNQFJzjL2cRrHnvmZmgtkRV8/3FMA=,tag:Wpcnacj66xPSdeZArYjPdg==,type:str]
|
||||
stringData:
|
||||
token: ENC[AES256_GCM,data:AXuCVQ+K5ZkpFqh/sILso6bbltVnGm82NFidoy6YWvrZpQ/7xE73DSCagBNDR1QVS7dgbocI8aj6Fdfea5eqG6kMbqNpvFZdoL1jdJ+W2g+0IpOYm5bjFhC6/NTEELsXHr80/qO0us5sXmn3ZPgFaZpB2kQpZ61zVM9PqMEsB1M7ic2pAvvvR5AXV74iOMAmQMAgpMxCwIMMbZHGQTn6spg3WGjnRhvWMmpJjpaJq3bTYhffhQ2A,iv:4SevydlDJFiIFzmcGCje6cqM8SNrfZNDozXjex72aDs=,tag:rdr18DG2O0qt4r+F7v6egw==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age1yqjvqkwwwxdj7mr546zfs5ckzuwndp69ya2n2drd4vypwns8w4ys04qfy3
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnQzNHUTZTL0lYaG44VTFE
|
||||
ME1ieW9zYzZjVGMzclRnbEJ2aEFoME1Ic3hBCjhWSk1rajJUeDdHbE1QejRHbmlz
|
||||
TkQ3UGV5SDVHZUh0aEwwT1FyMHViR3cKLS0tIHE2Vi93STVsbTVMKzNjQ1hKWlA5
|
||||
a1ZSOFFjbjFoRGNSYUg3YjlVcUZPcDQKoNJDDO1ATI6h6rKUeizcrEq2N7zcGfTy
|
||||
mnVoeKrrlC9WSC5fbLnKxAIF782UxT7sFFQGypIzEulmMPJ+vbuo4g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-06-29T19:20:57Z"
|
||||
mac: ENC[AES256_GCM,data:hl8Ps6bx94hRZ0UR9JvDiT/H15WJgcRKs26lyVcFZIarv2yW7mJkJZ2MfBQc5E3IdoQs/cW89RxiAv4sU6ELS+jlWkQP4gl2gJrgzOelYBVN0bHzvICBN0bV7iRMlr35BSEztQ7P+9GRSsPVt3Gqwtlu1Cby2TEsDhurcX4xclI=,iv:P1ROxx7+Tk7mLGhhEYpbG7cdE8p1se9wJVrgPFyDtZk=,tag:k6+lr579yNZVRY8Po3Md1g==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.10.2
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
# Home Assistant config-sync sidecar.
|
||||
#
|
||||
# /config is a git checkout of gitea.renner.uno/k.renner/homeassistant living on
|
||||
# an RWO PVC. This loop force-syncs ONLY the HA YAML config paths from
|
||||
# origin/<branch> into the working tree, then calls homeassistant.reload_all so
|
||||
# the change goes live. It deliberately does NOT touch:
|
||||
# - www/ (HACS rewrites those tracked files at runtime; leave them to HACS)
|
||||
# - untracked runtime state (.storage/, *.db, secrets.yaml, tts/, deps/, ...)
|
||||
# Git auth uses the PAT already embedded in the PVC's remote URL; reload auth
|
||||
# uses the dedicated long-lived token in $HA_TOKEN.
|
||||
#
|
||||
# Source of truth is git: any uncommitted edit to a synced path made directly in
|
||||
# code-server is overwritten on the next sync. Commit + push to persist.
|
||||
set -eu
|
||||
|
||||
CONFIG_DIR="${CONFIG_DIR:-/config}"
|
||||
BRANCH="${SYNC_BRANCH:-main}"
|
||||
INTERVAL="${SYNC_INTERVAL:-30}"
|
||||
HA_URL="${HA_URL:-http://127.0.0.1:8123}"
|
||||
|
||||
# HA config paths to force-sync from origin/<branch>. Anything not listed here
|
||||
# (notably www/ and untracked runtime files) is left untouched.
|
||||
PATHS="automations.yaml automations packages scripts.yaml templates.yaml configuration.yaml notify.yaml groups.yaml powercalc.yaml blueprints"
|
||||
|
||||
log() { echo "$(date -Iseconds) config-sync: $*"; }
|
||||
|
||||
reload_ha() {
|
||||
if [ -z "${HA_TOKEN:-}" ]; then
|
||||
log "WARN no HA_TOKEN set; files synced but HA not reloaded"
|
||||
return
|
||||
fi
|
||||
if wget -q -O /dev/null \
|
||||
--header="Authorization: Bearer ${HA_TOKEN}" \
|
||||
--header="Content-Type: application/json" \
|
||||
--post-data='{}' \
|
||||
"${HA_URL}/api/services/homeassistant/reload_all"; then
|
||||
log "homeassistant.reload_all OK"
|
||||
else
|
||||
log "ERROR homeassistant.reload_all failed (rc=$?)"
|
||||
fi
|
||||
}
|
||||
|
||||
log "starting (branch=${BRANCH} interval=${INTERVAL}s dir=${CONFIG_DIR})"
|
||||
cd "${CONFIG_DIR}"
|
||||
git config --global --add safe.directory "${CONFIG_DIR}" 2>/dev/null || true
|
||||
|
||||
while true; do
|
||||
if git fetch --quiet origin "${BRANCH}" 2>/dev/null; then
|
||||
REMOTE="$(git rev-parse "origin/${BRANCH}" 2>/dev/null || echo none)"
|
||||
if [ "${REMOTE}" != none ]; then
|
||||
# The ONLY reload trigger: do the synced config paths in the working tree
|
||||
# differ from the remote? Commits that touch only unsynced paths (www/,
|
||||
# shelly_scripts/, docs, ...) advance the ref but never reload.
|
||||
# shellcheck disable=SC2086
|
||||
if ! git diff --quiet "${REMOTE}" -- ${PATHS} 2>/dev/null; then
|
||||
log "config change detected; syncing paths from ${REMOTE}"
|
||||
# Overwrite only the config paths (working tree + index) from the remote.
|
||||
# shellcheck disable=SC2086
|
||||
if git checkout "${REMOTE}" -- ${PATHS} 2>/tmp/sync.err; then
|
||||
# Advance the branch ref to the remote WITHOUT touching the working
|
||||
# tree, so www/ drift and untracked runtime state are preserved while
|
||||
# git status stays clean for the synced paths.
|
||||
git update-ref "refs/heads/${BRANCH}" "${REMOTE}"
|
||||
log "synced config to ${REMOTE}; reloading HA"
|
||||
reload_ha
|
||||
else
|
||||
log "ERROR checkout failed: $(cat /tmp/sync.err 2>/dev/null)"
|
||||
fi
|
||||
elif [ "$(git rev-parse HEAD 2>/dev/null)" != "${REMOTE}" ]; then
|
||||
# Config already matches the remote; a commit touched only unsynced
|
||||
# paths. Advance the ref to keep git status clean — but do NOT reload.
|
||||
git update-ref "refs/heads/${BRANCH}" "${REMOTE}"
|
||||
log "non-config commit; advanced ref to ${REMOTE} (no reload)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "WARN git fetch failed; will retry"
|
||||
fi
|
||||
sleep "${INTERVAL}"
|
||||
done
|
||||
@@ -155,10 +155,52 @@ spec:
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
# Git config-sync sidecar. /config is a git checkout on the PVC; this
|
||||
# force-syncs only the HA YAML config paths from origin/main of the
|
||||
# gitea repo into the working tree, then calls homeassistant.reload_all.
|
||||
# Leaves www/ (HACS-managed) and untracked runtime state (.storage,
|
||||
# *.db, secrets.yaml) untouched. git auth uses the PAT already embedded
|
||||
# in the PVC's remote URL; reload auth uses a dedicated long-lived token.
|
||||
# Runs as root to match HA's root-owned /config files. See config-sync.sh.
|
||||
- name: config-sync
|
||||
image: alpine/git:2.54.0
|
||||
command: ["/bin/sh", "/scripts/sync.sh"]
|
||||
env:
|
||||
- name: SYNC_BRANCH
|
||||
value: main
|
||||
- name: SYNC_INTERVAL
|
||||
value: "30"
|
||||
- name: HA_URL
|
||||
value: http://127.0.0.1:8123
|
||||
- name: HA_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: home-assistant-config-sync-token
|
||||
key: token
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: config-sync-script
|
||||
mountPath: /scripts
|
||||
readOnly: true
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
runAsGroup: 0
|
||||
volumes:
|
||||
- name: config
|
||||
persistentVolumeClaim:
|
||||
claimName: home-assistant-config
|
||||
# Mounts config-sync.sh (via configMapGenerator) read-only at /scripts.
|
||||
- name: config-sync-script
|
||||
configMap:
|
||||
name: ha-config-sync-script
|
||||
defaultMode: 0755
|
||||
- name: proxy-config
|
||||
configMap:
|
||||
name: ha-proxy-config
|
||||
|
||||
@@ -10,6 +10,7 @@ resources:
|
||||
- ingress.yaml
|
||||
- vscode-ingress.yaml
|
||||
- prometheus-token.sops.yaml
|
||||
- config-sync-token.sops.yaml
|
||||
- mosquitto-bootstrap.yaml
|
||||
- mosquitto-config.yaml
|
||||
- mosquitto-pvc.yaml
|
||||
@@ -36,5 +37,12 @@ configMapGenerator:
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- default.conf.template=proxy.conf
|
||||
# config-sync sidecar script. Hash suffix disabled (name stays stable), so
|
||||
# editing config-sync.sh does not auto-roll the pod — restart manually with
|
||||
# `kubectl rollout restart deploy/home-assistant -n home-assistant`.
|
||||
- name: ha-config-sync-script
|
||||
namespace: home-assistant
|
||||
files:
|
||||
- sync.sh=config-sync.sh
|
||||
generatorOptions:
|
||||
disableNameSuffixHash: true
|
||||
|
||||
Reference in New Issue
Block a user