Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ jobs:
- name: Orgo computer plugin tests
run: python3 -m unittest discover -s computer-viewer/agent-plugin/orgo-computer/tests -p 'test_*.py'

# -n 1: without it xargs passes every later path as an ARGUMENT to the
# first script, so only one file was ever parsed.
- name: Shell syntax
run: find . -name '*.sh' -not -path './.git/*' -print0 | xargs -0 bash -n
run: find . -name '*.sh' -not -path './.git/*' -print0 | xargs -0 -n 1 bash -n

- name: HD agent tests
run: python3 computer-viewer/tests/test-hiperf-agent.py

- name: Host-script bind tests
run: bash computer-viewer/tests/test-bind.sh
Expand Down Expand Up @@ -63,3 +68,37 @@ jobs:

- name: Installer tests
run: bash tests/test-install.sh

windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: PowerShell syntax
shell: pwsh
run: |
$failed = $false
$files = Get-ChildItem -Path . -Recurse -Filter '*.ps1' |
Where-Object { $_.FullName -notmatch '\\\.git\\' }
if (-not $files) { Write-Host 'no .ps1 files found'; exit 1 }
foreach ($f in $files) {
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$null, [ref]$errors) | Out-Null
if ($errors -and $errors.Count -gt 0) {
$failed = $true
Write-Host "FAIL $($f.FullName)"
foreach ($e in $errors) {
Write-Host " line $($e.Extent.StartLineNumber): $($e.Message)"
}
} else {
Write-Host "OK $($f.FullName)"
}
}
if ($failed) { exit 1 }

- name: HD agent tests
run: python3 computer-viewer/tests/test-hiperf-agent.py
12 changes: 6 additions & 6 deletions MANIFEST.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ caaaf1e751ddcba1d4b216ab18bd4077d18c4c9c8e85915d45bdbf746a5f95fc computer-viewe
4aa6259a032574b7f363b94abad064ec159ebe845d99043501726e437fca2bac computer-viewer/agent-plugin/orgo-computer/schemas.py
869ee2f55e5660e4c51986541923ac9b850799f50860fcd0ca7fd671c77b95c5 computer-viewer/agent-plugin/orgo-computer/skills/computer-basics/SKILL.md
2f6fc7d4fb1de1ba6bd1b3e81713715e20d575cf142a3d21fafac278a01a7953 computer-viewer/agent-plugin/orgo-computer/tools.py
35e55ae018ff651ff325836edc8f161853b1345fb0d06e6ba65591c9009da412 computer-viewer/connect-linux.sh
447bc5e48cd70db0d177eb496bb3aa3812f32bfd670539a962a6b33f79476dc3 computer-viewer/connect-linux.sh
51f9fa287677242587da208b4d2b62f3275baca1b7860a834cfb874f7ed420bd computer-viewer/connect-mac.sh
963e1c5ac722fbc813a79d1de2a5ba8301de353f968ce71e68e284d5116a311a computer-viewer/connect-windows.ps1
5aeb18940412105fc8fbf00b98c02f2f14c18d7da64af7858f7b5635033ba203 computer-viewer/hiperf-agent.py
f8366e6251f96f160e90d130bdbb50b5668b78e802ef275879b77bb101f34a07 computer-viewer/hiperf-linux.sh
e9d80a434605888da086b24de5292b69e93791f8ec65aa1e47612798705c975a computer-viewer/hiperf-mac.sh
89456ad0f7c192d5e19149dc64254fd7c0a4e1ca348fb41b3105d0edc8453cc8 computer-viewer/hiperf-windows.ps1
81d1cfe7c6efe2acc260d7cc21c25888c49bab052da1d873c1062e24f0d742cc computer-viewer/connect-windows.ps1
1ba00dc54020492dd3a1d61993d1f1511f9246c0de9a1f6b860e3b6782249fa8 computer-viewer/hiperf-agent.py
372a6f5cf5618bd51b13e3f841cd045eaf4e8b9fa8707ef94fc04650d335517f computer-viewer/hiperf-linux.sh
d60528abc1817592c22323ddc8b112c67ef02b6d8204e8b5102d119f375b94b8 computer-viewer/hiperf-mac.sh
1c108d4c236e6685c39fa644ca896ba7430e874b361b2af7873bc1fb91f096e0 computer-viewer/hiperf-windows.ps1
093aee168eb589a88f16a7402654c038b6ebca1aa36e10ea75df811c00cc34d3 computer-viewer/plugin.js
08b527c943eb410ffa1a35d7d14c018d9eba22363150c3dc35a70327f1e88a28 computer-viewer/vendor/novnc-rfb.mjs
0aceb385ca7c9f1d305b2b0d7daa0f177b3a778c60beb6e901a2f0ba5622654c task-dock/plugin.js
Expand Down
2 changes: 1 addition & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Cutting a release

