#!/usr/bin/env python3 """Package custom files, upload them, and run bootstrap on the Ubuntu server.""" from __future__ import annotations import io import os import secrets import stat import string import zipfile from pathlib import Path import paramiko HOST = "10.15.15.89" USER = "ubuntu" PASSWORD = "ubuntu" ROOT = Path(r"c:\laragon\www\light-otantik-nux") DEPLOY = ROOT / "deploy" CRED_FILE = DEPLOY / "credentials.env" MESOMB_APP = "9d46e5620e1bb9d82a58061e3ac2bcdf1cd7736b" MESOMB_ACCESS = "6a86b5e5-fe33-4de7-ab00-de33d68f2782" MESOMB_SECRET = "395ee4f3-2cd6-41b0-904e-2a735797d70a" def gen_pass(n: int = 20) -> str: alphabet = string.ascii_letters + string.digits return "".join(secrets.choice(alphabet) for _ in range(n)) def connect() -> paramiko.SSHClient: client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( HOST, username=USER, password=PASSWORD, timeout=20, allow_agent=False, look_for_keys=False, ) return client def sudo(client: paramiko.SSHClient, cmd: str, timeout: int = 900) -> tuple[int, str]: wrapped = f"sudo -S -p '' bash -lc {repr(cmd)}" stdin, stdout, stderr = client.exec_command(wrapped, timeout=timeout, get_pty=True) stdin.write(PASSWORD + "\n") stdin.flush() out = stdout.read().decode("utf-8", errors="replace") err = stderr.read().decode("utf-8", errors="replace") code = stdout.channel.recv_exit_status() text = out + (("\n" + err) if err.strip() and err.strip() not in out else "") lines = [ln for ln in text.splitlines() if ln.strip() != PASSWORD] return code, "\n".join(lines) def zip_deploy() -> bytes: buf = io.BytesIO() text_names = {".sh", ".conf", ".ini", ".php", ".tpl", ".htaccess", ".env", ".py", ".md", ".json", ".xml"} with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: for path in DEPLOY.rglob("*"): if path.is_dir(): continue if path.name in {"credentials.env"}: continue rel = path.relative_to(DEPLOY).as_posix() data = path.read_bytes() if path.suffix in text_names or path.name in {".htaccess", "bootstrap.sh"}: data = data.replace(b"\r\n", b"\n") zf.writestr(rel, data) return buf.getvalue() def main() -> None: db_pass = gen_pass(24) admin_pass = gen_pass(16) CRED_FILE.write_text( "\n".join( [ f"DB_NAME=phpnuxbill", f"DB_USER=nuxbill", f"DB_PASS={db_pass}", f"ADMIN_USER=admin", f"ADMIN_PASS={admin_pass}", f"MESOMB_APP_KEY={MESOMB_APP}", f"MESOMB_ACCESS_KEY={MESOMB_ACCESS}", f"MESOMB_SECRET_KEY={MESOMB_SECRET}", f"URL=http://10.15.15.89/", f"ADMIN_URL=http://10.15.15.89/admin", f"PORTAL_URL=http://10.15.15.89/?_route=portal", ] ) + "\n", encoding="utf-8", ) os.chmod(CRED_FILE, stat.S_IRUSR | stat.S_IWUSR) print("credentials written to", CRED_FILE) payload = zip_deploy() print("zip size", len(payload)) client = connect() try: sftp = client.open_sftp() with sftp.file("/tmp/woyla-deploy.zip", "wb") as f: f.write(payload) sftp.close() print("uploaded zip") env_file = ( "DB_NAME=phpnuxbill\n" "DB_USER=nuxbill\n" f"DB_PASS={db_pass}\n" f"ADMIN_PASS={admin_pass}\n" f"MESOMB_APP_KEY={MESOMB_APP}\n" f"MESOMB_ACCESS_KEY={MESOMB_ACCESS}\n" f"MESOMB_SECRET_KEY={MESOMB_SECRET}\n" ) sftp = client.open_sftp() with sftp.file("/tmp/woyla.env", "w") as f: f.write(env_file) sftp.close() code, out = sudo(client, "mv /tmp/woyla.env /root/woyla.env && chmod 600 /root/woyla.env") print("env", code, out[-400:]) run_sh = """#!/bin/bash set -e while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do echo waiting-apt; sleep 5; done rm -rf /tmp/woyla-deploy mkdir -p /tmp/woyla-deploy python3 -c "import zipfile; zipfile.ZipFile('/tmp/woyla-deploy.zip').extractall('/tmp/woyla-deploy'); print('extracted')" chmod +x /tmp/woyla-deploy/bootstrap.sh set -a . /root/woyla.env set +a bash /tmp/woyla-deploy/bootstrap.sh """ sftp = client.open_sftp() with sftp.file("/tmp/woyla-run.sh", "w") as f: f.write(run_sh.replace("\r\n", "\n")) sftp.close() code, out = sudo(client, "chmod +x /tmp/woyla-run.sh && bash /tmp/woyla-run.sh", timeout=900) print(out) print("bootstrap exit", code) if code != 0: raise SystemExit(code) finally: client.close() if __name__ == "__main__": main()