feat: SMTP mail stack and separate server.env deploy
Add mail.php with STARTTLS sending, test scripts, server.env split from local .env, admin@ postbox docs, DKIM DNS support-ticket notes, and todo 11 for boka relay. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
-3
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deploy public/, config/, and .env to wint.global / Plesk via FTP/FTPS."""
|
||||
"""Deploy public/, config/, and server.env to wint.global / Plesk via FTP/FTPS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -85,6 +85,21 @@ def upload_file(ftp: ftplib.FTP, local: Path, remote_path: str) -> None:
|
||||
print(f" ↑ {local.relative_to(ROOT)} → {remote_path}")
|
||||
|
||||
|
||||
def delete_remote_file(ftp: ftplib.FTP, remote_path: str) -> None:
|
||||
remote_path = remote_path.replace("\\", "/").lstrip("/")
|
||||
remote_dir = os.path.dirname(remote_path)
|
||||
remote_name = os.path.basename(remote_path)
|
||||
try:
|
||||
if remote_dir:
|
||||
ensure_remote_dir(ftp, remote_dir)
|
||||
else:
|
||||
cwd_to_root(ftp)
|
||||
ftp.delete(remote_name)
|
||||
print(f" ✕ deleted {remote_path}")
|
||||
except ftplib.error_perm as exc:
|
||||
print(f" (skip delete {remote_path}: {exc})")
|
||||
|
||||
|
||||
def upload_tree(ftp: ftplib.FTP, local_dir: Path, remote_base: str) -> None:
|
||||
remote_base = remote_base.strip("/")
|
||||
for path in sorted(local_dir.rglob("*")):
|
||||
@@ -101,6 +116,13 @@ def main() -> None:
|
||||
env = load_env(ROOT / ".env")
|
||||
require_env(env, "FTP_HOST", "FTP_USER", "FTP_PASSWORD")
|
||||
|
||||
server_env = ROOT / "server.env"
|
||||
if not server_env.is_file():
|
||||
sys.exit(
|
||||
"Missing server.env — run: python3 scripts/sync-server-env.py\n"
|
||||
"(or copy server.env.example to server.env and fill in server values)"
|
||||
)
|
||||
|
||||
remote_web = env.get("FTP_REMOTE_PATH", "httpdocs").strip("/")
|
||||
remote_private = env.get("FTP_PRIVATE_PATH", "private").strip("/")
|
||||
|
||||
@@ -109,8 +131,10 @@ def main() -> None:
|
||||
config_files = sum(1 for p in (ROOT / "config").rglob("*") if p.is_file())
|
||||
print(f" public/ ({public_files} files) → {remote_web}/")
|
||||
print(f" config/ ({config_files} files) → {remote_private}/config/")
|
||||
print(f" .env → {remote_private}/.env")
|
||||
print(f" server.env → {remote_private}/server.env")
|
||||
print(f" (remove legacy {remote_private}/.env if present)")
|
||||
print(f" (mkdir) {remote_private}/data/")
|
||||
print(" .env stays local — not deployed")
|
||||
|
||||
if args.dry_run:
|
||||
return
|
||||
@@ -122,7 +146,8 @@ def main() -> None:
|
||||
upload_tree(ftp, ROOT / "public", remote_web)
|
||||
upload_tree(ftp, ROOT / "config", f"{remote_private}/config")
|
||||
ensure_remote_dir(ftp, f"{remote_private}/data")
|
||||
upload_file(ftp, ROOT / ".env", f"{remote_private}/.env")
|
||||
upload_file(ftp, server_env, f"{remote_private}/server.env")
|
||||
delete_remote_file(ftp, f"{remote_private}/.env")
|
||||
print("\nDone.")
|
||||
finally:
|
||||
ftp.quit()
|
||||
|
||||
Reference in New Issue
Block a user