Files
gitlab-instance-0a899031_no…/deploy/nomadro-live/remote_provision.py
2026-08-24 04:35:48 -05:00

217 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Provision nomadro.live English WordPress + audience router (non-destructive).
What it does:
1) Uploads a fresh WordPress compose stack to /var/www/nomadro-live-wp (on 127.0.0.1:8089).
2) Uploads a dedicated audience router to /var/www/nomadro-live-router (on 127.0.0.1:4404).
3) Updates /etc/caddy/sites/nomadro.live.caddy so:
- /api/* continues to go to the existing FastAPI backend (:8015)
- other pages:
zh -> existing Next.js frontend (:3015)
en/default -> new WordPress (:8089)
4) Boots the router via systemd and runs bootstrap-wp.sh to seed 20 English articles.
"""
from __future__ import annotations
import os
import secrets
import string
import sys
import time
from pathlib import Path
import paramiko
HOST = "107.173.30.245"
USER = "root"
PASSWORD = os.environ.get("NOMADRO_SSH_PASS", "")
REMOTE_WP_DIR = "/var/www/nomadro-live-wp"
REMOTE_ROUTER_DIR = "/var/www/nomadro-live-router"
REMOTE_SYSTEMD_SERVICE = "/etc/systemd/system/nomadro-live-router.service"
REMOTE_CADDY_SITE = "/etc/caddy/sites/nomadro.live.caddy"
LOCAL_DIR = Path(__file__).resolve().parent
WP_LOCAL_DIR = LOCAL_DIR / "wordpress"
ROUTER_LOCAL_DIR = LOCAL_DIR / "router"
SYSTEMD_LOCAL_FILE = ROUTER_LOCAL_DIR / "nomadro-live-router.service"
CADDY_LOCAL_FILE = LOCAL_DIR / "caddy" / "nomadro.live.caddy"
def gen_pass(n: int = 24) -> str:
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(n))
def run(ssh: paramiko.SSHClient, cmd: str, timeout: int = 600) -> None:
print(f"\n$ {cmd}")
_stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout, get_pty=True)
out = stdout.read().decode(errors="replace")
err = stderr.read().decode(errors="replace")
def safe_print(s: str, file=None) -> None:
text = s.encode("ascii", "backslashreplace").decode("ascii")
print(text, file=file) if file is not None else print(text)
if out.strip():
safe_print(out[-4000:])
if err.strip():
safe_print(err[-2000:], file=sys.stderr)
code = stdout.channel.recv_exit_status()
if code != 0:
raise RuntimeError(f"Command failed ({code}): {cmd}")
def sftp_mkdirs(sftp: paramiko.SFTPClient, remote: str) -> None:
parts = remote.strip("/").split("/")
cur = ""
for part in parts:
cur += "/" + part
try:
sftp.stat(cur)
except FileNotFoundError:
sftp.mkdir(cur)
def upload_tree(sftp: paramiko.SFTPClient, local: Path, remote: str) -> None:
sftp_mkdirs(sftp, remote)
text_ext = {".sh", ".mjs", ".js", ".json", ".yml", ".yaml", ".html", ".php", ".txt", ".md", ".caddy", ".example", ".service"}
for path in local.rglob("*"):
if path.name.startswith(".env") or path.suffix in {".pyc"}:
continue
rel = path.relative_to(local).as_posix()
target = f"{remote}/{rel}"
if path.is_dir():
sftp_mkdirs(sftp, target)
else:
sftp_mkdirs(sftp, str(Path(target).parent).replace("\\", "/"))
print(f"put {rel}")
if path.suffix.lower() in text_ext or path.name in {"Caddyfile", "Dockerfile"}:
data = path.read_bytes().replace(b"\r\n", b"\n").replace(b"\r", b"\n")
sftp.putfo(__import__("io").BytesIO(data), target)
else:
sftp.put(str(path), target)
def wait_wp_http(ssh: paramiko.SSHClient, port: int, tries: int = 60) -> None:
for _ in range(tries):
_stdin, stdout, _stderr = ssh.exec_command(
f"curl -fsS -o /dev/null http://127.0.0.1:{port} || true",
timeout=20,
get_pty=True,
)
ok = stdout.channel.recv_exit_status() == 0
if ok:
print("WP HTTP ready on", f"127.0.0.1:{port}")
return
time.sleep(2)
raise RuntimeError(f"WordPress did not become ready on :{port}")
def main() -> int:
if not PASSWORD:
print("Set NOMADRO_SSH_PASS env var", file=sys.stderr)
return 1
if not LOCAL_DIR.exists():
raise RuntimeError("Local deploy/nomadro-live folder missing")
mysql_pass = gen_pass()
mysql_root = gen_pass()
# User requested defaults
wp_admin_user = "hackrobot"
wp_admin_pass = "Xiao4669805@"
wp_admin_email = "hello@nomadro.live"
env_text = "\n".join(
[
f"MYSQL_PASSWORD={mysql_pass}",
f"MYSQL_ROOT_PASSWORD={mysql_root}",
f"WP_ADMIN_USER={wp_admin_user}",
f"WP_ADMIN_PASSWORD={wp_admin_pass}",
f"WP_ADMIN_EMAIL={wp_admin_email}",
"",
]
)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASSWORD, timeout=30, allow_agent=False, look_for_keys=False)
sftp = ssh.open_sftp()
# Do not rotate DB passwords if the stack already exists.
env_exists = False
try:
sftp.stat(f"{REMOTE_WP_DIR}/.env")
env_exists = True
except FileNotFoundError:
env_exists = False
print("==> Upload nomadro.live WordPress stack")
upload_tree(sftp, WP_LOCAL_DIR, REMOTE_WP_DIR)
if not env_exists:
with sftp.file(f"{REMOTE_WP_DIR}/.env", "w") as f:
f.write(env_text)
else:
print("Keeping existing remote .env (DB already initialized)")
sftp.chmod(f"{REMOTE_WP_DIR}/bootstrap-wp.sh", 0o755)
# 2) Upload router code
print("\n==> Upload nomadro.live router")
upload_tree(sftp, ROUTER_LOCAL_DIR, REMOTE_ROUTER_DIR)
# 3) Configure Caddy site (overwrite just this one file)
print("\n==> Update Caddy site for nomadro.live")
sftp.put(str(CADDY_LOCAL_FILE), REMOTE_CADDY_SITE)
sftp.chmod(REMOTE_CADDY_SITE, 0o644)
# 4) Configure systemd service
print("\n==> Install systemd service for router")
sftp.put(str(SYSTEMD_LOCAL_FILE), REMOTE_SYSTEMD_SERVICE)
sftp.chmod(REMOTE_SYSTEMD_SERVICE, 0o644)
sftp.close()
# 5) Start docker stack
print("\n==> Start WordPress stack (docker compose up -d)")
run(ssh, f"cd {REMOTE_WP_DIR} && docker compose up -d", timeout=600)
# Wait WP
wait_wp_http(ssh, 8089, tries=60)
# 6) Seed content + trust pages
print("\n==> Bootstrap WordPress content (20 English posts)")
run(ssh, f"cd {REMOTE_WP_DIR} && bash bootstrap-wp.sh", timeout=900)
# 7) Install router deps + start service (keep Caddy unchanged until router is ready)
print("\n==> Start nomadro-live-router")
run(ssh, f"cd {REMOTE_ROUTER_DIR} && npm install --omit=dev", timeout=600)
run(ssh, "systemctl daemon-reload", timeout=60)
# Restart is safe: service does not modify other apps/data.
run(
ssh,
"systemctl enable --now nomadro-live-router.service || systemctl restart nomadro-live-router.service",
timeout=60,
)
# 8) Validate Caddy and reload
print("\n==> Reload Caddy (activate nomadro.live split)")
run(ssh, "caddy validate --config /etc/caddy/Caddyfile", timeout=60)
run(ssh, "systemctl reload caddy", timeout=60)
ssh.close()
print("\n=== nomadro.live provisioning done ===")
print(f"WP admin user: {wp_admin_user}")
print(f"WP admin pass: {wp_admin_pass}")
print(f"WP admin URL: https://nomadro.live/wp-admin/")
return 0
if __name__ == "__main__":
raise SystemExit(main())