清理:移除 RDP 遗留资料并纳管产品文档与运维工具
变更详情:清理 RDP 遗留文档与临时文件,纳管产品文档与运维工具脚本
This commit is contained in:
1 parent
26fde0369e
commit
7fdcb53dd9
14 files changed
+759
-100
No files matched your search
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
set -u
|
||||
LOG_DIR="$HOME/vdi-terminal/logs"
|
||||
LOG_FILE="$LOG_DIR/overnight-$(date +%Y%m%d).log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
log() { printf '%s %s\n' "$(date '+%Y-%m-%d %H:%M:%S %z')" "$*" >> "$LOG_FILE"; }
|
||||
log "monitor_start pid=$$ interval=60s"
|
||||
while :; do
|
||||
now=$(date '+%Y-%m-%d %H:%M:%S %z')
|
||||
shell_pid=$(pgrep -f '^/home/armbian/vdi-terminal/bin/kiosk-shell$' | head -n 1 || true)
|
||||
monitor_pid=$(pgrep -f '^/home/armbian/vdi-terminal/bin/vdi-kiosk-monitor' | head -n 1 || true)
|
||||
if [ -n "$shell_pid" ]; then
|
||||
ps_line=$(ps -o pcpu=,pmem=,rss=,etime= -p "$shell_pid" | tr '\n' ' ')
|
||||
else
|
||||
ps_line="missing"
|
||||
fi
|
||||
temp=$(awk '{printf "%.1f", $1/1000}' /sys/class/thermal/thermal_zone0/temp 2>/dev/null || printf "unknown")
|
||||
mem=$(awk '/MemTotal:|MemAvailable:|SwapFree:/{printf "%s=%sKB ",$1,$2}' /proc/meminfo 2>/dev/null || true)
|
||||
heartbeat="missing"
|
||||
if [ -f /tmp/vdi-kiosk.heartbeat ]; then
|
||||
heartbeat=$(stat -c '%Y' /tmp/vdi-kiosk.heartbeat 2>/dev/null || printf 'unknown')
|
||||
fi
|
||||
rdp=$(ss -tnp 2>/dev/null | grep '192.168.1.70:3389' | tr '\n' ';' || true)
|
||||
starts=$(grep -c '主进程已启动' /tmp/vdi-kiosk-monitor.log 2>/dev/null || printf 0)
|
||||
exits=$(grep -c '主进程已退出' /tmp/vdi-kiosk-monitor.log 2>/dev/null || printf 0)
|
||||
crashes=$(grep -ciE 'segmentation fault|bus error|double free|invalid pointer|aborted|terminate called' /tmp/vdi-kiosk-monitor.log 2>/dev/null || printf 0)
|
||||
log "sample now=$now shell_pid=${shell_pid:-missing} monitor_pid=${monitor_pid:-missing} ps=[$ps_line] temp_c=$temp heartbeat_epoch=$heartbeat rdp=[$rdp] starts=$starts exits=$exits crashes=$crashes $mem"
|
||||
sleep 60
|
||||
done
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Run a command on the PVE node via the same VNC term channel the web console uses.
|
||||
|
||||
Usage: python3 pve_term_exec.py "<command>"
|
||||
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]))
|
||||
@@ -0,0 +1,55 @@
|
||||
import json
|
||||
import ssl
|
||||
import sys
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
HOST = "https://192.168.1.51:8006"
|
||||
CTX = ssl.create_default_context()
|
||||
CTX.check_hostname = False
|
||||
CTX.verify_mode = ssl.CERT_NONE
|
||||
|
||||
|
||||
def api(path, data=None, ticket=None, csrf=None, json_body=None):
|
||||
body = None
|
||||
headers = {}
|
||||
if json_body is not None:
|
||||
body = json.dumps(json_body).encode()
|
||||
headers["Content-Type"] = "application/json"
|
||||
elif data is not None:
|
||||
body = urllib.parse.urlencode(data).encode()
|
||||
req = urllib.request.Request(HOST + path, body, headers)
|
||||
if ticket:
|
||||
req.add_header("Cookie", "PVEAuthCookie=" + ticket)
|
||||
if csrf:
|
||||
req.add_header("CSRFPreventionToken", csrf)
|
||||
with urllib.request.urlopen(req, context=CTX, timeout=40) as r:
|
||||
raw = r.read()
|
||||
return json.loads(raw)["data"] if raw else None
|
||||
|
||||
|
||||
def agent(ticket, csrf, command, timeout=90):
|
||||
r = api("/api2/json/nodes/starhe/qemu/100/agent/exec",
|
||||
json_body={"command": command}, ticket=ticket, csrf=csrf)
|
||||
pid = r["pid"]
|
||||
for _ in range(timeout // 2):
|
||||
import time
|
||||
time.sleep(2)
|
||||
st = api("/api2/json/nodes/starhe/qemu/100/agent/exec-status?pid=%d" % pid,
|
||||
ticket=ticket)
|
||||
if st.get("exited"):
|
||||
return st.get("out-data") or "", st.get("err-data") or ""
|
||||
return "TIMEOUT", ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
d = api("/api2/json/access/ticket",
|
||||
{"username": "root@pam", "password": "yx66668888"})
|
||||
ticket, csrf = d["ticket"], d["CSRFPreventionToken"]
|
||||
ps = sys.argv[1]
|
||||
out, err = agent(ticket, csrf, [
|
||||
"C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
|
||||
"-NoProfile", "-Command", ps])
|
||||
print(out)
|
||||
if err:
|
||||
print("ERR:", err[:300])
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Convert an XWD dump (from xwd) into a PNG, without external image libraries.
|
||||
|
||||
Usage: xwd -root -silent | python3 xwd2png.py out.png
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
import zlib
|
||||
|
||||
|
||||
def read_header(data):
|
||||
fields = struct.unpack(">25I", data[:100])
|
||||
names = [
|
||||
"header_size", "file_version", "pixmap_format", "pixmap_depth",
|
||||
"pixmap_width", "pixmap_height", "xoffset", "byte_order",
|
||||
"bitmap_unit", "bitmap_bit_order", "bitmap_pad", "bits_per_pixel",
|
||||
"bytes_per_line", "visual_class", "red_mask", "green_mask",
|
||||
"blue_mask", "bits_per_rgb", "colormap_entries", "ncolors",
|
||||
"window_width", "window_height", "window_x", "window_y",
|
||||
"window_bdrwidth",
|
||||
]
|
||||
return dict(zip(names, fields))
|
||||
|
||||
|
||||
def write_png(path, width, height, rgb_rows):
|
||||
raw = b"".join(b"\x00" + row for row in rgb_rows)
|
||||
|
||||
def chunk(tag, payload):
|
||||
return (struct.pack(">I", len(payload)) + tag + payload +
|
||||
struct.pack(">I", zlib.crc32(tag + payload) & 0xFFFFFFFF))
|
||||
|
||||
header = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)
|
||||
png = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", header) +
|
||||
chunk(b"IDAT", zlib.compress(raw, 6)) + chunk(b"IEND", b""))
|
||||
with open(path, "wb") as handle:
|
||||
handle.write(png)
|
||||
|
||||
|
||||
def main():
|
||||
out_path = sys.argv[1] if len(sys.argv) > 1 else "out.png"
|
||||
data = sys.stdin.buffer.read()
|
||||
head = read_header(data)
|
||||
width = head["pixmap_width"]
|
||||
height = head["pixmap_height"]
|
||||
bpp = head["bits_per_pixel"]
|
||||
stride = head["bytes_per_line"]
|
||||
offset = head["header_size"] + head["ncolors"] * 12
|
||||
pixels = data[offset:]
|
||||
|
||||
if bpp == 32:
|
||||
rows = []
|
||||
for y in range(height):
|
||||
line = pixels[y * stride:y * stride + width * 4]
|
||||
out = bytearray(width * 3)
|
||||
for x in range(width):
|
||||
src = x * 4
|
||||
dst = x * 3
|
||||
out[dst] = line[src + 2]
|
||||
out[dst + 1] = line[src + 1]
|
||||
out[dst + 2] = line[src]
|
||||
rows.append(bytes(out))
|
||||
elif bpp == 24:
|
||||
rows = []
|
||||
for y in range(height):
|
||||
line = pixels[y * stride:y * stride + width * 3]
|
||||
out = bytearray(width * 3)
|
||||
for x in range(width):
|
||||
src = x * 3
|
||||
dst = x * 3
|
||||
out[dst] = line[src + 2]
|
||||
out[dst + 1] = line[src + 1]
|
||||
out[dst + 2] = line[src]
|
||||
rows.append(bytes(out))
|
||||
else:
|
||||
sys.stderr.write("unsupported bits_per_pixel=%s\n" % bpp)
|
||||
return 2
|
||||
|
||||
write_png(out_path, width, height, rows)
|
||||
print("%s %dx%d" % (out_path, width, height))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user