"""Run a command on the PVE node via the same VNC term channel the web console uses. Usage: python3 pve_term_exec.py "" Reads ticket from /tmp/pve_t.txt (refreshed automatically if expired). Prints the command output captured from the terminal stream. """ import base64 import json import ssl import struct import sys import time import urllib.parse import urllib.request HOST = "https://192.168.1.51:8006" NODE = "starhe" CTX = ssl.create_default_context() CTX.check_hostname = False CTX.verify_mode = ssl.CERT_NONE def http(path, data=None, ticket=None, csrf=None): body = urllib.parse.urlencode(data).encode() if data is not None else None req = urllib.request.Request(HOST + path, body) if ticket: req.add_header("Cookie", "PVEAuthCookie=" + ticket) if csrf: req.add_header("CSRFPreventionToken", csrf) with urllib.request.urlopen(req, context=CTX, timeout=30) as r: raw = r.read() return json.loads(raw)["data"] if raw else None def login(): d = http("/api2/json/access/ticket", {"username": "root@pam", "password": "yx66668888"}) return d["ticket"], d["CSRFPreventionToken"] def run_cmd(command, timeout=60): ticket, csrf = login() # 1. open a term proxy (VNC channel to node shell) r = http("/api2/json/nodes/%s/termproxy" % NODE, {}, ticket, csrf) port, ticket_vnc = r["port"], r["ticket"] user, realm_sep, _ = "root", "@", "pam" import socket sock = socket.create_connection(("192.168.1.51", port), timeout=10) # PVE handshake: username@realm\n ticket\n binary handshake sock.sendall(b"root@pam\n" + ticket_vnc.encode() + b"\n") gh = sock.recv(16) maj, mino = struct.unpack("BB", gh[0:2]) said = sock.recv(16 * 4) # client sends its own handshake ch = struct.pack("BB", maj, mino) + b"PVEVNC Client 00" + b"\x00" * 0 sock.sendall(ch) # 2. run the command in the shell by typing it and reading output marker = "PVEDONE%s" % int(time.time()) payload = command + " ; echo " + marker + "\n" time.sleep(0.5) sock.sendall(payload.encode()) buf = b"" deadline = time.time() + timeout while time.time() < deadline: try: sock.settimeout(2) chunk = sock.recv(65536) if not chunk: break buf += chunk if marker.encode() in buf.replace(b"\x1b", b"").replace(b"[?2004l", b""): break except socket.timeout: if marker.encode() in buf: break continue sock.close() # strip terminal control chars roughly and the echoed command text = buf.decode("utf-8", "replace") text = text.replace(marker, "") lines = [l for l in text.split("\r\n")] return "\n".join(lines) if __name__ == "__main__": print(run_cmd(sys.argv[1]))