Steps 4-6: the jobs, the set-up tab with generated scripts, Install the staged folder, site bundles and the campus template

- Overview jobs: Change the playlist, Add or remove a HyperDeck, Add a MIDI link, Add a computer,
  Make a PC program startable; 'Add a key for it' on unwatched Companion connections.
- Computers > Setup: steps with a [how] each and the set-up scripts generated per computer by
  settings/scripts.py from the templates in remote-access/ (no site-shaped value left in them);
  Remove this computer drops the fixes and checks that used it.
- Site setup > Site: the running and staged code, Install the staged folder (a one-shot launchd job
  running install.sh --keep-config, refused while live), Export site bundle (zip), New campus from a
  bundle (this campus's site block stays; every copied computer is flagged until touched).
- sites/: na -> the repo config, _template = the empty campus; install.sh --site <id>.
- Review reads appended checks as 'added'; tests for all of it.
- Deployed page-only to NA Ingest. (config.json, status_panel.py and readiness.py are left to the
  READY-page work in progress in the same tree.)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 14:53:08 -04:00
co-authored by Claude Fable 5.1
parent dafa14a512
commit a33e344cae
19 changed files with 804 additions and 59 deletions
-5
View File
@@ -1,5 +0,0 @@
# Paste this into Apple Remote Desktop: select the Macs -> Manage -> Send UNIX Command,
# "Run command as: User: root" (or "Current user"), then Send. It lets NA Ingest's
# status panel log in as whoever is logged in on that Mac, with its key (no password),
# and prints that account name for the config. Safe to run more than once.
U=$(stat -f%Su /dev/console); H=$(dscl . -read "/Users/$U" NFSHomeDirectory | awk '{print $2}'); mkdir -p "$H/.ssh" && touch "$H/.ssh/authorized_keys" && (grep -q 'status-panel@NA-Ingest' "$H/.ssh/authorized_keys" || echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHM2BPUpVpbMxKumLayVSutUsFWUXEfJ/4vxIJ0thbgY status-panel@NA-Ingest' >> "$H/.ssh/authorized_keys") && chmod 700 "$H/.ssh" && chmod 600 "$H/.ssh/authorized_keys" && { [ "$(id -u)" != 0 ] || chown -R "$U:staff" "$H/.ssh"; } && echo "OK user=$U mac=$(scutil --get ComputerName) ssh-group=$(dseditgroup -o checkmember -m "$U" com.apple.access_ssh 2>/dev/null | cut -d' ' -f1)"
+5
View File
@@ -0,0 +1,5 @@
# Paste this into Apple Remote Desktop: select the Macs -> Manage -> Send UNIX Command,
# "Run command as: User: root" (or "Current user"), then Send. It lets {{INGEST_NAME}}'s
# status panel log in as whoever is logged in on that Mac, with its key (no password),
# and prints that account name for the config. Safe to run more than once.
U=$(stat -f%Su /dev/console); H=$(dscl . -read "/Users/$U" NFSHomeDirectory | awk '{print $2}'); mkdir -p "$H/.ssh" && touch "$H/.ssh/authorized_keys" && (grep -q '{{KEY_COMMENT}}' "$H/.ssh/authorized_keys" || echo '{{KEY}}' >> "$H/.ssh/authorized_keys") && chmod 700 "$H/.ssh" && chmod 600 "$H/.ssh/authorized_keys" && { [ "$(id -u)" != 0 ] || chown -R "$U:staff" "$H/.ssh"; } && echo "OK user=$U mac=$(scutil --get ComputerName) ssh-group=$(dseditgroup -o checkmember -m "$U" com.apple.access_ssh 2>/dev/null | cut -d' ' -f1)"
@@ -1,25 +1,25 @@
# Paste into Apple Remote Desktop: select NA ProPresenter Mac, NA Tracks, NA Translation, NA Ingest ->
# Paste into Apple Remote Desktop: select {{DVS_MACS}} ->
# Manage -> Send UNIX Command -> "Run command as: User: root" -> Send.
#
# Lets the status panel start Dante Virtual Soundcard's audio engine, which needs admin rights:
# 1. /usr/local/sbin/status-panel-dvs-start - an admin-owned script that does exactly what
# 1. {{DVS_HELPER}} - an admin-owned script that does exactly what
# pressing Start in DVS does (sets wantToRun = 1 and restarts the DVS service), nothing else.
# 2. /etc/sudoers.d/status-panel - lets the logged-in user run ONLY that script as admin,
# without a password. It is checked with visudo before it is put in place.
# Undo on a Mac: rm /usr/local/sbin/status-panel-dvs-start /etc/sudoers.d/status-panel
# Undo on a Mac: rm {{DVS_HELPER}} /etc/sudoers.d/status-panel
U=$(stat -f%Su /dev/console)
mkdir -p /usr/local/sbin
cat > /usr/local/sbin/status-panel-dvs-start <<'EOS'
cat > {{DVS_HELPER}} <<'EOS'
#!/bin/sh
# Companion status panel (NA Ingest): start Dante Virtual Soundcard's audio engine,
# Companion status panel ({{INGEST_NAME}}): start Dante Virtual Soundcard's audio engine,
# as pressing Start in DVS does. Remove with:
# rm /usr/local/sbin/status-panel-dvs-start /etc/sudoers.d/status-panel
# rm {{DVS_HELPER}} /etc/sudoers.d/status-panel
/usr/bin/defaults write /Library/Preferences/com.audinate.dante.dvs wantToRun -int 1
/bin/launchctl kickstart -k system/com.audinate.dante.DanteVirtualSoundcard
EOS
chown root:wheel /usr/local/sbin/status-panel-dvs-start
chmod 755 /usr/local/sbin/status-panel-dvs-start
echo "$U ALL=(root) NOPASSWD: /usr/local/sbin/status-panel-dvs-start" > /tmp/status-panel.sudoers
chown root:wheel {{DVS_HELPER}}
chmod 755 {{DVS_HELPER}}
echo "$U ALL=(root) NOPASSWD: {{DVS_HELPER}}" > /tmp/status-panel.sudoers
if /usr/sbin/visudo -cf /tmp/status-panel.sudoers >/dev/null 2>&1; then
install -m 440 -o root -g wheel /tmp/status-panel.sudoers /etc/sudoers.d/status-panel
echo "OK user=$U mac=$(scutil --get ComputerName) sudoers.d-included=$(grep -c '^#includedir /private/etc/sudoers.d' /etc/sudoers)"
@@ -1,26 +1,27 @@
# Status panel remote access for NA Ingest - run ONCE on each Windows PC
# (OBS PC, Lighting PC, Bible TV PC), logged in as the normal user.
# Status panel remote access for {{INGEST_NAME}} - run ONCE on {{PC_NAME}}, logged in as the normal user.
# (Generated by the settings page from the computer's record; run it again after adding a program.)
#
# 1. Save this file as C:\Users\<you>\windows-setup.ps1
# 2. Start menu -> type PowerShell -> right-click -> Run as administrator
# 3. Set-ExecutionPolicy RemoteSigned -Scope CurrentUser (answer Y; once per PC)
# 4. Run it: & "$env:USERPROFILE\windows-setup.ps1"
# 5. Send the "RESULT" block it prints back to Claude. If it says RESTART THIS PC,
# restart, then repeat steps 2 and 4.
# 5. Paste the "RESULT" block it prints into the settings page (Computers > Setup). If it says
# RESTART THIS PC, restart, then repeat steps 2 and 4.
#
# It turns on Windows' built-in SSH server, lets NA Ingest log in with its key
# (no password), and creates "start" tasks for the apps it finds (OBS, Resolume
# Arena, rtpMIDI, the Bible app). The tasks start the app on this desktop, which
# It turns on Windows' built-in SSH server, lets {{INGEST_NAME}} log in with its key
# (no password), and creates "start" tasks for the programs listed for this PC
# ({{PROGRAMS}}). The tasks start the program on this desktop, which
# a plain SSH command cannot do on Windows. Safe to run more than once.
$ErrorActionPreference = 'Stop'
$Key = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHM2BPUpVpbMxKumLayVSutUsFWUXEfJ/4vxIJ0thbgY status-panel@NA-Ingest'
$IngestIPs = @('10.1.1.1', '10.6.3.222') # NA Ingest on the 10.1 and 10.6 networks
$Key = '{{KEY}}'
$KeyMark = '{{KEY_COMMENT}}'
$IngestIPs = @({{INGEST_IPS}}) # {{INGEST_NAME}}'s addresses
$notes = @()
# 1. OpenSSH Server, started now and at every boot, allowed through the firewall
$cap = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' | Select-Object -First 1
if (-not $cap) { throw 'This Windows has no OpenSSH Server feature to install - send this message to Claude.' }
if (-not $cap) { throw 'This Windows has no OpenSSH Server feature to install - note this on the settings page.' }
$restart = $false
if ($cap.State -ne 'Installed') {
Write-Host 'Installing the OpenSSH Server (Windows downloads it - this can take 10 minutes)...'
@@ -29,7 +30,7 @@ if ($cap.State -ne 'Installed') {
if (-not (Get-Service sshd -ErrorAction SilentlyContinue)) {
$state = (Get-WindowsCapability -Online -Name $cap.Name).State
Write-Host ''
Write-Host '===== RESULT (send this to Claude) ====='
Write-Host '===== RESULT (paste this into the settings page) ====='
Write-Host ('pc=' + $env:COMPUTERNAME + ' sshd=missing openssh=' + $state + ' restart_needed=' + $restart)
Write-Host 'RESTART THIS PC, then run this script again.'
Write-Host '========================================'
@@ -42,21 +43,21 @@ if (-not (Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction Silentl
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True `
-Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
}
# Windows' own rule covers "Private" networks only; open it on any network, but to NA Ingest alone.
# Windows' own rule covers "Private" networks only; open it on any network, but to {{INGEST_NAME}} alone.
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Profile Any -RemoteAddress $IngestIPs
Enable-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'
# 2. NA Ingest's key. Administrators read C:\ProgramData\ssh\administrators_authorized_keys,
# 2. {{INGEST_NAME}}'s key. Administrators read C:\ProgramData\ssh\administrators_authorized_keys,
# everyone else their own .ssh\authorized_keys - write both.
$adminKeys = Join-Path $env:ProgramData 'ssh\administrators_authorized_keys'
if (-not (Test-Path $adminKeys) -or -not (Select-String -Path $adminKeys -SimpleMatch 'status-panel@NA-Ingest' -Quiet)) {
if (-not (Test-Path $adminKeys) -or -not (Select-String -Path $adminKeys -SimpleMatch $KeyMark -Quiet)) {
Add-Content -Path $adminKeys -Value $Key -Encoding ascii
}
icacls $adminKeys /inheritance:r /grant '*S-1-5-32-544:F' /grant '*S-1-5-18:F' | Out-Null
$userSsh = Join-Path $env:USERPROFILE '.ssh'
New-Item -ItemType Directory -Force -Path $userSsh | Out-Null
$userKeys = Join-Path $userSsh 'authorized_keys'
if (-not (Test-Path $userKeys) -or -not (Select-String -Path $userKeys -SimpleMatch 'status-panel@NA-Ingest' -Quiet)) {
if (-not (Test-Path $userKeys) -or -not (Select-String -Path $userKeys -SimpleMatch $KeyMark -Quiet)) {
Add-Content -Path $userKeys -Value $Key -Encoding ascii
}
@@ -66,22 +67,18 @@ function Find-First($candidates) {
return $null
}
$roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}, (Join-Path $env:LOCALAPPDATA 'Programs')) | Where-Object { $_ }
$bible = Get-ChildItem -Path $roots -Filter '*.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'bible' -and $_.Name -notmatch 'uninst|update|crash|helper' } |
Select-Object -First 1 -ExpandProperty FullName
# Apps can live on any local drive (the LiveStream PC has OBS on D:, the Lighting PC has Vista on G:).
# Programs can live on any local drive; a program file is looked for under the usual folders, then on each drive.
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Free -ne $null } | ForEach-Object { $_.Root.TrimEnd('\') }
$vista = Find-First (@($drives | ForEach-Object { "$_\Vista3\Vista.exe" }) +
@(Get-ChildItem -Path $roots -Filter 'Vista.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object FullName))
function Find-Exe($name) {
$hit = Get-ChildItem -Path $roots -Filter $name -Recurse -Depth 3 -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch 'uninst|update|crash|helper' } | Select-Object -First 1 -ExpandProperty FullName
if (-not $hit) { $hit = Get-ChildItem -Path $drives -Filter $name -Recurse -Depth 2 -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch 'uninst|update|crash|helper|Windows\\' } | Select-Object -First 1 -ExpandProperty FullName }
return $hit
}
$apps = @(
@{ Name = 'OBS'; Exe = (Find-First @($drives | ForEach-Object { "$_\Program Files\obs-studio\bin\64bit\obs64.exe" })); Args = '--disable-shutdown-check' },
@{ Name = 'Vista'; Exe = $vista; Args = '' },
@{ Name = 'Arena'; Exe = (Find-First (Get-ChildItem "$env:ProgramFiles\Resolume Arena*\Arena.exe" -ErrorAction SilentlyContinue | ForEach-Object FullName)); Args = '' },
@{ Name = 'rtpMIDI'; Exe = (Find-First @("${env:ProgramFiles(x86)}\Tobias Erichsen\rtpMIDI\rtpMIDI.exe", "$env:ProgramFiles\Tobias Erichsen\rtpMIDI\rtpMIDI.exe")); Args = '' },
@{ Name = 'BibleApp'; Exe = $bible; Args = ''; AtLogon = $true } # also opens it 15 s after login
{{APPS}}
)
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Limited
# An app ticked "Run this program as an administrator" (OBS on the LiveStream PC) cannot start from a
# A program ticked "Run this program as an administrator" cannot start from a
# Limited task: Windows refuses with 0x800702E4. Its task runs with highest privileges instead.
$elevated = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Highest
$layers = @{}
@@ -109,10 +106,12 @@ foreach ($app in $apps) {
$tasks += ('{0} = {1}{2}' -f $app.Name, $app.Exe, $(if ($app.AtLogon) { ' (also at login)' } else { '' }))
}
# rtpMIDI: if its service ever stops unexpectedly, Windows starts it again (5 s, 5 s, then 30 s).
if (Get-Service rtpMIDIService -ErrorAction SilentlyContinue) {
sc.exe failure rtpMIDIService reset= 86400 actions= restart/5000/restart/5000/restart/30000 | Out-Null
$notes += 'rtpMIDI service restarts itself if it stops'
# Services the panel starts: if one ever stops unexpectedly, Windows starts it again (5 s, 5 s, then 30 s).
foreach ($svc in @({{SERVICES}})) {
if (Get-Service $svc -ErrorAction SilentlyContinue) {
sc.exe failure $svc reset= 86400 actions= restart/5000/restart/5000/restart/30000 | Out-Null
$notes += ($svc + ' restarts itself if it stops')
}
}
# 4. Wake-on-LAN: let the wired adapter wake the PC (the BIOS setting must be on too)
@@ -125,7 +124,7 @@ foreach ($nic in $nics) {
}
Write-Host ''
Write-Host '===== RESULT (send this to Claude) ====='
Write-Host '===== RESULT (paste this into the settings page) ====='
Write-Host ('pc=' + $env:COMPUTERNAME + ' user=' + $env:USERNAME + ' domain=' + $env:USERDOMAIN)
Write-Host ('sshd=' + (Get-Service sshd).Status)
foreach ($nic in $nics) { Write-Host ('nic=' + $nic.Name + ' mac=' + $nic.MacAddress + ' ip=' + ((Get-NetIPAddress -InterfaceIndex $nic.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).IPAddress -join ',') + ' network=' + (Get-NetConnectionProfile -InterfaceIndex $nic.ifIndex -ErrorAction SilentlyContinue).NetworkCategory) }