Snapshot of the three AC peak-shaving automations exactly as they run on the HA server today, imported via the config API, plus deploy tooling. This is the baseline the v2 hardening diffs against. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
2.6 KiB
Python
82 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Deploy AC automations from automations/*.yaml to Home Assistant.
|
|
|
|
Source of truth is THIS repo. Home Assistant (remote, API-only) is the deploy
|
|
target. Each YAML file's ``id`` is the key: the script POSTs to
|
|
``/api/config/automation/config/<id>`` which replaces that automation *in place*
|
|
(same id -> same entity_id, run history and traces stay continuous).
|
|
|
|
Usage:
|
|
source ~/.bashrc # provides HA_API_KEY
|
|
python3 deploy.py --check # parse/validate only, no writes
|
|
python3 deploy.py # deploy every automations/*.yaml
|
|
python3 deploy.py ac_peak_ends # deploy only the file(s) whose id matches
|
|
|
|
Env:
|
|
HA_API_KEY long-lived HA token (required to deploy)
|
|
HA_HOST host:port (default 192.168.128.3:8123)
|
|
"""
|
|
import glob
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
import yaml
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
HA_HOST = os.environ.get("HA_HOST", "192.168.128.3:8123")
|
|
TOKEN = os.environ.get("HA_API_KEY")
|
|
|
|
|
|
def load_all():
|
|
autos = []
|
|
for path in sorted(glob.glob(os.path.join(HERE, "automations", "*.yaml"))):
|
|
with open(path) as fh:
|
|
doc = yaml.safe_load(fh)
|
|
if not doc or "id" not in doc:
|
|
sys.exit(f"{path}: missing required 'id'")
|
|
autos.append((path, doc))
|
|
return autos
|
|
|
|
|
|
def deploy(doc):
|
|
url = f"http://{HA_HOST}/api/config/automation/config/{doc['id']}"
|
|
req = urllib.request.Request(
|
|
url, data=json.dumps(doc).encode(), method="POST",
|
|
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req) as r:
|
|
return r.status, r.read().decode().strip()
|
|
except urllib.error.HTTPError as e:
|
|
return e.code, e.read().decode().strip()
|
|
|
|
|
|
def main():
|
|
args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
|
check = "--check" in sys.argv
|
|
autos = load_all()
|
|
if args:
|
|
autos = [(p, d) for p, d in autos if d["id"] in args]
|
|
if not autos:
|
|
sys.exit(f"no automations matched {args}")
|
|
if not check and not TOKEN:
|
|
sys.exit("HA_API_KEY not set (try: source ~/.bashrc)")
|
|
|
|
rc = 0
|
|
for path, doc in autos:
|
|
name = os.path.basename(path)
|
|
if check:
|
|
print(f"ok parse {name} id={doc['id']} alias={doc.get('alias')!r}")
|
|
continue
|
|
status, resp = deploy(doc)
|
|
ok = status == 200
|
|
rc |= 0 if ok else 1
|
|
print(f"{'OK ' if ok else 'ERR'} {status} {name} id={doc['id']} {resp}")
|
|
sys.exit(rc)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|