Releases are annotated tags named `vYYYY.MM.DD`. The install one-liners pin
Releases are annotated tags named `vYYYY.MM.DD` (a same-day follow-up is `vYYYY.MM.DD.N`). The install one-liners pin
that tag and check every copied file against that tag's `MANIFEST.sha256`.

## How to cut one
Expand Down
23 changes: 19 additions & 4 deletions computer-viewer/connect-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -209,24 +209,39 @@ WantedBy=default.target
EOF
}

show_effective_listener() {
# `enable --now` is a no-op on an already-active unit, so a re-run used to
# leave the OLD bind in place. Print what is actually listening now.
local port="$1"
command -v ss >/dev/null 2>&1 || return 0
echo " effective listener on :${port}"
ss -ltnp 2>/dev/null | grep ":${port}" || echo " (nothing listening on :${port} yet)"
}

enable_user_units() {
echo "==> systemctl --user daemon-reload && enable --now"
echo "==> systemctl --user daemon-reload, then enable + restart"
if ! command -v systemctl >/dev/null 2>&1; then
echo "systemctl not found. User units written to ${UNIT_DIR} but not started." >&2
echo " After login: systemctl --user daemon-reload && systemctl --user enable --now $*" >&2
echo " After login: systemctl --user daemon-reload && systemctl --user enable $* && systemctl --user restart $*" >&2
return 0
fi
if ! systemctl --user daemon-reload; then
echo "systemd --user is not running (typical over SSH without lingering)." >&2
echo "Units written. On the graphical session run:" >&2
echo " systemctl --user daemon-reload" >&2
echo " systemctl --user enable --now $*" >&2
echo " systemctl --user enable $* && systemctl --user restart $*" >&2
return 0
fi
local u
for u in "$@"; do
systemctl --user enable --now "$u" || echo " failed to enable $u (will be available after login)" >&2
systemctl --user enable "$u" || echo " failed to enable $u (will be available after login)" >&2
if systemctl --user is-active --quiet "$u"; then
systemctl --user restart "$u" || echo " failed to restart $u" >&2
else
systemctl --user start "$u" || echo " failed to start $u (will be available after login)" >&2
fi
done
show_effective_listener "${LISTEN_PORT}"
}

