#!/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())