"""wildpc secret — read/write secrets in the *active* backend. The active backend (file or openbao) is selected by the ``secrets:`` block of wildpc.yaml (env overrides). Writing a secret by hand means knowing that choice and the backend's storage layout — get it wrong and the value lands somewhere the resolver never reads, so ``${secret:NAME}`` silently degrades to a ```` placeholder that a service then uses as if it were the real credential (exactly how immich's DB password went to a file on an OpenBao fleet). This command routes every read/write through :func:`wildpc_core.config.active_secret_backend`, so there's no wrong store to pick. ``wildpc apply`` refuses to converge a deployment whose secrets don't resolve here. """ from __future__ import annotations import argparse import getpass import sys from wildpc_core.config import active_backend_name, active_secret_backend def run_secret(args: argparse.Namespace) -> int: sub = getattr(args, "secret_command", None) if not sub: print("Usage: wildpc secret {list|set|get|rm}") return 1 if sub == "list": return _list() if sub == "set": return _set(args.name, args.value) if sub == "get": return _get(args.name) if sub == "rm": return _rm(args.name, getattr(args, "yes", False)) return 1 def _list() -> int: backend = active_backend_name() names = active_secret_backend().list_names() if not names: print(f"No secrets in the active '{backend}' backend.") return 0 print(f"Secrets in the active '{backend}' backend ({len(names)}):") for n in names: print(f" {n}") return 0 def _set(name: str, value: str | None) -> int: backend = active_backend_name() if value is None: # No value on the argv (keeps it out of shell history / ps). Read from a # hidden prompt when interactive, else from stdin (pipe-friendly). if sys.stdin.isatty(): value = getpass.getpass(f"Value for {name}: ") else: value = sys.stdin.read().strip() if not value: print("Error: empty value — refusing to set.", file=sys.stderr) return 1 active_secret_backend().write(name, value) print(f"Set '{name}' in the active '{backend}' backend.") return 0 def _get(name: str) -> int: value = active_secret_backend().read(name) if value is None: print( f"Secret '{name}' not found in the active '{active_backend_name()}' backend.", file=sys.stderr, ) return 1 print(value) return 0 def _rm(name: str, yes: bool) -> int: backend = active_backend_name() if active_secret_backend().read(name) is None: print(f"Secret '{name}' not found in the active '{backend}' backend.", file=sys.stderr) return 1 if not yes: reply = input(f"Delete secret '{name}' from the '{backend}' backend? [y/N] ") if reply.strip().lower() not in ("y", "yes"): print("Aborted.") return 1 active_secret_backend().delete(name) print(f"Deleted '{name}' from the active '{backend}' backend.") return 0