note_linger() {
Expand Down
88 changes: 88 additions & 0 deletions computer-viewer/connect-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,78 @@ $UsbmmiddTaskName = 'ComputerViewerVirtualDisplay'
$UltraVncZipUrl = 'https://uvnc.eu/download/1800/UltraVNC_1824.zip'
$UltraVncDir = Join-Path ${env:ProgramFiles} 'UltraVNC'

# sha256 of the two version-pinned third-party downloads, computed 2026-09-07
# from the exact URLs above. These URLs name a version, so their bytes must not
# change; if a pin ever fails, bump the URL and the pin together, deliberately.
$TightVncSha256 = 'FA86D817AC29C5FFE1E8E7095E738D9BA5CA28AA62304AC234580916622A8CA2'
$UltraVncZipSha256 = '8AF948089626008F02EDD1254AFC15C814E454EC5FC9E3EAA860356F19D4F113'

# $UsbmmiddUrl is an unversioned "latest" link, so no hash can be pinned to it.
# The weaker fallback is an Authenticode check before anything from the zip is
# executed. Note that deviceinstaller64.exe itself carries NO Authenticode
# signature (its PE certificate table is empty, checked 2026-09-07); the signed
# artefact in the zip is the driver catalog usbmmidd.cat, WHQL-signed by
# "Microsoft Windows Hardware Compatibility Publisher" (Amyuni's driver, signed
# through Microsoft's hardware program). That catalog is what Windows itself
# validates at install time, so it is the strongest signal available here.
$UsbmmiddSigner = 'Microsoft Windows Hardware Compatibility Publisher'

function Test-IsAdmin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal $id
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

function Test-PinnedHash {
# $true when the downloaded file matches the pinned sha256.
param([string]$Path, [string]$Expected, [string]$Label)
if (-not (Test-Path -LiteralPath $Path)) {
Write-Warning "$Label was not downloaded; nothing to verify."
return $false
}
$actual = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
if ($actual -ne $Expected.ToUpperInvariant()) {
Write-Warning "$Label sha256 mismatch - refusing to use it."
Write-Warning " expected: $Expected"
Write-Warning " got: $actual"
return $false
}
Write-Host " $Label sha256 $actual verified"
return $true
}

function Test-SignedBy {
# Weaker than a pinned hash - only for downloads behind a moving URL.
# Requires a Valid Authenticode status AND the expected signer subject.
param([string]$Path, [string]$SignerMatch, [string]$Label)
if (-not (Test-Path -LiteralPath $Path)) {
Write-Warning "$Label is missing; cannot check its signature."
return $false
}
$sig = $null
try {
$sig = Get-AuthenticodeSignature -LiteralPath $Path
} catch {
Write-Warning "$Label signature could not be read: $_"
return $false
}
if (-not $sig -or $sig.Status -ne 'Valid') {
$status = if ($sig) { $sig.Status } else { 'none' }
Write-Warning "$Label Authenticode status is '$status', not 'Valid' - refusing to use it."
return $false
}
$subject = ''
if ($sig.SignerCertificate) { $subject = [string]$sig.SignerCertificate.Subject }
if ($subject -notlike "*$SignerMatch*") {
Write-Warning "$Label is signed by an unexpected publisher - refusing to use it."
Write-Warning " expected subject to contain: $SignerMatch"
Write-Warning " got: $subject"
return $false
}
Write-Host " $Label signature Valid, signer $subject"
return $true
}

function Write-Step([string]$Message) {
Write-Host "==> $Message"
}
Expand Down Expand Up @@ -288,6 +354,8 @@ function Install-UsbmmiddVirtualDisplay {
Write-Warning "usbmmidd extract failed: $_"
return $false
}
# No pinned hash is possible for a moving "latest" URL; the driver
# catalog's signature is checked below, before anything is executed.
$installer = Get-ChildItem -LiteralPath $extract -Recurse -Filter 'deviceinstaller64.exe' | Select-Object -First 1
if (-not $installer) {
Write-Warning 'usbmmidd zip did not contain deviceinstaller64.exe'
Expand All @@ -304,6 +372,17 @@ function Install-UsbmmiddVirtualDisplay {
}

if (-not $already) {
# Gate on the driver catalog's Authenticode signature BEFORE running the
# installer as admin. See the $UsbmmiddSigner comment for why this is the
# check available (moving URL, unsigned deviceinstaller64.exe).
$catDir = Split-Path -Parent $exe
$cat = Join-Path $catDir 'usbmmidd.cat'
if (-not (Test-Path -LiteralPath $cat)) { $cat = Join-Path $catDir 'usbmmIdd.cat' }
if (-not (Test-SignedBy -Path $cat -SignerMatch $UsbmmiddSigner -Label 'usbmmidd driver catalog')) {
Write-Warning 'Not running deviceinstaller64.exe.'
Write-Warning 'Fallback: plug in a monitor or an HDMI/DisplayPort dummy plug (~$8), then re-run.'
return $false
}
Write-Host " $exe install usbmmidd.inf usbmmidd"
$outFile = Join-Path $env:TEMP 'usbmmidd-install-out.txt'
$errFile = Join-Path $env:TEMP 'usbmmidd-install-err.txt'
Expand Down Expand Up @@ -449,6 +528,11 @@ function Install-UltraVncHeadlessFallback {
Write-Warning 'TightVNC will keep serving :5900. Expect a black picture on a headless IDD. Dummy-plug fallback still applies.'
return $false
}
if (-not (Test-PinnedHash -Path $zip -Expected $UltraVncZipSha256 -Label 'UltraVNC zip')) {
Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue
Write-Warning 'Not extracting or running UltraVNC. TightVNC keeps serving :5900.'
return $false
}
try {
if (Test-Path -LiteralPath $extract) { Remove-Item -LiteralPath $extract -Recurse -Force }
New-Item -ItemType Directory -Force -Path $extract | Out-Null
Expand Down Expand Up @@ -546,6 +630,10 @@ if (-not $tvnInstalled) {
$msi = Join-Path $env:TEMP 'tightvnc-2.8.88-gpl-setup-64bit.msi'
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $TightVncUrl -OutFile $msi -UseBasicParsing
if (-not (Test-PinnedHash -Path $msi -Expected $TightVncSha256 -Label 'TightVNC MSI')) {
Remove-Item -LiteralPath $msi -Force -ErrorAction SilentlyContinue
throw 'TightVNC MSI failed its pinned sha256 check. Refusing to run msiexec on it.'
}
Write-Step 'Installing TightVNC Server (service, SAS/CAD, VNC auth)'
# MSI password properties are unreliable - see header comment. Print $vncPassword later regardless.
$msiArgs = @(
Expand Down
Loading
Loading