Files
2026-09-21 17:30:02 +08:00

97 lines
4.3 KiB
Python

"""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()