"""Generate private cloud/preview credentials without printing secrets.""" import argparse import hashlib import os from pathlib import Path import secrets import shutil import tempfile ROOT = Path(__file__).resolve().parents[1] def hash_password(password): salt = secrets.token_bytes(16) key = hashlib.scrypt(password.encode(), salt=salt, n=16384, r=8, p=1, dklen=32) return "scrypt$16384$8$1$" + salt.hex() + "$" + key.hex() def upgrade_auth(target, access, preview): if not target.is_file() or not access.is_file(): raise SystemExit("Existing environment and administrator credential files are required.") original = target.read_text(encoding="utf-8") if any(line.startswith("WOLIN_AUTH_PASSWORD_HASH=") for line in original.splitlines()): print("Form login is already configured; credentials were not changed.") return credentials = dict(line.split(": ", 1) for line in access.read_text(encoding="utf-8").splitlines()) replacements = { "WOLIN_AUTH_USER": credentials["Username"], "WOLIN_AUTH_PASSWORD_HASH": "'" + hash_password(credentials["Password"]) + "'", "WOLIN_SESSION_SECRET": secrets.token_urlsafe(48), "AUTH_COOKIE_SECURE": "false" if preview else "true", } lines = [line for line in original.splitlines() if line.partition("=")[0] not in {*replacements, "WOLIN_AUTH_HASH"}] lines.extend(key + "=" + value for key, value in replacements.items()) backup = ROOT / "artifacts" / (target.name + ".before-form-login") if not backup.exists(): shutil.copyfile(target, backup) if os.name != "nt": backup.chmod(0o600) descriptor, temporary = tempfile.mkstemp(prefix="auth-env-", dir=access.parent) try: with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream: stream.write("\n".join(lines) + "\n") os.replace(temporary, target) finally: if os.path.exists(temporary): os.unlink(temporary) print("Upgraded to form login; administrator password and database credentials unchanged.") def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--preview", action="store_true", help="Bind only to localhost:18080 for a local rehearsal") parser.add_argument("--upgrade-auth", action="store_true", help="Replace legacy Basic Auth configuration while keeping existing passwords") args = parser.parse_args() target = ROOT / (".env.preview" if args.preview else ".env.cloud") access = ROOT / "artifacts" / ("preview-access.txt" if args.preview else "cloud-access.txt") if args.upgrade_auth: upgrade_auth(target, access, args.preview) return if target.exists() or access.exists(): raise SystemExit("Refusing to overwrite existing credentials; keep your existing files.") password = secrets.token_urlsafe(24) password_hash = hash_password(password) preview = args.preview lines = [ "COMPOSE_PROJECT_NAME=" + ("wolin-cloud-preview" if preview else "wolin-cloud"), "SITE_ADDRESS=" + ("http://localhost" if preview else "zhouxing199901.site"), "HTTP_BIND=" + ("127.0.0.1" if preview else "0.0.0.0"), "HTTP_PORT=" + ("18080" if preview else "80"), "HTTPS_PORT=" + ("18443" if preview else "443"), "DB_PASSWORD=" + secrets.token_urlsafe(32), "DB_ROOT_PASSWORD=" + secrets.token_urlsafe(32), "WOLIN_AUTH_USER=admin", "WOLIN_AUTH_PASSWORD_HASH='" + password_hash + "'", "WOLIN_SESSION_SECRET=" + secrets.token_urlsafe(48), "AUTH_COOKIE_SECURE=" + ("false" if preview else "true"), "DEEPSEEK_API_KEY=", "DEEPSEEK_MODEL=deepseek-flash", ] access.parent.mkdir(parents=True, exist_ok=True) # Exclusive creation prevents accidentally rotating a running database's credentials. with target.open("x", encoding="utf-8", newline="\n") as stream: stream.write("\n".join(lines) + "\n") with access.open("x", encoding="utf-8", newline="\n") as stream: stream.write("Username: admin\nPassword: " + password + "\n") if os.name != "nt": target.chmod(0o600) access.chmod(0o600) print("Created", target) print("Administrator credentials saved privately to", access) if __name__ == "__main__": main()