From c10d2f61b016353e5036f55f0533dac2279a421c Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 8 Aug 2026 14:05:08 +0200 Subject: [PATCH 1/2] fix: refresh Butler vault cache before DNS update --- app.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index b2cfa6b..32078e7 100644 --- a/app.py +++ b/app.py @@ -977,10 +977,26 @@ print(backup) return {"status": "reloaded", "backup": backup} -async def _upsert_dns_records(zone: str, name: str) -> dict: +def _get_hetzner_dns_token() -> str: + token = _read("HETZNER_DNS_TOKEN") + if token: + return token + rc, _out, err = _ssh( + "sascha@10.4.1.116", + "sudo /app-config/homelab-butler/vault-sync.sh", + timeout=120, + ) + if rc != 0: + raise RuntimeError(f"Vault cache sync failed: {err[-300:]}") + _load_vault_cache() token = _read("HETZNER_DNS_TOKEN") if not token: - raise RuntimeError("HETZNER_DNS_TOKEN is unavailable") + raise RuntimeError("HETZNER_DNS_TOKEN is unavailable after vault sync") + return token + + +async def _upsert_dns_records(zone: str, name: str) -> dict: + token = await asyncio.to_thread(_get_hetzner_dns_token) headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} api = "https://api.hetzner.cloud/v1" async with httpx.AsyncClient(timeout=30) as client: -- 2.49.1 From cf48a80b4d9be457d5f809158eb052160827f804 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 8 Aug 2026 14:05:08 +0200 Subject: [PATCH 2/2] test: cover automatic vault cache refresh --- tests/test_app.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_app.py b/tests/test_app.py index 681fae3..e6922c4 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -309,3 +309,16 @@ def test_proxy_route_endpoint_configures_caddy_and_dns(monkeypatch): ("caddy", "speed.guck.tv", "127.0.0.1:8080"), ("dns", "guck.tv", "speed"), ] + + +def test_hetzner_token_refreshes_vault_cache_when_missing(monkeypatch): + reads = iter([None, "refreshed-token"]) + calls = [] + monkeypatch.setattr(app, "_read", lambda _name: next(reads)) + monkeypatch.setattr(app, "_load_vault_cache", lambda: calls.append("reload")) + monkeypatch.setattr(app, "_ssh", lambda host, command, timeout=600: (calls.append((host, command, timeout)) or (0, "vault-sync: ok", ""))) + + assert app._get_hetzner_dns_token() == "refreshed-token" + assert calls[0][0] == "sascha@10.4.1.116" + assert calls[0][1] == "sudo /app-config/homelab-butler/vault-sync.sh" + assert calls[1] == "reload" -- 2.49.1