recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit)
Sanitized single-commit public mirror of recipe-maintainer. - Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders. - Removed plans/ and planned-updates/ (deployment-planning docs) so no client/ deployment domains appear in the public repo. - All other secret stores were already gitignored. - docs.coopcloud.tech retained as a submodule (public upstream).
This commit is contained in:
82
lib/secrets.py
Normal file
82
lib/secrets.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""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/<domain>.
|
||||
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/<domain>."""
|
||||
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/<domain>.
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user