TorbatYar/scripts/remote_ssh.py
Mortezakoohjani 800b0ba2c5 Deploy TorbatYar for torbatyar.ir with nginx multi-tenant routing.
Wire production domain, CORS for tenant subdomains, celery volume mounts, and nginx reverse proxy configs for apex, API, identity, auth, and wildcard tenants.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 21:43:33 +03:30

121 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Simple SSH helper for deployment (paramiko)."""
from __future__ import annotations
import argparse
import base64
import sys
from pathlib import Path
import paramiko
def connect(host: str, user: str, password: str) -> paramiko.SSHClient:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
host,
username=user,
password=password,
timeout=30,
allow_agent=False,
look_for_keys=False,
)
return client
def _exec(client: paramiko.SSHClient, command: str, timeout: int = 120) -> tuple[int, str, str]:
stdin, stdout, stderr = client.exec_command(command, timeout=timeout, get_pty=True)
out = stdout.read().decode(errors="replace")
err = stderr.read().decode(errors="replace")
code = stdout.channel.recv_exit_status()
return code, out, err
def run(
host: str,
user: str,
password: str,
command: str,
*,
sudo: bool = False,
timeout: int = 120,
) -> int:
client = connect(host, user, password)
try:
if sudo:
b64 = base64.b64encode(command.encode()).decode()
full = (
f"echo {password!r} | sudo -S -p '' bash -c "
f"'echo {b64} | base64 -d | bash'"
)
else:
full = f"bash -lc {command!r}"
code, out, err = _exec(client, full, timeout=timeout)
sys.stdout.write(out)
if err.strip():
sys.stderr.write(err)
return code
finally:
client.close()
def upload(
host: str,
user: str,
password: str,
local: str,
remote: str,
*,
sudo: bool = False,
) -> int:
data = Path(local).read_bytes()
b64 = base64.b64encode(data).decode()
remote_q = remote.replace("'", "'\\''")
write_cmd = f"mkdir -p \"$(dirname '{remote_q}')\" && echo {b64} | base64 -d > '{remote_q}'"
return run(host, user, password, write_cmd, sudo=sudo, timeout=300)
def main() -> int:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="action", required=True)
p_run = sub.add_parser("run")
p_run.add_argument("--host", required=True)
p_run.add_argument("--user", required=True)
p_run.add_argument("--password", required=True)
p_run.add_argument("--sudo", action="store_true")
p_run.add_argument("--timeout", type=int, default=120)
p_run.add_argument("command")
p_up = sub.add_parser("upload")
p_up.add_argument("--host", required=True)
p_up.add_argument("--user", required=True)
p_up.add_argument("--password", required=True)
p_up.add_argument("--local", required=True)
p_up.add_argument("--remote", required=True)
p_up.add_argument("--sudo", action="store_true")
args = parser.parse_args()
if args.action == "run":
return run(
args.host,
args.user,
args.password,
args.command,
sudo=args.sudo,
timeout=args.timeout,
)
return upload(
args.host,
args.user,
args.password,
args.local,
args.remote,
sudo=args.sudo,
)
if __name__ == "__main__":
raise SystemExit(main())