diff --git a/ansible/oneoff/files/hass_tplink_rehost.py b/ansible/oneoff/files/hass_tplink_rehost.py new file mode 100644 index 0000000..daaa33d --- /dev/null +++ b/ansible/oneoff/files/hass_tplink_rehost.py @@ -0,0 +1,50 @@ +"""Point HA's 9 TP-Link (Kasa) config entries at their new IoT VLAN addresses. + +Run on galactica as the podman user while HA is STOPPED: + python3 /tmp/fix_tplink.py # dry run, prints what would change + python3 /tmp/fix_tplink.py apply # writes the change (refuses unless all 9 match) + +Only data.host is changed; entries are matched by MAC (unique_id). +""" +import json +import os +import re +import sys + +path = "/home/podman/.local/share/volumes/hass/config/.storage/core.config_entries" +new = { + "54af970a5d01": "192.168.2.220", # Printer Plug KP115 + "788cb56955b4": "192.168.2.146", # Driveway String Lights HS200 + "2887ba1bd687": "192.168.2.199", # Kitchen Wall Light KS230 + "5ce9319dc5d6": "192.168.2.38", # Kitchen Lights HS220 + "5ce9319da5e0": "192.168.2.200", # Dining Hall HS220 + "5ce9319dd193": "192.168.2.159", # Dining Room HS220 + "5ce931a23a6e": "192.168.2.61", # Bathroom Hallway HS220 + "f0a731771227": "192.168.2.55", # Stair Light KS230 + "788cb5694e4a": "192.168.2.252", # Bedroom Light HS200 +} +apply = len(sys.argv) > 1 and sys.argv[1] == "apply" + +with open(path) as f: + d = json.load(f) + +n = 0 +for e in d["data"]["entries"]: + if e.get("domain") != "tplink": + continue + mac = re.sub(r"[^0-9a-f]", "", (e.get("unique_id") or "").lower()) + ip = new.get(mac) + print(f'{e["title"]:<36} host={e["data"].get("host")} -> {ip}') + if ip: + e["data"]["host"] = ip + n += 1 +print("matched", n, "of 9") + +if apply: + if n != 9: + sys.exit("refusing to write: not all 9 matched") + tmp = path + ".tmp" + with open(tmp, "w") as f: + json.dump(d, f, indent=4, ensure_ascii=False) + os.replace(tmp, path) + print("written") diff --git a/ansible/oneoff/hass-tplink-rehost.yml b/ansible/oneoff/hass-tplink-rehost.yml new file mode 100644 index 0000000..03b1cba --- /dev/null +++ b/ansible/oneoff/hass-tplink-rehost.yml @@ -0,0 +1,72 @@ +--- +# One-off: repoint Home Assistant's TP-Link (Kasa) config entries at their new +# addresses after the IoT WiFi moved from the Default network to the IoT VLAN +# (192.168.1.x -> 192.168.2.x, fixed IPs reserved in UniFi). +# +# HA 2026.9.3's tplink "Reconfigure" flow ignores the host entered in the form +# and reconnects to the stored one, so it cannot be used to change the address. +# Instead this edits data.host in .storage/core.config_entries while HA is +# stopped. Entries are matched by MAC (unique_id); nothing else is touched. +# +# Run: +# .venv/bin/ansible-playbook -i ansible/inventories/home/hosts.yml \ +# ansible/oneoff/hass-tplink-rehost.yml +- name: Repoint HA tplink entries at the IoT VLAN + hosts: home.debyl.io + gather_facts: false + become: true + become_user: podman + vars: + hass_storage: /home/podman/.local/share/volumes/hass/config/.storage + hass_entries: "{{ hass_storage }}/core.config_entries" + tasks: + - name: Get podman uid for systemctl --user + ansible.builtin.command: id -u + register: podman_uid + changed_when: false + + - name: Dry run - all 9 tplink entries must match before HA is stopped + ansible.builtin.script: + cmd: files/hass_tplink_rehost.py + executable: python3 + register: dry_run + changed_when: false + failed_when: "'matched 9 of 9' not in dry_run.stdout" + + - name: Show dry run + ansible.builtin.debug: + var: dry_run.stdout_lines + + - name: Back up core.config_entries + ansible.builtin.copy: + src: "{{ hass_entries }}" + dest: "{{ hass_entries }}.bak-iot-vlan-20261001" + remote_src: true + mode: preserve + force: false + + - name: Stop HA, rewrite hosts, start HA + environment: + XDG_RUNTIME_DIR: "/run/user/{{ podman_uid.stdout }}" + block: + - name: Stop hass + ansible.builtin.systemd: + name: hass.service + state: stopped + scope: user + + - name: Rewrite tplink hosts + ansible.builtin.script: + cmd: files/hass_tplink_rehost.py apply + executable: python3 + register: applied + + - name: Show result + ansible.builtin.debug: + var: applied.stdout_lines + always: + - name: Start hass + ansible.builtin.systemd: + name: hass.service + state: started + scope: user