"""Secret resolution, syncing, and reading from containers. Replaces the bash scripts resolve-secrets.sh and sync-secrets.sh with Python equivalents that work with the new deployment model. """ from pathlib import Path from lib.config import paths, load_recipe_toml from lib.env import read_env_file from lib.ssh import read_container_secrets def secrets_dir() -> Path: """Return the secrets directory: recipe-info/testsecrets/.""" return paths.TESTSECRETS_DIR def load_secrets(domain: str) -> dict[str, str]: """Load locally-stored secrets for a deployment. Reads from recipe-info/testsecrets/. Returns empty dict if no secrets file exists. """ p = secrets_dir() / domain if not p.exists(): return {} return read_env_file(p) def save_secrets(domain: str, secrets: dict[str, str]) -> Path: """Save secrets to recipe-info/testsecrets/.""" d = secrets_dir() d.mkdir(parents=True, exist_ok=True) p = d / domain with open(p, "w") as f: for name in sorted(secrets.keys()): f.write(f"{name}={secrets[name]}\n") return p def sync_secrets_for_deployment(server: str, domain: str) -> dict[str, str]: """Sync secrets from running containers to local file. SSHes into the server, reads /run/secrets/ from all containers of the stack, and saves to recipe-info/testsecrets/. Returns the secrets dict. """ stack_prefix = domain.replace(".", "_") secrets = read_container_secrets(server, stack_prefix) if secrets: save_secrets(domain, secrets) print(f" Wrote {len(secrets)} secrets for {domain}", flush=True) else: print(f" No secrets found for {domain}", flush=True) return secrets def sync_all_secrets(server: str) -> dict[str, dict]: """Sync secrets for all recipes that have a recipe.toml. Returns {domain: {secret_name: value}}. """ from lib.models import load_default_instance inst = load_default_instance() recipe_info_dir = paths.RECIPE_INFO_DIR if not recipe_info_dir.exists(): return {} results = {} for d in sorted(recipe_info_dir.iterdir()): recipe_toml = d / "recipe.toml" if d.is_dir() and recipe_toml.exists(): recipe_name = d.name domain = f"{recipe_name}.{inst.domain_suffix}" print(f"=== {recipe_name} ({domain}) ===", flush=True) results[domain] = sync_secrets_for_deployment( server, domain ) return results