"""Per-site secret generation + SOPS/age encryption of the .env file.""" from __future__ import annotations import secrets as _secrets from pathlib import Path from .config import Config, Site from .runner import Runner def generate_password(nbytes: int = 24) -> str: """URL-safe token (~32 chars); no shell-special characters.""" return _secrets.token_urlsafe(nbytes) def db_env_key(site: Site) -> str: return "WORDPRESS_DB_PASSWORD" if site.profile == "wordpress" else "DB_PASSWORD" def write_env(runner: Runner, out_dir: Path, mapping: dict[str, str]) -> None: body = "".join(f"{k}={v}\n" for k, v in mapping.items()) runner.write_file(out_dir / ".env", body, mode=0o600, secret=True) def encrypt_env(runner: Runner, cfg: Config, out_dir: Path) -> None: """Encrypt .env → secrets.enc.yaml with SOPS/age (the committed artifact).""" if not cfg.sops_age_recipient: runner._echo("WARN no sops_age_recipient configured; skipping encryption " "(.env is git-ignored but secrets.enc.yaml will not be created)") return if runner.dry_run: runner._echo(f"DRY sops --encrypt {out_dir/'.env'} > {out_dir/'secrets.enc.yaml'}") return out = runner.run( ["sops", "--encrypt", "--age", cfg.sops_age_recipient, "--input-type", "dotenv", "--output-type", "yaml", str(out_dir / ".env")], capture=True, ) runner.write_file(out_dir / "secrets.enc.yaml", out)