Update pve RustDesk GA sync scripts

This commit is contained in:
alexveley
2026-06-24 01:57:11 -04:00
parent 0f8ba4fba1
commit c60eba1352
2 changed files with 113 additions and 39 deletions
+112 -38
View File
@@ -25,18 +25,50 @@ SHARE = os.environ.get('BLAIR_SHARE', '/tank/blair-share')
REGISTRY = os.path.join(SHARE, 'fleet', 'registry.json') REGISTRY = os.path.join(SHARE, 'fleet', 'registry.json')
NODE = os.environ.get('BLAIR_PVE_NODE', 'pve') NODE = os.environ.get('BLAIR_PVE_NODE', 'pve')
DRY_RUN = os.environ.get('BLAIR_DRY_RUN', '') in ('1', 'true', 'yes') DRY_RUN = os.environ.get('BLAIR_DRY_RUN', '') in ('1', 'true', 'yes')
VERBOSE = os.environ.get('BLAIR_VERBOSE', '') in ('1', 'true', 'yes')
VMIDS_ENV = os.environ.get('BLAIR_VMIDS', '').strip() VMIDS_ENV = os.environ.get('BLAIR_VMIDS', '').strip()
RUSTDESK_CFG = ( GUEST_READ_PATHS = [
r'C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk2.toml' r'C:\ProgramData\Blair\rustdesk-report.json',
) r'C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk2.toml',
PS_GET_ID = ( r'C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk.toml',
r"$p='C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk2.toml'; " r'C:\Windows\System32\config\systemprofile\AppData\Roaming\RustDesk\config\RustDesk2.toml',
r"if (-not (Test-Path -LiteralPath $p)) { exit 1 }; " r'C:\ProgramData\RustDesk\config\RustDesk2.toml',
r"$t=Get-Content -Raw -LiteralPath $p; " ]
r"if ($t -match '(?m)^\s*id\s*=\s*''?(\d{6,12})') { Write-Output $Matches[1]; exit 0 }; "
r"exit 1" PS_READ_ID = r'''
$paths = @(
'C:\ProgramData\Blair\rustdesk-report.json',
'C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk2.toml',
'C:\Windows\ServiceProfiles\LocalService\AppData\Roaming\RustDesk\config\RustDesk.toml',
'C:\Windows\System32\config\systemprofile\AppData\Roaming\RustDesk\config\RustDesk2.toml',
'C:\ProgramData\RustDesk\config\RustDesk2.toml'
) )
foreach ($p in $paths) {
if (-not (Test-Path -LiteralPath $p)) { continue }
$t = Get-Content -Raw -LiteralPath $p
if ($p -like '*.json') {
try {
$j = $t | ConvertFrom-Json
if ($j.rustdesk_id -match '^\d{6,12}$') { Write-Output $j.rustdesk_id; exit 0 }
} catch {}
}
if ($t -match '(?m)^\s*id\s*=\s*[''"]?(\d{6,12})') { Write-Output $Matches[1]; exit 0 }
}
foreach ($rp in @(
'HKLM:\SOFTWARE\RustDesk\Host',
'HKLM:\SOFTWARE\RustDesk',
'HKLM:\SOFTWARE\WOW6432Node\RustDesk\Host',
'HKLM:\SOFTWARE\WOW6432Node\RustDesk'
)) {
if (-not (Test-Path $rp)) { continue }
foreach ($n in @('id', 'Id', 'rustdesk_id', 'peer_id')) {
$v = (Get-ItemProperty -Path $rp -Name $n -ErrorAction SilentlyContinue).$n
if ($v -match '^\d{6,12}$') { Write-Output $v; exit 0 }
}
}
exit 1
'''
def log(msg: str) -> None: def log(msg: str) -> None:
@@ -142,40 +174,81 @@ def guest_exec_text(vmid: int, command: list[str], timeout: int = 90) -> tuple[s
def guest_file_read(vmid: int, guest_path: str) -> tuple[str | None, str | None]: def guest_file_read(vmid: int, guest_path: str) -> tuple[str | None, str | None]:
proc = subprocess.run( for path in {guest_path, guest_path.replace('\\', '/')}:
['pvesh', 'get', f'/nodes/{NODE}/qemu/{vmid}/agent/file-read', '--file', guest_path], proc = subprocess.run(
capture_output=True, text=True, check=False, [
'pvesh', 'get', f'/nodes/{NODE}/qemu/{vmid}/agent/file-read',
'--file', path, '--output-format', 'json',
],
capture_output=True, text=True, check=False,
)
if proc.returncode != 0:
continue
try:
payload = json.loads(proc.stdout)
except json.JSONDecodeError:
continue
data = payload.get('data', payload)
content_b64 = data.get('content') if isinstance(data, dict) else None
if not content_b64:
continue
return base64.b64decode(content_b64).decode('utf-8', errors='replace'), None
return None, f'file-read failed for {guest_path}'
def parse_id_from_content(text: str, path: str) -> str | None:
if path.lower().endswith('.json'):
try:
rid = str(json.loads(text).get('rustdesk_id', '') or '').strip()
if re.fullmatch(r'\d{6,12}', rid):
return rid
except json.JSONDecodeError:
pass
for pattern in (
r'(?m)^\s*id\s*=\s*["\']?(\d{6,12})',
r'(?m)rustdesk_id\s*=\s*["\']?(\d{6,12})',
):
match = re.search(pattern, text)
if match:
return match.group(1)
return None
def powershell_encoded(script: str) -> str:
return base64.b64encode(script.strip().encode('utf-16-le')).decode('ascii')
def guest_exec_powershell(vmid: int) -> tuple[str | None, str | None]:
enc = powershell_encoded(PS_READ_ID)
return guest_exec_text(
vmid,
['powershell.exe', '-NoProfile', '-EncodedCommand', enc],
timeout=120,
) )
if proc.returncode != 0:
msg = proc.stderr.strip() or proc.stdout.strip() or f'file-read rc={proc.returncode}'
return None, msg
try:
payload = json.loads(proc.stdout)
except json.JSONDecodeError:
return None, f'file-read json error: {proc.stdout[:160]!r}'
data = payload.get('data', payload)
content_b64 = data.get('content') if isinstance(data, dict) else None
if not content_b64:
return None, 'file-read empty content'
return base64.b64decode(content_b64).decode('utf-8', errors='replace'), None
def parse_rustdesk_id_from_toml(text: str) -> str | None:
match = re.search(r'(?m)^\s*id\s*=\s*["\']?(\d{6,12})', text)
return match.group(1) if match else None
def read_rustdesk_id(vmid: int) -> tuple[str | None, str | None]: def read_rustdesk_id(vmid: int) -> tuple[str | None, str | None]:
text, err = guest_file_read(vmid, RUSTDESK_CFG) errors: list[str] = []
if text: for path in GUEST_READ_PATHS:
rid = parse_rustdesk_id_from_toml(text) text, err = guest_file_read(vmid, path)
if not text:
if VERBOSE:
errors.append(f'{path}: {err}')
continue
rid = parse_id_from_content(text, path)
if rid: if rid:
if VERBOSE:
log(f'VM {vmid}: ID {rid} from file-read {path}')
return rid, None return rid, None
err = 'id not found in RustDesk2.toml' errors.append(f'{path}: no id in file')
return guest_exec_text(
vmid, rid, err = guest_exec_powershell(vmid)
['powershell.exe', '-NoProfile', '-Command', PS_GET_ID], if rid:
) if VERBOSE:
log(f'VM {vmid}: ID {rid} from guest exec')
return rid, None
errors.append(f'exec: {err}')
return None, errors[-1] if errors else 'unknown'
def qm_description(vmid: int) -> str: def qm_description(vmid: int) -> str:
@@ -224,7 +297,8 @@ def main() -> int:
new_id, err = read_rustdesk_id(vmid) new_id, err = read_rustdesk_id(vmid)
if not new_id: if not new_id:
log(f'VM {vmid}: skip (could not read RustDesk ID: {err})') hint = ' (try BLAIR_VERBOSE=1)' if not VERBOSE else ''
log(f'VM {vmid}: skip (could not read RustDesk ID: {err}){hint}')
continue continue
old_registry_id = registry_old_id(registry, vmid) old_registry_id = registry_old_id(registry, vmid)
+1 -1
View File
@@ -27,7 +27,7 @@ chmod 644 "$CRON"
cat "$CRON" cat "$CRON"
echo "=== dry run ===" echo "=== dry run ==="
"$TARGET" --dry-run BLAIR_VERBOSE=1 "$TARGET" --dry-run
echo "=== live run ===" echo "=== live run ==="
"$TARGET" "$TARGET"