#!/usr/bin/env python3 """Upload WordPress deploy bundle and provision on the Nomadro server.""" 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_DIR = "/var/www/nomadro-wp" LOCAL_DIR = Path(__file__).resolve().parent def gen_pass(n: int = 24) -> str: alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(n)) def safe_print(text: str) -> None: try: print(text) except UnicodeEncodeError: print(text.encode("utf-8", errors="replace").decode("utf-8", errors="replace")) def run(ssh: paramiko.SSHClient, cmd: str, timeout: int = 600) -> str: 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") code = stdout.channel.recv_exit_status() if out: safe_print(out[-4000:]) if err.strip(): safe_print(err[-2000:]) if code != 0: raise RuntimeError(f"Command failed ({code}): {cmd}") return out 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) for path in local.rglob("*"): if path.name.startswith(".env") or path.name.endswith(".pyc"): continue if path.name == "remote_provision.py": 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}") sftp.put(str(path), target) def main() -> int: if not PASSWORD: print("Set NOMADRO_SSH_PASS", file=sys.stderr) return 1 mysql_pass = gen_pass() mysql_root = gen_pass() wp_admin = "Xiao4669805@" wp_user = "hackrobot" wp_email = "admin@nomadro.com" env_text = ( f"MYSQL_PASSWORD={mysql_pass}\n" f"MYSQL_ROOT_PASSWORD={mysql_root}\n" f"WP_ADMIN_USER={wp_user}\n" f"WP_ADMIN_PASSWORD={wp_admin}\n" f"WP_ADMIN_EMAIL={wp_email}\n" ) secrets_path = LOCAL_DIR / ".env.generated" secrets_path.write_text(env_text, encoding="utf-8") print(f"Wrote local secrets to {secrets_path} (do not commit)") 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() print("==> Upload wordpress bundle") upload_tree(sftp, LOCAL_DIR, REMOTE_DIR) with sftp.file(f"{REMOTE_DIR}/.env", "w") as f: f.write(env_text) sftp.chmod(f"{REMOTE_DIR}/install-docker.sh", 0o755) sftp.chmod(f"{REMOTE_DIR}/bootstrap-wp.sh", 0o755) sftp.close() run(ssh, f"bash {REMOTE_DIR}/install-docker.sh", timeout=900) run(ssh, f"cd {REMOTE_DIR} && docker compose pull", timeout=900) run(ssh, f"cd {REMOTE_DIR} && docker compose up -d", timeout=300) print("==> Wait for WP HTTP") for _ in range(60): stdin, stdout, stderr = ssh.exec_command( "curl -fsS -o /dev/null -w '%{http_code}' http://127.0.0.1:8088 || true" ) code = stdout.read().decode().strip() if code.startswith("2") or code.startswith("3"): print("WP HTTP", code) break time.sleep(3) else: raise RuntimeError("WordPress did not become ready on :8088") # Preserve extra caddy site imports already on server run( ssh, f"cp {REMOTE_DIR}/Caddyfile.adsense /etc/caddy/Caddyfile && " "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy", ) run(ssh, f"cd {REMOTE_DIR} && bash bootstrap-wp.sh", timeout=900) # Quick public check via local caddy run(ssh, "curl -fsSI https://nomadro.com | head -20 || true") run( ssh, "curl -fsS https://nomadro.com | tr '\\n' ' ' | grep -o '