feat(app): node-override secrets UX + supabase scaffold refresh
- Secrets page: 'Node overrides' section (per-node values that shadow a shared
secret on that node) — list/reveal/edit/delete + add; shared secrets show an
'override: <node>' badge. Authority-only writes.
- API: GET /secrets/overrides, GET/PUT/DELETE /secrets/overrides/{node}/{name};
OpenBaoBackend.list_node_overrides enumerates castle/nodes/*
- supabase scaffold: drop stale 'cat ~/.castle/secrets/...' + 'castle deploy',
point at the dashboard Secrets page / castle apply
- tests: file-backend override endpoints (empty/400/404)
Verified live: primer's postgres override discoverable + full CRUD round-trip.
This commit is contained in:
@@ -104,15 +104,29 @@ class OpenBaoBackend:
|
||||
self._request("DELETE", url)
|
||||
|
||||
def list_names(self) -> list[str]:
|
||||
url = f"{self._addr}/v1/{self._mount}/metadata?list=true"
|
||||
# Drop folder entries (e.g. "nodes/") — those group per-node overrides.
|
||||
return sorted(k for k in self._list_path("") if not k.endswith("/"))
|
||||
|
||||
def _list_path(self, prefix: str) -> list[str]:
|
||||
sep = "/" if prefix else ""
|
||||
url = f"{self._addr}/v1/{self._mount}/metadata{sep}{prefix}?list=true"
|
||||
try:
|
||||
data = self._request("GET", url)
|
||||
keys = data.get("data", {}).get("keys", [])
|
||||
# Drop folder entries (e.g. "nodes/") — those group per-node overrides.
|
||||
return sorted(k for k in keys if not k.endswith("/"))
|
||||
return self._request("GET", url).get("data", {}).get("keys", [])
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def list_node_overrides(self) -> dict[str, list[str]]:
|
||||
"""Per-node secret overrides: ``{host: [names]}`` from ``<mount>/nodes/*``."""
|
||||
out: dict[str, list[str]] = {}
|
||||
for entry in self._list_path("nodes"):
|
||||
if not entry.endswith("/"):
|
||||
continue
|
||||
host = entry.rstrip("/")
|
||||
names = [k for k in self._list_path(f"nodes/{host}") if not k.endswith("/")]
|
||||
if names:
|
||||
out[host] = sorted(names)
|
||||
return out
|
||||
|
||||
def _request(self, method: str, url: str, body: dict | None = None) -> dict:
|
||||
payload = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request( # noqa: S310
|
||||
|
||||
Reference in New Issue
Block a user