#!/usr/bin/env python3 import argparse import getpass import os import posixpath import shlex import sys import threading import time import paramiko def pump_output(channel, stop_flag): while not stop_flag["stop"]: if channel.recv_ready(): data = channel.recv(4096) if not data: break sys.stdout.buffer.write(data) sys.stdout.buffer.flush() continue if channel.exit_status_ready(): if not channel.recv_ready(): break time.sleep(0.05) stop_flag["stop"] = True def main(): parser = argparse.ArgumentParser(description="Upload deploy-neko.sh and run it interactively on a remote Linux server.") parser.add_argument("--host", default="192.168.2.179") parser.add_argument("--user", default="eric") parser.add_argument("--password", default=os.environ.get("NEKO_REMOTE_PASSWORD")) parser.add_argument("--local-script", default=os.path.join(os.path.dirname(__file__), "deploy-neko.sh")) parser.add_argument("--remote-dir", default="~/neko-tools") parser.add_argument("--args", default="", help="Optional arguments passed to deploy-neko.sh") args = parser.parse_args() if not args.password: args.password = getpass.getpass("SSH password: ") local_script = os.path.abspath(args.local_script) if not os.path.isfile(local_script): raise SystemExit(f"Script not found: {local_script}") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=args.host, username=args.user, password=args.password, timeout=15) sftp = client.open_sftp() remote_home = sftp.normalize(".") remote_dir = args.remote_dir.replace("~", remote_home, 1) try: sftp.stat(remote_dir) except IOError: client.exec_command(f"mkdir -p {shlex.quote(remote_dir)}") time.sleep(0.2) remote_script = posixpath.join(remote_dir, "deploy-neko.sh") sftp.put(local_script, remote_script) sftp.chmod(remote_script, 0o755) sftp.close() channel = client.invoke_shell(width=180, height=40) stop_flag = {"stop": False} worker = threading.Thread(target=pump_output, args=(channel, stop_flag), daemon=True) worker.start() time.sleep(0.2) quoted_remote_dir = shlex.quote(remote_dir) quoted_remote_script = shlex.quote(remote_script) trailing_args = f" {args.args.strip()}" if args.args.strip() else "" channel.send(f"cd {quoted_remote_dir}\n") channel.send(f"bash {quoted_remote_script}{trailing_args}\n") try: while not stop_flag["stop"]: line = sys.stdin.readline() if not line: break channel.send(line) except KeyboardInterrupt: pass finally: time.sleep(0.2) try: channel.send("\n") except Exception: pass stop_flag["stop"] = True worker.join(timeout=1) channel.close() client.close() if __name__ == "__main__": main()