Muninn has one body: drop the workspace copy
The three muninn crates, their seventeen actuator scripts, the two Raven capture-mux wrappers that called one of them, and the two Muninn docs all leave this workspace. GameCult/Muninn has carried them since 2026-09-09 and has moved on (media stream advertisement, receivers dialling the producer); this copy was a fork drifting behind under the same name, and gamecult-ops now binds every Muninn target to the Muninn repository. Odin keeps the Muninn records in odin-core until they are moved; that is the remaining piece of the extraction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
1ff6a323de
commit
d41c744b2e
34 changed files with 4 additions and 22638 deletions
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %~dp0activate-muninn-raven-av-srt.ps1
|
||||
|
|
@ -1 +0,0 @@
|
|||
throw "activate-muninn-raven-media.ps1 archived: Muninn media activation is daemon-owned and target resolution is through Odin/CultMesh. Redeploy Muninn through Idunn and publish a typed muninn.capture_stream_command with a cultmesh:// media target URI."
|
||||
|
|
@ -1,321 +0,0 @@
|
|||
param(
|
||||
[string] $RavenHost = "raven.Home",
|
||||
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn.exe",
|
||||
[string] $LocalMuninnExe = "",
|
||||
[int] $ConnectTimeoutSeconds = 10,
|
||||
[string] $SshUser = "madman's lullaby",
|
||||
[string] $IdentityFile = "C:\Users\Meta\.ssh\id_ed25519_192_168_1_84",
|
||||
[switch] $SkipBuild,
|
||||
[switch] $SkipRestart,
|
||||
[switch] $PreflightOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($LocalMuninnExe)) {
|
||||
$LocalMuninnExe = Join-Path $repoRoot "target\release\muninn.exe"
|
||||
}
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
Push-Location $repoRoot
|
||||
try {
|
||||
cargo build -p muninn-daemon --release
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "cargo build -p muninn-daemon --release failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LocalMuninnExe)) {
|
||||
throw "Local Muninn executable was not found at '$LocalMuninnExe'."
|
||||
}
|
||||
|
||||
if ($ConnectTimeoutSeconds -lt 1) {
|
||||
throw "ConnectTimeoutSeconds must be at least 1."
|
||||
}
|
||||
|
||||
if (-not $SkipRestart) {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "verify-muninn-media-profile.ps1") `
|
||||
-RestartScript (Join-Path $PSScriptRoot "restart-muninn.ps1")
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "verify-muninn-media-profile.ps1 failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
function Set-AsciiFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Path,
|
||||
[Parameter(Mandatory = $true)] [string] $Content
|
||||
)
|
||||
|
||||
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`r`n"), [System.Text.Encoding]::ASCII)
|
||||
}
|
||||
|
||||
function ConvertTo-PowerShellStringLiteral {
|
||||
param([Parameter(Mandatory = $true)] [string] $Value)
|
||||
return "'" + $Value.Replace("'", "''") + "'"
|
||||
}
|
||||
|
||||
function Get-SshCommonArgs {
|
||||
param([int] $TimeoutSeconds = $ConnectTimeoutSeconds)
|
||||
|
||||
$args = @(
|
||||
"-o", "BatchMode=yes",
|
||||
"-o", "ConnectTimeout=$TimeoutSeconds",
|
||||
"-o", "ConnectionAttempts=1"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($IdentityFile)) {
|
||||
$args += @("-i", $IdentityFile)
|
||||
}
|
||||
return $args
|
||||
}
|
||||
|
||||
function Get-SshTarget {
|
||||
param([Parameter(Mandatory = $true)] [string] $Target)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SshUser)) {
|
||||
return $Target
|
||||
}
|
||||
return "${SshUser}@${Target}"
|
||||
}
|
||||
|
||||
function Get-SshEffectiveConfig {
|
||||
param([Parameter(Mandatory = $true)] [string] $Target)
|
||||
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $Target
|
||||
$output = & ssh.exe -G @commonArgs $sshTarget 2>&1
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
|
||||
$config = @{
|
||||
hostname = $Target
|
||||
user = $null
|
||||
identityfile = @()
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$config.error = ($output -join " ")
|
||||
return $config
|
||||
}
|
||||
|
||||
foreach ($line in $output) {
|
||||
if ($line -match "^hostname\s+(.+)$") {
|
||||
$config.hostname = $Matches[1]
|
||||
} elseif ($line -match "^user\s+(.+)$") {
|
||||
$config.user = $Matches[1]
|
||||
} elseif ($line -match "^identityfile\s+(.+)$") {
|
||||
$config.identityfile += $Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
return $config
|
||||
}
|
||||
|
||||
function Test-TcpPort {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $HostName,
|
||||
[Parameter(Mandatory = $true)] [int] $Port,
|
||||
[Parameter(Mandatory = $true)] [int] $TimeoutSeconds
|
||||
)
|
||||
|
||||
$client = [System.Net.Sockets.TcpClient]::new()
|
||||
try {
|
||||
$async = $client.BeginConnect($HostName, $Port, $null, $null)
|
||||
if (-not $async.AsyncWaitHandle.WaitOne([TimeSpan]::FromSeconds($TimeoutSeconds))) {
|
||||
return "timeout"
|
||||
}
|
||||
$client.EndConnect($async)
|
||||
return "reachable"
|
||||
} catch {
|
||||
return $_.Exception.Message
|
||||
} finally {
|
||||
$client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
function Test-IcmpHost {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $HostName,
|
||||
[Parameter(Mandatory = $true)] [int] $TimeoutSeconds
|
||||
)
|
||||
|
||||
$ping = [System.Net.NetworkInformation.Ping]::new()
|
||||
try {
|
||||
$reply = $ping.Send($HostName, [Math]::Max(1, $TimeoutSeconds) * 1000)
|
||||
if ($null -ne $reply -and $reply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) {
|
||||
return "reachable"
|
||||
}
|
||||
if ($null -ne $reply) {
|
||||
return $reply.Status.ToString()
|
||||
}
|
||||
return "unreachable"
|
||||
} catch {
|
||||
return $_.Exception.Message
|
||||
} finally {
|
||||
$ping.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RavenSshDiagnostic {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Target,
|
||||
[Parameter(Mandatory = $true)] [int] $TimeoutSeconds
|
||||
)
|
||||
|
||||
$config = Get-SshEffectiveConfig -Target $Target
|
||||
$hostName = [string] $config.hostname
|
||||
$icmp = Test-IcmpHost -HostName $hostName -TimeoutSeconds $TimeoutSeconds
|
||||
$tcp22 = Test-TcpPort -HostName $hostName -Port 22 -TimeoutSeconds $TimeoutSeconds
|
||||
$identity = if ($config.identityfile.Count -gt 0) {
|
||||
($config.identityfile -join ",")
|
||||
} else {
|
||||
"default"
|
||||
}
|
||||
$user = if ([string]::IsNullOrWhiteSpace($config.user)) { "default" } else { $config.user }
|
||||
$configError = if ($config.ContainsKey("error")) { "; ssh_config_error=$($config.error)" } else { "" }
|
||||
|
||||
return "resolved_host=$hostName user=$user identityfile=$identity icmp=$icmp tcp22=$tcp22$configError"
|
||||
}
|
||||
|
||||
function Invoke-RavenSshPreflight {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Target,
|
||||
[Parameter(Mandatory = $true)] [int] $TimeoutSeconds
|
||||
)
|
||||
|
||||
$sshArgs = @(
|
||||
(Get-SshTarget -Target $Target),
|
||||
"cmd /c echo raven-ssh-ready"
|
||||
)
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
$commonArgs = Get-SshCommonArgs -TimeoutSeconds $TimeoutSeconds
|
||||
$output = & ssh.exe @commonArgs @sshArgs 2>&1
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$diagnostic = Get-RavenSshDiagnostic -Target $Target -TimeoutSeconds $TimeoutSeconds
|
||||
throw "Raven SSH preflight failed for '$Target' within ${TimeoutSeconds}s: $($output -join ' '); $diagnostic"
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-RavenSshPreflight -Target $RavenHost -TimeoutSeconds $ConnectTimeoutSeconds
|
||||
|
||||
if ($PreflightOnly) {
|
||||
Write-Host "Raven SSH preflight succeeded for '$RavenHost'."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$deployId = [guid]::NewGuid().ToString("N")
|
||||
$localTempRoot = Join-Path $env:TEMP "odin-raven-muninn-binary-$deployId"
|
||||
$localRemoteScript = Join-Path $localTempRoot "deploy-raven-muninn-binary.ps1"
|
||||
$localSftpBatch = Join-Path $localTempRoot "deploy-raven-muninn-binary.sftp"
|
||||
$remoteExeSftpPath = "C:/Windows/Temp/muninn-$deployId.exe"
|
||||
$remoteScriptSftpPath = "C:/Windows/Temp/deploy-raven-muninn-binary-$deployId.ps1"
|
||||
$remoteExePath = "C:\Windows\Temp\muninn-$deployId.exe"
|
||||
$remoteScriptPath = "C:\Windows\Temp\deploy-raven-muninn-binary-$deployId.ps1"
|
||||
$remoteBackupPath = "$MuninnExe.bak-$deployId"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
||||
|
||||
$remoteScript = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
`$target = $(ConvertTo-PowerShellStringLiteral $MuninnExe)
|
||||
`$incoming = $(ConvertTo-PowerShellStringLiteral $remoteExePath)
|
||||
`$backup = $(ConvertTo-PowerShellStringLiteral $remoteBackupPath)
|
||||
|
||||
if (-not (Test-Path -LiteralPath `$incoming)) {
|
||||
throw "Uploaded Muninn executable was not found at `$incoming"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent `$target) | Out-Null
|
||||
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object { `$_.Name -ieq "muninn.exe" } |
|
||||
ForEach-Object {
|
||||
& cmd.exe /c ("taskkill /PID {0} /T /F >nul 2>&1" -f `$_.ProcessId) | Out-Null
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath `$target) {
|
||||
Copy-Item -LiteralPath `$target -Destination `$backup -Force
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath `$incoming -Destination `$target -Force
|
||||
Remove-Item -LiteralPath `$incoming -Force -ErrorAction SilentlyContinue
|
||||
`$item = Get-Item -LiteralPath `$target
|
||||
Write-Output ("muninn.exe deployed length={0} path={1}" -f `$item.Length, `$item.FullName)
|
||||
`$global:LASTEXITCODE = 0
|
||||
"@
|
||||
|
||||
Set-AsciiFile -Path $localRemoteScript -Content $remoteScript
|
||||
Set-AsciiFile -Path $localSftpBatch -Content (@(
|
||||
'put "{0}" "{1}"' -f $LocalMuninnExe, $remoteExeSftpPath
|
||||
'put "{0}" "{1}"' -f $localRemoteScript, $remoteScriptSftpPath
|
||||
) -join "`r`n")
|
||||
|
||||
$sftpArgs = @(
|
||||
"-b", $localSftpBatch,
|
||||
(Get-SshTarget -Target $RavenHost)
|
||||
)
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
& sftp.exe @commonArgs @sftpArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "sftp upload to Raven failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$remoteRunner = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
try {
|
||||
& "$remoteScriptPath"
|
||||
exit 0
|
||||
} finally {
|
||||
Remove-Item -LiteralPath "$remoteScriptPath" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
"@
|
||||
$encodedRunner = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($remoteRunner))
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $RavenHost
|
||||
& ssh.exe @commonArgs $sshTarget "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -OutputFormat Text -EncodedCommand $encodedRunner"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "remote Muninn binary deploy failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (-not $SkipRestart) {
|
||||
$restartArgs = @(
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
(Join-Path $PSScriptRoot "restart-muninn.ps1"),
|
||||
"-RavenHost",
|
||||
$RavenHost,
|
||||
"-MuninnExe",
|
||||
$MuninnExe,
|
||||
"-ConnectTimeoutSeconds",
|
||||
$ConnectTimeoutSeconds.ToString()
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($SshUser)) {
|
||||
$restartArgs += @("-SshUser", $SshUser)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($IdentityFile)) {
|
||||
$restartArgs += @("-IdentityFile", $IdentityFile)
|
||||
}
|
||||
& powershell.exe @restartArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "restart-muninn.ps1 failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
echo health-muninn.cmd archived: Raven Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-remote-telemetry-health. Use health-muninn.ps1 only as an explicit witness inspection, not Idunn health truth.
|
||||
exit /b 2
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
param(
|
||||
[string] $RavenHost = "raven-local",
|
||||
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn.exe",
|
||||
[string] $StorePath = "C:\Meta\Odin\state\muninn.telemetry.cc",
|
||||
[string] $ActivateStorePath = "C:\Meta\Odin\state\muninn.activate.cc",
|
||||
[string] $LogRoot = "C:\Meta\Odin\logs\muninn",
|
||||
[int] $MaxStoreAgeSeconds = 180,
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
|
||||
[int] $ConnectTimeoutSeconds = 10,
|
||||
[string] $SshUser = "madman's lullaby",
|
||||
[string] $IdentityFile = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
||||
}
|
||||
|
||||
function Set-AsciiFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Path,
|
||||
[Parameter(Mandatory = $true)] [string] $Content
|
||||
)
|
||||
|
||||
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`r`n"), [System.Text.Encoding]::ASCII)
|
||||
}
|
||||
|
||||
function Get-SshCommonArgs {
|
||||
$args = @(
|
||||
"-o", "BatchMode=yes",
|
||||
"-o", "ConnectTimeout=$ConnectTimeoutSeconds",
|
||||
"-o", "ConnectionAttempts=1"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($IdentityFile)) {
|
||||
$args += @("-i", $IdentityFile)
|
||||
}
|
||||
return $args
|
||||
}
|
||||
|
||||
function Get-SshTarget {
|
||||
param([Parameter(Mandatory = $true)] [string] $Target)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SshUser)) {
|
||||
return $Target
|
||||
}
|
||||
return "${SshUser}@${Target}"
|
||||
}
|
||||
|
||||
function Invoke-RavenUploadedPowerShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $RavenHost,
|
||||
[Parameter(Mandatory = $true)] [string] $RemoteScriptContent,
|
||||
[Parameter(Mandatory = $true)] [string] $TempPrefix
|
||||
)
|
||||
|
||||
$uploadId = [guid]::NewGuid().ToString("N")
|
||||
$localTempRoot = Join-Path $env:TEMP "$TempPrefix-$uploadId"
|
||||
$localRemoteScript = Join-Path $localTempRoot "$TempPrefix-$uploadId.ps1"
|
||||
$localSftpBatch = Join-Path $localTempRoot "$TempPrefix-$uploadId.sftp"
|
||||
$remoteSftpPath = "C:/Windows/Temp/$TempPrefix-$uploadId.ps1"
|
||||
$remotePsPath = "C:\Windows\Temp\$TempPrefix-$uploadId.ps1"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
||||
Set-AsciiFile -Path $localRemoteScript -Content $RemoteScriptContent
|
||||
Set-AsciiFile -Path $localSftpBatch -Content ('put "{0}" "{1}"' -f $localRemoteScript, $remoteSftpPath)
|
||||
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $RavenHost
|
||||
& sftp.exe @commonArgs -b $localSftpBatch $sshTarget
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
$remoteRunner = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
try {
|
||||
& "$remotePsPath"
|
||||
exit 0
|
||||
} finally {
|
||||
Remove-Item -LiteralPath "$remotePsPath" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
"@
|
||||
$encodedRunner = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($remoteRunner))
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $RavenHost
|
||||
& ssh.exe @commonArgs $sshTarget "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -OutputFormat Text -EncodedCommand $encodedRunner"
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$remoteScript = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
if (-not (Test-Path -LiteralPath "$MuninnExe")) {
|
||||
throw "Muninn executable not found at $MuninnExe"
|
||||
}
|
||||
`$muninnDir = Split-Path -Parent "$MuninnExe"
|
||||
`$taskExpectations = @(
|
||||
@{ Name = "GameCult-Muninn"; Launcher = "powershell"; Vbs = Join-Path `$muninnDir "start-muninn-serve-hidden.vbs"; Ps = Join-Path `$muninnDir "start-muninn-serve.ps1" }
|
||||
)
|
||||
foreach (`$expectation in `$taskExpectations) {
|
||||
`$task = Get-ScheduledTask -TaskName `$expectation.Name -ErrorAction Stop
|
||||
`$action = @(`$task.Actions)[0]
|
||||
if (`$expectation.Launcher -eq "powershell") {
|
||||
if (`$action.Execute -notmatch 'powershell\.exe$') {
|
||||
throw "`$(`$expectation.Name) action executes `$(`$action.Execute), expected powershell.exe"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*`$(`$expectation.Ps)*") {
|
||||
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not reference `$(`$expectation.Ps)"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*-File*") {
|
||||
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not execute a PowerShell launcher"
|
||||
}
|
||||
} else {
|
||||
if (`$action.Execute -notmatch '(^|\\)wscript\.exe$') {
|
||||
throw "`$(`$expectation.Name) action executes `$(`$action.Execute), expected wscript.exe"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*`$(`$expectation.Vbs)*") {
|
||||
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not reference `$(`$expectation.Vbs)"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*//B*" -or `$action.Arguments -notlike "*//Nologo*") {
|
||||
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not force hidden WScript execution"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath `$expectation.Vbs)) {
|
||||
throw "Missing hidden launcher for `$(`$expectation.Name) at `$(`$expectation.Vbs)"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath `$expectation.Ps)) {
|
||||
throw "Missing PowerShell launcher for `$(`$expectation.Name) at `$(`$expectation.Ps)"
|
||||
}
|
||||
`$psContent = Get-Content -LiteralPath `$expectation.Ps -Raw
|
||||
if (`$psContent -like "*'' | ConvertFrom-Json*") {
|
||||
throw "`$(`$expectation.Ps) has an empty encoded-arguments payload"
|
||||
}
|
||||
if (`$psContent -notmatch 'Start-Process') {
|
||||
throw "`$(`$expectation.Ps) does not launch a hidden process"
|
||||
}
|
||||
`$vbs = Get-Content -LiteralPath `$expectation.Vbs -Raw
|
||||
if (`$vbs -match 'cmdPath\s*=') {
|
||||
throw "`$(`$expectation.Vbs) still routes through a cmdPath trampoline"
|
||||
}
|
||||
if (`$vbs -notmatch '\.ps1') {
|
||||
throw "`$(`$expectation.Vbs) does not reference a PowerShell launcher"
|
||||
}
|
||||
}
|
||||
if (Get-ScheduledTask -TaskName "GameCult-Muninn-Activate" -ErrorAction SilentlyContinue) {
|
||||
throw "Obsolete GameCult-Muninn-Activate task is still present on Raven"
|
||||
}
|
||||
if (Get-ScheduledTask -TaskName "GameCult-Muninn-VideoProof" -ErrorAction SilentlyContinue) {
|
||||
throw "Obsolete GameCult-Muninn-VideoProof task is still present on Raven"
|
||||
}
|
||||
`$servePidPath = Join-Path "$LogRoot" "muninn-serve.pid"
|
||||
`$servePid = `$null
|
||||
if (Test-Path -LiteralPath `$servePidPath) {
|
||||
`$servePidText = (Get-Content -LiteralPath `$servePidPath -Raw).Trim()
|
||||
if (`$servePidText -match '^\d+$') {
|
||||
`$servePid = [int] `$servePidText
|
||||
}
|
||||
}
|
||||
`$process = `$null
|
||||
if (`$null -ne `$servePid) {
|
||||
`$process = Get-CimInstance Win32_Process -Filter ("ProcessId = {0}" -f `$servePid) -ErrorAction SilentlyContinue |
|
||||
Where-Object { `$_.Name -ieq "muninn.exe" -and `$_.CommandLine -like "*serve*" } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
if (`$null -eq `$process) {
|
||||
`$process = Get-CimInstance Win32_Process |
|
||||
Where-Object { `$_.Name -ieq "muninn.exe" -and `$_.CommandLine -like "*serve*" } |
|
||||
Select-Object -First 1
|
||||
}
|
||||
if (`$null -eq `$process) {
|
||||
throw "Muninn serve process is not running"
|
||||
}
|
||||
foreach (`$pattern in @(
|
||||
"--host raven",
|
||||
"--activate-store $ActivateStorePath",
|
||||
"--idunn-rudp-health $IdunnRudpHealth",
|
||||
"--idunn-daemon muninn",
|
||||
"--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-health"
|
||||
)) {
|
||||
if (`$process.CommandLine -notlike "*`$pattern*") {
|
||||
throw "Muninn serve process is missing expected command-line segment `${pattern}: `$(`$process.CommandLine)"
|
||||
}
|
||||
}
|
||||
`$conflictingWriter = Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
`$_.Name -like "muninn*.exe" -and
|
||||
`$_.ProcessId -ne `$process.ProcessId -and
|
||||
`$_.ParentProcessId -ne `$process.ProcessId -and
|
||||
`$_.CommandLine -like "*activate*" -and
|
||||
`$_.CommandLine -like "*$StorePath*"
|
||||
} |
|
||||
Select-Object -First 1
|
||||
if (`$null -ne `$conflictingWriter) {
|
||||
throw "Conflicting Muninn activate writer still targets ${StorePath}: `$(`$conflictingWriter.CommandLine)"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath "$StorePath")) {
|
||||
throw "Muninn telemetry store is missing at $StorePath"
|
||||
}
|
||||
`$storeAgeSeconds = (([DateTime]::UtcNow) - (Get-Item -LiteralPath "$StorePath").LastWriteTimeUtc).TotalSeconds
|
||||
if (`$storeAgeSeconds -gt $MaxStoreAgeSeconds) {
|
||||
throw "Muninn telemetry store is stale (`$([math]::Round(`$storeAgeSeconds))s old)"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath `$servePidPath)) {
|
||||
throw "Muninn serve PID file is missing at `$servePidPath"
|
||||
}
|
||||
if (`$null -ne `$servePid -and `$process.ProcessId -ne `$servePid) {
|
||||
throw "Muninn serve PID file references `$servePid but live Muninn serve process is `$(`$process.ProcessId)"
|
||||
}
|
||||
"@
|
||||
|
||||
Invoke-RavenUploadedPowerShell -RavenHost $RavenHost -RemoteScriptContent $remoteScript -TempPrefix "odin-raven-muninn-health"
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
echo health-nightwing-muninn.cmd archived: Nightwing Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-remote-telemetry-and-move-hid. Use health-nightwing-muninn.ps1 only as an explicit witness inspection, not Idunn health truth.
|
||||
exit /b 2
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
param(
|
||||
[string] $SshTarget = "nightwing",
|
||||
[string] $MuninnExe = "/home/metacrat/.local/bin/muninn",
|
||||
[string] $StorePath = "/home/metacrat/.local/state/gamecult/muninn/muninn.telemetry.cc",
|
||||
[string] $PidPath = "/home/metacrat/.local/state/gamecult/muninn/muninn.pid",
|
||||
[string[]] $MoveState = @(),
|
||||
[int] $IntervalSeconds = 15,
|
||||
[int] $MaxStoreAgeSeconds = 180,
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no WireGuard endpoint default is allowed."
|
||||
}
|
||||
|
||||
function Set-AsciiFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Path,
|
||||
[Parameter(Mandatory = $true)] [string] $Content
|
||||
)
|
||||
|
||||
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`n"), [System.Text.Encoding]::ASCII)
|
||||
}
|
||||
|
||||
function Invoke-NightwingUploadedShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $SshTarget,
|
||||
[Parameter(Mandatory = $true)] [string] $RemoteScriptContent,
|
||||
[Parameter(Mandatory = $true)] [string] $TempPrefix
|
||||
)
|
||||
|
||||
$uploadId = [guid]::NewGuid().ToString("N")
|
||||
$localTempRoot = Join-Path $env:TEMP "$TempPrefix-$uploadId"
|
||||
$localRemoteScript = Join-Path $localTempRoot "$TempPrefix-$uploadId.sh"
|
||||
$localSftpBatch = Join-Path $localTempRoot "$TempPrefix-$uploadId.sftp"
|
||||
$remoteScriptPath = "/tmp/$TempPrefix-$uploadId.sh"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
||||
Set-AsciiFile -Path $localRemoteScript -Content $RemoteScriptContent
|
||||
Set-AsciiFile -Path $localSftpBatch -Content ('put "{0}" "{1}"' -f $localRemoteScript, $remoteScriptPath)
|
||||
|
||||
& sftp.exe -b $localSftpBatch $SshTarget
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
& ssh.exe -o BatchMode=yes -o ConnectTimeout=10 $SshTarget "chmod +x '$remoteScriptPath' && bash '$remoteScriptPath'"
|
||||
$remoteExit = $LASTEXITCODE
|
||||
& ssh.exe -o BatchMode=yes -o ConnectTimeout=10 $SshTarget "rm -f '$remoteScriptPath'"
|
||||
exit $remoteExit
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$remoteScript = @"
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [ ! -x '$MuninnExe' ]; then
|
||||
echo 'Muninn executable not found at $MuninnExe' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -f '$PidPath' ] && kill -0 "`$(cat '$PidPath')" 2>/dev/null; then
|
||||
:
|
||||
elif pgrep -f '[m]uninn serve .*--host nightwing' >/dev/null 2>&1; then
|
||||
:
|
||||
else
|
||||
echo 'Muninn serve process is not running on Nightwing' >&2
|
||||
exit 1
|
||||
fi
|
||||
process_line="`$(pgrep -af '[m]uninn serve .*--host nightwing' | head -n1 || true)"
|
||||
if [ -z "`$process_line" ]; then
|
||||
echo 'Nightwing Muninn serve command line is unavailable' >&2
|
||||
exit 1
|
||||
fi
|
||||
for pattern in \
|
||||
'--idunn-rudp-health $IdunnRudpHealth' \
|
||||
'--idunn-daemon nightwing-muninn' \
|
||||
'--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-and-move-hid'
|
||||
do
|
||||
if ! printf '%s\n' "`$process_line" | grep -F -- "`$pattern" >/dev/null 2>&1; then
|
||||
echo "Nightwing Muninn serve command line is missing `$pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ ! -f '$StorePath' ]; then
|
||||
echo 'Muninn telemetry store is missing on Nightwing' >&2
|
||||
exit 1
|
||||
fi
|
||||
store_mtime="`$(stat -c %Y '$StorePath')"
|
||||
now="`$(date +%s)"
|
||||
store_age="`$((now - store_mtime))"
|
||||
if [ "`$store_age" -gt '$MaxStoreAgeSeconds' ]; then
|
||||
echo "Muninn telemetry store is stale on Nightwing (`$store_age s old)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if printf '%s\n' "`$process_line" | grep -F -- '--move-state' >/dev/null 2>&1 || \
|
||||
printf '%s\n' "`$process_line" | grep -F -- '--move-evidence-stream' >/dev/null 2>&1; then
|
||||
identity_output="`$('$MuninnExe' move-identity-status --store '$StorePath' --host nightwing)"
|
||||
if printf '%s\n' "`$identity_output" | grep -Fq 'No Muninn Move identity records found'; then
|
||||
echo 'Muninn Move identity roster is empty on Nightwing' >&2
|
||||
exit 1
|
||||
fi
|
||||
identity_count="`$(printf '%s\n' "`$identity_output" | grep -c 'move-identity move=')"
|
||||
echo "Muninn healthy: nightwing store_age=`${store_age}s move_runtime=explicit move_identities=`${identity_count}"
|
||||
else
|
||||
echo "Muninn healthy: nightwing store_age=`${store_age}s move_runtime=disabled"
|
||||
fi
|
||||
"@
|
||||
|
||||
Invoke-NightwingUploadedShell -SshTarget $SshTarget -RemoteScriptContent $remoteScript -TempPrefix "odin-nightwing-muninn-health"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
echo health-starfire-muninn.cmd archived: Starfire Muninn health is daemon-published over CultNet/RUDP as muninn.cultnet-rudp-local-telemetry-and-quest-access. Use health-starfire-muninn.ps1 only as an explicit witness inspection, not Idunn health truth.
|
||||
exit /b 2
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
param(
|
||||
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn.exe",
|
||||
[string] $StorePath = "C:\Meta\Odin\state\starfire.muninn.telemetry.cc",
|
||||
[string] $LogRoot = "C:\Meta\Odin\logs\starfire-muninn",
|
||||
[string[]] $MoveState = @(),
|
||||
[int] $MaxStoreAgeSeconds = 180,
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $MuninnExe)) {
|
||||
throw "Muninn executable not found at $MuninnExe"
|
||||
}
|
||||
|
||||
$process = Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
$_.Name -ieq "muninn.exe" -and
|
||||
$_.CommandLine -like "*serve*" -and
|
||||
$_.CommandLine -like "*--host*starfire*" -and
|
||||
$_.CommandLine -like "*--quest-adb*"
|
||||
} |
|
||||
Select-Object -First 1
|
||||
if ($null -eq $process) {
|
||||
throw "Starfire Muninn serve process is not running with Quest access enabled"
|
||||
}
|
||||
foreach ($pattern in @(
|
||||
"--idunn-rudp-health $IdunnRudpHealth",
|
||||
"--idunn-daemon starfire-muninn",
|
||||
"--idunn-health-contract muninn.cultnet-rudp-local-telemetry-and-quest-access"
|
||||
)) {
|
||||
if ($process.CommandLine -notlike "*$pattern*") {
|
||||
throw "Starfire Muninn serve process is missing expected command-line segment ${pattern}: $($process.CommandLine)"
|
||||
}
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $StorePath)) {
|
||||
throw "Starfire Muninn telemetry store is missing at $StorePath"
|
||||
}
|
||||
$storeAgeSeconds = (([DateTime]::UtcNow) - (Get-Item -LiteralPath $StorePath).LastWriteTimeUtc).TotalSeconds
|
||||
if ($storeAgeSeconds -gt $MaxStoreAgeSeconds) {
|
||||
throw "Starfire Muninn telemetry store is stale ($([math]::Round($storeAgeSeconds))s old)"
|
||||
}
|
||||
$pidPath = Join-Path $LogRoot "muninn-serve.pid"
|
||||
if (-not (Test-Path -LiteralPath $pidPath)) {
|
||||
throw "Starfire Muninn serve PID file is missing at $pidPath"
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
param(
|
||||
[string] $RavenHost = "raven",
|
||||
[string] $MuninnDir = "C:\Meta\Odin\Muninn"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$remoteScript = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
function Register-HiddenVbsTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath `$VbsPath)) {
|
||||
throw "Hidden launcher not found for `$TaskName at `$VbsPath"
|
||||
}
|
||||
|
||||
`$action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "//B //Nologo ""`$VbsPath"""
|
||||
`$trigger = New-ScheduledTaskTrigger -Once -At ([DateTime]::Today.AddHours(23).AddMinutes(59))
|
||||
`$principal = New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Limited
|
||||
`$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew
|
||||
Register-ScheduledTask -TaskName `$TaskName -Action `$action -Trigger `$trigger -Principal `$principal -Settings `$settings -Force | Out-Null
|
||||
}
|
||||
|
||||
function Assert-HiddenVbsTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath
|
||||
)
|
||||
|
||||
`$task = Get-ScheduledTask -TaskName `$TaskName -ErrorAction Stop
|
||||
`$action = @(`$task.Actions)[0]
|
||||
if (`$action.Execute -notmatch '(^|\\)wscript\.exe$') {
|
||||
throw "`$TaskName action executes `$(`$action.Execute), expected wscript.exe"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*`$VbsPath*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not reference `$VbsPath"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*//B*" -or `$action.Arguments -notlike "*//Nologo*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not force background WScript execution"
|
||||
}
|
||||
`$vbs = Get-Content -LiteralPath `$VbsPath -Raw
|
||||
if (`$vbs -match 'cmdPath\s*=') {
|
||||
throw "`$TaskName hidden launcher at `$VbsPath still routes through a cmdPath trampoline"
|
||||
}
|
||||
if (`$vbs -notmatch '\.ps1') {
|
||||
throw "`$TaskName hidden launcher at `$VbsPath does not reference a PowerShell launcher"
|
||||
}
|
||||
}
|
||||
|
||||
`$serveVbs = Join-Path "$MuninnDir" "start-muninn-serve-hidden.vbs"
|
||||
`$activateCmd = Join-Path "$MuninnDir" "activate-raven-av-srt.cmd"
|
||||
`$activatePs = Join-Path "$MuninnDir" "activate-raven-av-srt.ps1"
|
||||
`$activateVbs = Join-Path "$MuninnDir" "activate-raven-av-srt-hidden.vbs"
|
||||
`$videoProofCmd = Join-Path "$MuninnDir" "muninn-raven-video-to-starfire-obs.cmd"
|
||||
`$videoProofPs = Join-Path "$MuninnDir" "muninn-raven-video-to-starfire-obs.ps1"
|
||||
`$videoProofVbs = Join-Path "$MuninnDir" "muninn-raven-video-to-starfire-obs-hidden.vbs"
|
||||
|
||||
function Write-HiddenPowerShellVbsLauncher {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$PsPath,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath `$PsPath)) {
|
||||
throw "PowerShell launcher not found at `$PsPath"
|
||||
}
|
||||
|
||||
`$lines = @(
|
||||
'Set fso = CreateObject("Scripting.FileSystemObject")',
|
||||
'scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)',
|
||||
'Set shell = CreateObject("WScript.Shell")',
|
||||
"psLauncher = ""`$PsPath""",
|
||||
'shell.CurrentDirectory = scriptDir',
|
||||
'shell.Run "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & psLauncher & """", 0, False'
|
||||
)
|
||||
Set-Content -LiteralPath `$VbsPath -Value `$lines -Encoding ASCII
|
||||
}
|
||||
|
||||
function Write-WscriptCmdLauncher {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$CmdPath,
|
||||
[Parameter(Mandatory = `$true)] [string] `$WorkingDirectory,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath
|
||||
)
|
||||
|
||||
`$cmdLines = @(
|
||||
'@echo off',
|
||||
"cd /d ""`$WorkingDirectory""",
|
||||
"wscript.exe //B //Nologo ""`$VbsPath"""
|
||||
)
|
||||
Set-Content -LiteralPath `$CmdPath -Value `$cmdLines -Encoding ASCII
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath `$activatePs) {
|
||||
Write-HiddenPowerShellVbsLauncher -PsPath `$activatePs -VbsPath `$activateVbs
|
||||
if (Test-Path -LiteralPath `$activateCmd) {
|
||||
Write-WscriptCmdLauncher -CmdPath `$activateCmd -WorkingDirectory "$MuninnDir" -VbsPath `$activateVbs
|
||||
}
|
||||
}
|
||||
if (Test-Path -LiteralPath `$videoProofPs) {
|
||||
Write-HiddenPowerShellVbsLauncher -PsPath `$videoProofPs -VbsPath `$videoProofVbs
|
||||
if (Test-Path -LiteralPath `$videoProofCmd) {
|
||||
Write-WscriptCmdLauncher -CmdPath `$videoProofCmd -WorkingDirectory "$MuninnDir" -VbsPath `$videoProofVbs
|
||||
}
|
||||
}
|
||||
|
||||
Register-HiddenVbsTask -TaskName "GameCult-Muninn" -VbsPath `$serveVbs
|
||||
if (Test-Path -LiteralPath `$activateVbs) {
|
||||
Register-HiddenVbsTask -TaskName "GameCult-Muninn-Activate" -VbsPath `$activateVbs
|
||||
}
|
||||
if (Test-Path -LiteralPath `$videoProofVbs) {
|
||||
Register-HiddenVbsTask -TaskName "GameCult-Muninn-VideoProof" -VbsPath `$videoProofVbs
|
||||
}
|
||||
|
||||
Assert-HiddenVbsTask -TaskName "GameCult-Muninn" -VbsPath `$serveVbs
|
||||
if (Test-Path -LiteralPath `$activateVbs) {
|
||||
Assert-HiddenVbsTask -TaskName "GameCult-Muninn-Activate" -VbsPath `$activateVbs
|
||||
}
|
||||
if (Test-Path -LiteralPath `$videoProofVbs) {
|
||||
Assert-HiddenVbsTask -TaskName "GameCult-Muninn-VideoProof" -VbsPath `$videoProofVbs
|
||||
}
|
||||
"@
|
||||
|
||||
$uploadId = [guid]::NewGuid().ToString("N")
|
||||
$localRemoteScript = Join-Path $env:TEMP "odin-raven-muninn-task-repair-$uploadId.ps1"
|
||||
$localSftpBatch = Join-Path $env:TEMP "odin-raven-muninn-task-repair-$uploadId.sftp"
|
||||
$remoteSftpPath = "C:/Windows/Temp/odin-raven-muninn-task-repair-$uploadId.ps1"
|
||||
$remotePsPath = "C:\Windows\Temp\odin-raven-muninn-task-repair-$uploadId.ps1"
|
||||
try {
|
||||
Set-Content -LiteralPath $localRemoteScript -Encoding ASCII -Value $remoteScript
|
||||
Set-Content -LiteralPath $localSftpBatch -Encoding ASCII -Value "put ""$localRemoteScript"" ""$remoteSftpPath"""
|
||||
& sftp.exe -b $localSftpBatch $RavenHost
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
$remoteRunner = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
try {
|
||||
& "$remotePsPath"
|
||||
`$code = `$LASTEXITCODE
|
||||
} finally {
|
||||
Remove-Item -LiteralPath "$remotePsPath" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
exit `$code
|
||||
"@
|
||||
$encodedRunner = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($remoteRunner))
|
||||
& ssh.exe -o BatchMode=yes -o ConnectTimeout=10 $RavenHost "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedRunner"
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localRemoteScript, $localSftpBatch -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
@echo off
|
||||
if not "%IDUNN_ACTUATOR%"=="1" (
|
||||
echo restart-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
if /I not "%IDUNN_COMMAND_AUTHORITY%"=="idunn-daemon" (
|
||||
echo restart-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %~dp0restart-muninn.ps1
|
||||
|
|
@ -1,897 +0,0 @@
|
|||
param(
|
||||
[string] $RavenHost = "raven.Home",
|
||||
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn.exe",
|
||||
[string] $StorePath = "C:\Meta\Odin\state\muninn.telemetry.cc",
|
||||
[string] $ActivateStorePath = "C:\Meta\Odin\state\muninn.activate.cc",
|
||||
[string] $LogRoot = "C:\Meta\Odin\logs\muninn",
|
||||
[string] $LocalLoopbackScript = (Join-Path $PSScriptRoot "wasapi-loopback-capture.ps1"),
|
||||
[string] $LoopbackScript = "C:\Meta\Odin\Muninn\scripts\wasapi-loopback-capture.ps1",
|
||||
[string] $Ffmpeg = "C:\Users\Madman's Lullaby\AppData\Local\Microsoft\WinGet\Links\ffmpeg.exe",
|
||||
[string] $MediaTargetUri = $env:MUNINN_MEDIA_TARGET_URI,
|
||||
[string] $AudioDevice = "Realtek",
|
||||
[string[]] $VideoSources = @(),
|
||||
[string[]] $AudioSources = @(),
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
|
||||
[string] $IdunnDaemon = "muninn",
|
||||
[string] $IdunnHealthContract = "muninn.cultnet-rudp-remote-telemetry-health",
|
||||
[string] $OdinCultMeshUri = $(if ($env:ODIN_CULTMESH_URI) { $env:ODIN_CULTMESH_URI } else { "cultmesh://odin/rendezvous/provider-catalog" }),
|
||||
[string] $OdinCultMeshRudpEndpoint = $env:CULTMESH_URI_ODIN_RUDP,
|
||||
[string[]] $MoveStates = @("xbox-raven=xinput://0"),
|
||||
[string] $HidControllerRudpTarget = "",
|
||||
[string] $HidControllerRudpBind = "0.0.0.0:17887",
|
||||
[string] $HidControllerRudpAdvertise = $env:MUNINN_HID_CONTROLLER_RUDP_ADVERTISE,
|
||||
[int] $ConnectTimeoutSeconds = 10,
|
||||
[int] $ServeStartTimeoutSeconds = 20,
|
||||
[string] $SshUser = "madman's lullaby",
|
||||
[string] $IdentityFile = "C:\Users\Meta\.ssh\id_ed25519_192_168_1_84"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($env:IDUNN_ACTUATOR -ne "1" -or $env:IDUNN_COMMAND_AUTHORITY -ne "idunn-daemon") {
|
||||
throw "restart-muninn.ps1 is an Idunn actuator body. Redeploy by poking Idunn; direct service restart is not an owned path."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LocalLoopbackScript)) {
|
||||
throw "Local Muninn loopback script not found at $LocalLoopbackScript"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($MediaTargetUri) -or -not $MediaTargetUri.StartsWith("cultmesh://", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Muninn media target URI must be supplied by -MediaTargetUri or MUNINN_MEDIA_TARGET_URI and must start with cultmesh://."
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshUri) -and $OdinCultMeshUri.StartsWith("cultmesh://", [System.StringComparison]::OrdinalIgnoreCase) -and [string]::IsNullOrWhiteSpace($OdinCultMeshRudpEndpoint)) {
|
||||
throw "Odin CultMesh RUDP endpoint must be supplied by -OdinCultMeshRudpEndpoint or CULTMESH_URI_ODIN_RUDP when -OdinCultMeshUri is a cultmesh:// URI."
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind) -and [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
throw "HID controller RUDP advertise endpoint must be supplied by -HidControllerRudpAdvertise or MUNINN_HID_CONTROLLER_RUDP_ADVERTISE when HID RUDP bind is enabled; no Raven LAN default is allowed."
|
||||
}
|
||||
|
||||
function Set-AsciiFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Path,
|
||||
[Parameter(Mandatory = $true)] [string] $Content
|
||||
)
|
||||
|
||||
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`r`n"), [System.Text.Encoding]::ASCII)
|
||||
}
|
||||
|
||||
function ConvertTo-PowerShellStringLiteral {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Value
|
||||
)
|
||||
|
||||
return "'" + $Value.Replace("'", "''") + "'"
|
||||
}
|
||||
|
||||
function ConvertTo-PowerShellArrayLiteral {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string[]] $Values
|
||||
)
|
||||
|
||||
$lines = $Values | ForEach-Object { " {0}" -f (ConvertTo-PowerShellStringLiteral $_) }
|
||||
return "@(`r`n{0}`r`n)" -f ($lines -join ",`r`n")
|
||||
}
|
||||
|
||||
function ConvertTo-PowerShellEnvAssignmentLines {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [hashtable] $Environment
|
||||
)
|
||||
|
||||
$lines = @()
|
||||
foreach ($key in ($Environment.Keys | Sort-Object)) {
|
||||
$name = [string] $key
|
||||
if ($name -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') {
|
||||
throw "Invalid environment variable name for Muninn launcher: $name"
|
||||
}
|
||||
$value = [string] $Environment[$key]
|
||||
$lines += '$env:{0} = {1}' -f $name, (ConvertTo-PowerShellStringLiteral $value)
|
||||
}
|
||||
return ($lines -join "`r`n")
|
||||
}
|
||||
|
||||
function Get-SshCommonArgs {
|
||||
$args = @(
|
||||
"-o", "BatchMode=yes",
|
||||
"-o", "ConnectTimeout=$ConnectTimeoutSeconds",
|
||||
"-o", "ConnectionAttempts=1"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($IdentityFile)) {
|
||||
$args += @("-i", $IdentityFile)
|
||||
}
|
||||
return $args
|
||||
}
|
||||
|
||||
function Get-SshTarget {
|
||||
param([Parameter(Mandatory = $true)] [string] $Target)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SshUser)) {
|
||||
return $Target
|
||||
}
|
||||
return "${SshUser}@${Target}"
|
||||
}
|
||||
|
||||
function Test-LikelyVirtualDisplayToken {
|
||||
param([Parameter(Mandatory = $true)] [string] $Token)
|
||||
|
||||
$trimmed = $Token.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
return $true
|
||||
}
|
||||
return (
|
||||
$trimmed.StartsWith('MSBDD_', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('MSNIL', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('NOEDID', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('UGD', [System.StringComparison]::OrdinalIgnoreCase)
|
||||
)
|
||||
}
|
||||
|
||||
function Get-GraphicsDriverDisplayTokens {
|
||||
$connectivityRoot = 'HKLM:\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\Connectivity'
|
||||
if (-not (Test-Path -LiteralPath $connectivityRoot)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$bestTokens = @()
|
||||
$bestScore = [int]::MinValue
|
||||
foreach ($key in Get-ChildItem -LiteralPath $connectivityRoot -ErrorAction SilentlyContinue) {
|
||||
$item = Get-ItemProperty -LiteralPath $key.PSPath -ErrorAction SilentlyContinue
|
||||
if ($null -eq $item) {
|
||||
continue
|
||||
}
|
||||
foreach ($property in $item.PSObject.Properties) {
|
||||
if ($property.Name -like 'PS*' -or $property.Value -isnot [string]) {
|
||||
continue
|
||||
}
|
||||
$tokens = @(
|
||||
$property.Value.Split('+') |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
)
|
||||
if ($tokens.Count -lt 1) {
|
||||
continue
|
||||
}
|
||||
$realCount = @($tokens | Where-Object { -not (Test-LikelyVirtualDisplayToken $_) }).Count
|
||||
$score = ($realCount * 100) + $tokens.Count
|
||||
if ($score -gt $bestScore) {
|
||||
$bestScore = $score
|
||||
$bestTokens = $tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @($bestTokens | Select-Object -Unique)
|
||||
}
|
||||
|
||||
function Get-DefaultMuninnVideoSourceLabels {
|
||||
param([Parameter(Mandatory = $true)] [string] $HostLabel)
|
||||
|
||||
$displayNames = @(
|
||||
Get-CimInstance Win32_DesktopMonitor -ErrorAction SilentlyContinue |
|
||||
Where-Object { [string]::IsNullOrWhiteSpace($_.Status) -or $_.Status -eq 'OK' } |
|
||||
ForEach-Object {
|
||||
if ([string]::IsNullOrWhiteSpace($_.Name)) {
|
||||
'Display'
|
||||
} else {
|
||||
$_.Name.Trim()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$useRegistryFallback = (
|
||||
$displayNames.Count -lt 1 -or
|
||||
($displayNames.Count -eq 1 -and (
|
||||
$displayNames[0] -match '^\s*WinDisc\s*$' -or
|
||||
$displayNames[0] -match '^\s*Generic PnP Monitor\s*$' -or
|
||||
$displayNames[0] -match '^\s*Display\s*$'
|
||||
))
|
||||
)
|
||||
|
||||
if ($useRegistryFallback) {
|
||||
$displayTokens = @(Get-GraphicsDriverDisplayTokens)
|
||||
if ($displayTokens.Count -gt 0) {
|
||||
return @(for ($index = 0; $index -lt $displayTokens.Count; $index++) {
|
||||
'{0} display {1}' -f $HostLabel, ($index + 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if ($displayNames.Count -lt 1) {
|
||||
$displayNames = @('Display')
|
||||
}
|
||||
|
||||
return @(for ($index = 0; $index -lt $displayNames.Count; $index++) {
|
||||
$monitorName = $displayNames[$index]
|
||||
if ($monitorName -eq 'Display') {
|
||||
'{0} display {1}' -f $HostLabel, ($index + 1)
|
||||
} else {
|
||||
'{0} display {1} ({2})' -f $HostLabel, ($index + 1), $monitorName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function New-HiddenNativeLauncherPowerShellContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $false)] [string] $StorePath,
|
||||
[Parameter(Mandatory = $true)] [string] $LogRoot,
|
||||
[Parameter(Mandatory = $true)] [string[]] $Arguments,
|
||||
[Parameter(Mandatory = $true)] [string] $FilePath,
|
||||
[Parameter(Mandatory = $true)] [string] $StdoutPath,
|
||||
[Parameter(Mandatory = $true)] [string] $StderrPath,
|
||||
[Parameter(Mandatory = $true)] [string] $PidPath,
|
||||
[Parameter(Mandatory = $false)] [string] $WorkingDirectory,
|
||||
[Parameter(Mandatory = $false)] [hashtable] $Environment = @{},
|
||||
[Parameter(Mandatory = $false)] [string] $DynamicArgumentsScript,
|
||||
[Parameter(Mandatory = $false)] [switch] $WaitForExit
|
||||
)
|
||||
|
||||
$template = @'
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
__STORE_DIR_LINE__
|
||||
New-Item -ItemType Directory -Force -Path __LOG_ROOT__ | Out-Null
|
||||
$launcherLog = __PID_PATH__ + '.launcher.log'
|
||||
('launcher-start ' + [DateTime]::UtcNow.ToString('o')) | Set-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
__ENVIRONMENT_LINES__
|
||||
$arguments = __ARGUMENTS__
|
||||
__DYNAMIC_ARGUMENTS_SCRIPT__
|
||||
function Quote-NativeArgument([string] $Value) {
|
||||
if ($Value -match '[\s"]') {
|
||||
return '"' + $Value.Replace('"', '\"') + '"'
|
||||
}
|
||||
return $Value
|
||||
}
|
||||
$argumentLine = ($arguments | ForEach-Object { Quote-NativeArgument $_ }) -join ' '
|
||||
('launcher-args ' + $argumentLine) | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
if (__RUN_INLINE__) {
|
||||
$PID | Set-Content -Encoding ASCII -LiteralPath __PID_PATH__
|
||||
('launcher-pid ' + $PID + ' powershell-inline') | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
try {
|
||||
& __FILE_PATH__ @arguments > __STDOUT_PATH__ 2> __STDERR_PATH__
|
||||
$exitCode = $LASTEXITCODE
|
||||
} catch {
|
||||
('launcher-error ' + $_.Exception.Message) | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
throw
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrorActionPreference
|
||||
}
|
||||
('launcher-exit ' + $exitCode) | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
exit $exitCode
|
||||
}
|
||||
try {
|
||||
$process = Start-Process -FilePath __FILE_PATH__ -ArgumentList $argumentLine __WORKING_DIRECTORY_CLAUSE__-WindowStyle Hidden -PassThru -RedirectStandardOutput __STDOUT_PATH__ -RedirectStandardError __STDERR_PATH__
|
||||
$process.Id | Set-Content -Encoding ASCII -LiteralPath __PID_PATH__
|
||||
('launcher-pid ' + $process.Id) | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
} catch {
|
||||
('launcher-error ' + $_.Exception.Message) | Add-Content -Encoding ASCII -LiteralPath $launcherLog
|
||||
throw
|
||||
}
|
||||
__WAIT_FOR_EXIT_LINE__
|
||||
'@
|
||||
|
||||
$storeDirLine = if ([string]::IsNullOrWhiteSpace($StorePath)) {
|
||||
""
|
||||
} else {
|
||||
"New-Item -ItemType Directory -Force -Path (Split-Path -Parent {0}) | Out-Null" -f (ConvertTo-PowerShellStringLiteral $StorePath)
|
||||
}
|
||||
$workingDirectoryClause = if ([string]::IsNullOrWhiteSpace($WorkingDirectory)) {
|
||||
""
|
||||
} else {
|
||||
"-WorkingDirectory {0} " -f (ConvertTo-PowerShellStringLiteral $WorkingDirectory)
|
||||
}
|
||||
|
||||
return $template.
|
||||
Replace("__STORE_DIR_LINE__", $storeDirLine).
|
||||
Replace("__LOG_ROOT__", (ConvertTo-PowerShellStringLiteral $LogRoot)).
|
||||
Replace("__ENVIRONMENT_LINES__", (ConvertTo-PowerShellEnvAssignmentLines $Environment)).
|
||||
Replace("__ARGUMENTS__", (ConvertTo-PowerShellArrayLiteral $Arguments)).
|
||||
Replace("__DYNAMIC_ARGUMENTS_SCRIPT__", $DynamicArgumentsScript).
|
||||
Replace("__FILE_PATH__", (ConvertTo-PowerShellStringLiteral $FilePath)).
|
||||
Replace("__WORKING_DIRECTORY_CLAUSE__", $workingDirectoryClause).
|
||||
Replace("__STDOUT_PATH__", (ConvertTo-PowerShellStringLiteral $StdoutPath)).
|
||||
Replace("__STDERR_PATH__", (ConvertTo-PowerShellStringLiteral $StderrPath)).
|
||||
Replace("__PID_PATH__", (ConvertTo-PowerShellStringLiteral $PidPath)).
|
||||
Replace("__RUN_INLINE__", $(if ($WaitForExit) { '$true' } else { '$false' })).
|
||||
Replace("__WAIT_FOR_EXIT_LINE__", $(if ($WaitForExit) { '$process.WaitForExit(); exit $process.ExitCode' } else { "" }))
|
||||
}
|
||||
|
||||
function New-HiddenPowerShellVbsLauncherContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $PsPath,
|
||||
[Parameter(Mandatory = $false)] [switch] $WaitForExit
|
||||
)
|
||||
|
||||
return (@'
|
||||
Set fso = CreateObject("Scripting.FileSystemObject")
|
||||
scriptDir = fso.GetParentFolderName(WScript.ScriptFullName)
|
||||
psLauncher = "{0}"
|
||||
logPath = psLauncher & ".vbs.log"
|
||||
Set logFile = fso.OpenTextFile(logPath, 8, True)
|
||||
logFile.WriteLine "vbs-start " & Now
|
||||
logFile.Close
|
||||
Set shell = CreateObject("WScript.Shell")
|
||||
shell.CurrentDirectory = scriptDir
|
||||
exitCode = shell.Run("powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & psLauncher & """", 0, {1})
|
||||
Set logFile = fso.OpenTextFile(logPath, 8, True)
|
||||
logFile.WriteLine "vbs-exit " & exitCode & " " & Now
|
||||
logFile.Close
|
||||
'@) -f $PsPath, ($(if ($WaitForExit) { "True" } else { "False" }))
|
||||
}
|
||||
|
||||
function New-WscriptCmdLauncherContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $WorkingDirectory,
|
||||
[Parameter(Mandatory = $true)] [string] $VbsPath
|
||||
)
|
||||
|
||||
return (@'
|
||||
@echo off
|
||||
cd /d "{0}"
|
||||
wscript.exe //B //Nologo "{1}"
|
||||
'@) -f $WorkingDirectory, $VbsPath
|
||||
}
|
||||
|
||||
function Invoke-RavenUploadedPowerShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $RavenHost,
|
||||
[Parameter(Mandatory = $true)] [string] $RemoteScriptContent,
|
||||
[Parameter(Mandatory = $true)] [object[]] $UploadSpecs,
|
||||
[Parameter(Mandatory = $true)] [string] $TempPrefix
|
||||
)
|
||||
|
||||
$uploadId = [guid]::NewGuid().ToString("N")
|
||||
$localTempRoot = Join-Path $env:TEMP "$TempPrefix-$uploadId"
|
||||
$localRemoteScript = Join-Path $localTempRoot "$TempPrefix-$uploadId.ps1"
|
||||
$localSftpBatch = Join-Path $localTempRoot "$TempPrefix-$uploadId.sftp"
|
||||
$remoteSftpPath = "C:/Windows/Temp/$TempPrefix-$uploadId.ps1"
|
||||
$remotePsPath = "C:\Windows\Temp\$TempPrefix-$uploadId.ps1"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
||||
Set-AsciiFile -Path $localRemoteScript -Content $RemoteScriptContent
|
||||
|
||||
$batchLines = @()
|
||||
foreach ($spec in $UploadSpecs) {
|
||||
$batchLines += 'put "{0}" "{1}"' -f $spec.LocalPath, $spec.RemotePath
|
||||
}
|
||||
$batchLines += 'put "{0}" "{1}"' -f $localRemoteScript, $remoteSftpPath
|
||||
Set-AsciiFile -Path $localSftpBatch -Content ($batchLines -join "`r`n")
|
||||
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $RavenHost
|
||||
& sftp.exe @commonArgs -b $localSftpBatch $sshTarget
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
$remoteRunner = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
try {
|
||||
& "$remotePsPath"
|
||||
exit 0
|
||||
} finally {
|
||||
Remove-Item -LiteralPath "$remotePsPath" -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
"@
|
||||
$encodedRunner = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($remoteRunner))
|
||||
$commonArgs = Get-SshCommonArgs
|
||||
$sshTarget = Get-SshTarget -Target $RavenHost
|
||||
& ssh.exe @commonArgs $sshTarget "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -OutputFormat Text -EncodedCommand $encodedRunner"
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$muninnDir = Split-Path -Parent $MuninnExe
|
||||
$serveCmdPath = Join-Path $muninnDir "start-muninn-serve.cmd"
|
||||
$servePsPath = Join-Path $muninnDir "start-muninn-serve.ps1"
|
||||
$serveVbsPath = Join-Path $muninnDir "start-muninn-serve-hidden.vbs"
|
||||
|
||||
$serveArguments = @(
|
||||
"serve",
|
||||
"--store", $StorePath,
|
||||
"--activate-store", $ActivateStorePath,
|
||||
"--log-root", $LogRoot,
|
||||
"--host", "raven",
|
||||
"--stream", "muninn.raven.av.rudp",
|
||||
"--target-host", $MediaTargetUri,
|
||||
"--media-transport", "rudp"
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshUri)) {
|
||||
$serveArguments += @("--odin-cultmesh-uri", $OdinCultMeshUri)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpTarget)) {
|
||||
$serveArguments += @("--hid-controller-rudp-target", $HidControllerRudpTarget)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind)) {
|
||||
$serveArguments += @("--hid-controller-rudp-bind", $HidControllerRudpBind)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
$serveArguments += @("--hid-controller-rudp-advertise", $HidControllerRudpAdvertise)
|
||||
}
|
||||
foreach ($videoSource in $VideoSources) {
|
||||
$serveArguments += @("--video-source", $videoSource)
|
||||
}
|
||||
foreach ($audioSource in $AudioSources) {
|
||||
$serveArguments += @("--audio-source", $audioSource)
|
||||
}
|
||||
foreach ($moveState in $MoveStates) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($moveState)) {
|
||||
$serveArguments += @("--move-state", $moveState)
|
||||
}
|
||||
}
|
||||
$serveArguments += @(
|
||||
"--audio-device", $AudioDevice,
|
||||
"--ffmpeg", $Ffmpeg,
|
||||
"--loopback-script", $LoopbackScript,
|
||||
"--interval-seconds", "15",
|
||||
"--idunn-rudp-health", $IdunnRudpHealth,
|
||||
"--idunn-daemon", $IdunnDaemon,
|
||||
"--idunn-health-contract", $IdunnHealthContract
|
||||
)
|
||||
$serveDynamicArgumentsScript = @'
|
||||
function Test-LikelyVirtualDisplayToken {
|
||||
param([Parameter(Mandatory = $true)] [string] $Token)
|
||||
|
||||
$trimmed = $Token.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($trimmed)) {
|
||||
return $true
|
||||
}
|
||||
return (
|
||||
$trimmed.StartsWith('MSBDD_', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('MSNIL', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('NOEDID', [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
$trimmed.StartsWith('UGD', [System.StringComparison]::OrdinalIgnoreCase)
|
||||
)
|
||||
}
|
||||
|
||||
function Get-GraphicsDriverDisplayTokens {
|
||||
$connectivityRoot = 'HKLM:\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\Connectivity'
|
||||
if (-not (Test-Path -LiteralPath $connectivityRoot)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$bestTokens = @()
|
||||
$bestScore = [int]::MinValue
|
||||
foreach ($key in Get-ChildItem -LiteralPath $connectivityRoot -ErrorAction SilentlyContinue) {
|
||||
$item = Get-ItemProperty -LiteralPath $key.PSPath -ErrorAction SilentlyContinue
|
||||
if ($null -eq $item) {
|
||||
continue
|
||||
}
|
||||
foreach ($property in $item.PSObject.Properties) {
|
||||
if ($property.Name -like 'PS*' -or $property.Value -isnot [string]) {
|
||||
continue
|
||||
}
|
||||
$tokens = @(
|
||||
$property.Value.Split('+') |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
)
|
||||
if ($tokens.Count -lt 1) {
|
||||
continue
|
||||
}
|
||||
$realCount = @($tokens | Where-Object { -not (Test-LikelyVirtualDisplayToken $_) }).Count
|
||||
$score = ($realCount * 100) + $tokens.Count
|
||||
if ($score -gt $bestScore) {
|
||||
$bestScore = $score
|
||||
$bestTokens = $tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @($bestTokens | Select-Object -Unique)
|
||||
}
|
||||
|
||||
function Get-DefaultMuninnVideoSourceLabels {
|
||||
param([Parameter(Mandatory = $true)] [string] $HostLabel)
|
||||
|
||||
$displayNames = @(
|
||||
Get-CimInstance Win32_DesktopMonitor -ErrorAction SilentlyContinue |
|
||||
Where-Object { [string]::IsNullOrWhiteSpace($_.Status) -or $_.Status -eq 'OK' } |
|
||||
ForEach-Object {
|
||||
if ([string]::IsNullOrWhiteSpace($_.Name)) {
|
||||
'Display'
|
||||
} else {
|
||||
$_.Name.Trim()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
$useRegistryFallback = (
|
||||
$displayNames.Count -lt 1 -or
|
||||
($displayNames.Count -eq 1 -and (
|
||||
$displayNames[0] -match '^\s*WinDisc\s*$' -or
|
||||
$displayNames[0] -match '^\s*Generic PnP Monitor\s*$' -or
|
||||
$displayNames[0] -match '^\s*Display\s*$'
|
||||
))
|
||||
)
|
||||
|
||||
if ($useRegistryFallback) {
|
||||
$displayTokens = @(Get-GraphicsDriverDisplayTokens)
|
||||
if ($displayTokens.Count -gt 0) {
|
||||
return @(for ($index = 0; $index -lt $displayTokens.Count; $index++) {
|
||||
'{0} display {1}' -f $HostLabel, ($index + 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if ($displayNames.Count -lt 1) {
|
||||
$displayNames = @('Display')
|
||||
}
|
||||
|
||||
return @(for ($index = 0; $index -lt $displayNames.Count; $index++) {
|
||||
$monitorName = $displayNames[$index]
|
||||
if ($monitorName -eq 'Display') {
|
||||
'{0} display {1}' -f $HostLabel, ($index + 1)
|
||||
} else {
|
||||
'{0} display {1} ({2})' -f $HostLabel, ($index + 1), $monitorName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$hostArgumentIndex = [Array]::IndexOf($arguments, '--host')
|
||||
$hostLabel = if ($hostArgumentIndex -ge 0 -and ($hostArgumentIndex + 1) -lt $arguments.Count) {
|
||||
$arguments[$hostArgumentIndex + 1]
|
||||
} else {
|
||||
'host'
|
||||
}
|
||||
$hasExplicitVideoSources = @($arguments | Where-Object { $_ -eq '--video-source' }).Count -gt 0
|
||||
$hasExplicitAudioSources = @($arguments | Where-Object { $_ -eq '--audio-source' }).Count -gt 0
|
||||
if (-not ('Muninn.AudioEndpointDiscovery' -as [type])) {
|
||||
Add-Type -TypeDefinition @"
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Muninn {
|
||||
public static class AudioEndpointDiscovery {
|
||||
const int eRender = 0;
|
||||
const int eCapture = 1;
|
||||
const uint DEVICE_STATE_ACTIVE = 0x00000001;
|
||||
static readonly PROPERTYKEY DeviceFriendlyNameKey = new PROPERTYKEY(
|
||||
new Guid("A45C254E-DF1C-4EFD-8020-67D146A850E0"),
|
||||
14);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct PROPERTYKEY {
|
||||
public Guid fmtid;
|
||||
public uint pid;
|
||||
|
||||
public PROPERTYKEY(Guid formatId, uint propertyId) {
|
||||
fmtid = formatId;
|
||||
pid = propertyId;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct PROPVARIANT {
|
||||
public ushort vt;
|
||||
public ushort wReserved1;
|
||||
public ushort wReserved2;
|
||||
public ushort wReserved3;
|
||||
public IntPtr pointerValue;
|
||||
public int intValue;
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||
class MMDeviceEnumerator { }
|
||||
|
||||
[ComImport]
|
||||
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDeviceEnumerator {
|
||||
[PreserveSig]
|
||||
int EnumAudioEndpoints(int dataFlow, uint stateMask, out IMMDeviceCollection devices);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDeviceCollection {
|
||||
[PreserveSig]
|
||||
int GetCount(out uint count);
|
||||
[PreserveSig]
|
||||
int Item(uint index, out IMMDevice device);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("D666063F-1587-4E43-81F1-B948E807363F")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IMMDevice {
|
||||
[PreserveSig]
|
||||
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, out object audioClient);
|
||||
[PreserveSig]
|
||||
int OpenPropertyStore(uint access, out IPropertyStore properties);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IPropertyStore {
|
||||
[PreserveSig]
|
||||
int GetCount(out uint propertyCount);
|
||||
[PreserveSig]
|
||||
int GetAt(uint propertyIndex, out PROPERTYKEY key);
|
||||
[PreserveSig]
|
||||
int GetValue(ref PROPERTYKEY key, out PROPVARIANT value);
|
||||
[PreserveSig]
|
||||
int SetValue(ref PROPERTYKEY key, ref PROPVARIANT value);
|
||||
[PreserveSig]
|
||||
int Commit();
|
||||
}
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
static extern int PropVariantClear(ref PROPVARIANT variant);
|
||||
|
||||
public static string[] FriendlyNames(int dataFlow) {
|
||||
var names = new List<string>();
|
||||
IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
|
||||
IMMDeviceCollection devices;
|
||||
Marshal.ThrowExceptionForHR(enumerator.EnumAudioEndpoints(dataFlow, DEVICE_STATE_ACTIVE, out devices));
|
||||
try {
|
||||
uint count;
|
||||
Marshal.ThrowExceptionForHR(devices.GetCount(out count));
|
||||
for (uint index = 0; index < count; index++) {
|
||||
IMMDevice device;
|
||||
Marshal.ThrowExceptionForHR(devices.Item(index, out device));
|
||||
try {
|
||||
var name = DeviceFriendlyName(device);
|
||||
if (!String.IsNullOrWhiteSpace(name)) {
|
||||
names.Add(name.Trim());
|
||||
}
|
||||
} finally {
|
||||
Marshal.ReleaseComObject(device);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Marshal.ReleaseComObject(devices);
|
||||
Marshal.ReleaseComObject(enumerator);
|
||||
}
|
||||
return names.ToArray();
|
||||
}
|
||||
|
||||
static string DeviceFriendlyName(IMMDevice device) {
|
||||
IPropertyStore store;
|
||||
Marshal.ThrowExceptionForHR(device.OpenPropertyStore(0, out store));
|
||||
try {
|
||||
PROPERTYKEY key = DeviceFriendlyNameKey;
|
||||
PROPVARIANT value;
|
||||
Marshal.ThrowExceptionForHR(store.GetValue(ref key, out value));
|
||||
try {
|
||||
if (value.vt == 31 && value.pointerValue != IntPtr.Zero) {
|
||||
return Marshal.PtrToStringUni(value.pointerValue) ?? "";
|
||||
}
|
||||
return "";
|
||||
} finally {
|
||||
PropVariantClear(ref value);
|
||||
}
|
||||
} finally {
|
||||
Marshal.ReleaseComObject(store);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
if (-not $hasExplicitVideoSources) {
|
||||
$videoSourceLabels = @(Get-DefaultMuninnVideoSourceLabels -HostLabel $hostLabel)
|
||||
for ($index = 0; $index -lt $videoSourceLabels.Count; $index++) {
|
||||
$arguments += @('--video-source', ('display:{0}={1}' -f $index, $videoSourceLabels[$index]))
|
||||
}
|
||||
}
|
||||
if (-not $hasExplicitAudioSources) {
|
||||
$loopbackNames = @([Muninn.AudioEndpointDiscovery]::FriendlyNames(0) | Sort-Object -Unique)
|
||||
$inputNames = @([Muninn.AudioEndpointDiscovery]::FriendlyNames(1) | Sort-Object -Unique)
|
||||
if ($loopbackNames.Count -lt 1 -and $inputNames.Count -lt 1) {
|
||||
$audioDeviceIndex = [Array]::IndexOf($arguments, '--audio-device')
|
||||
if ($audioDeviceIndex -ge 0 -and ($audioDeviceIndex + 1) -lt $arguments.Count) {
|
||||
$loopbackNames = @($arguments[$audioDeviceIndex + 1])
|
||||
}
|
||||
}
|
||||
foreach ($audioName in $loopbackNames) {
|
||||
$arguments += @('--audio-source', ('wasapi-loopback:{0}={1} loopback ({0})' -f $audioName.Trim(), $hostLabel))
|
||||
}
|
||||
foreach ($audioName in $inputNames) {
|
||||
$arguments += @('--audio-source', ('wasapi-input:{0}={1} input ({0})' -f $audioName.Trim(), $hostLabel))
|
||||
}
|
||||
}
|
||||
'@
|
||||
|
||||
$servePsContent = New-HiddenNativeLauncherPowerShellContent `
|
||||
-StorePath $StorePath `
|
||||
-LogRoot $LogRoot `
|
||||
-Arguments $serveArguments `
|
||||
-FilePath $MuninnExe `
|
||||
-StdoutPath (Join-Path $LogRoot "muninn-serve.out.log") `
|
||||
-StderrPath (Join-Path $LogRoot "muninn-serve.err.log") `
|
||||
-PidPath (Join-Path $LogRoot "muninn-serve.pid") `
|
||||
-Environment @{ CULTMESH_URI_ODIN_RUDP = $OdinCultMeshRudpEndpoint } `
|
||||
-DynamicArgumentsScript $serveDynamicArgumentsScript
|
||||
|
||||
$uploadRoot = Join-Path $env:TEMP ("odin-raven-muninn-launchers-" + [guid]::NewGuid().ToString("N"))
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $uploadRoot | Out-Null
|
||||
|
||||
$localServePs = Join-Path $uploadRoot "start-muninn-serve.ps1"
|
||||
$localServeVbs = Join-Path $uploadRoot "start-muninn-serve-hidden.vbs"
|
||||
$localServeCmd = Join-Path $uploadRoot "start-muninn-serve.cmd"
|
||||
|
||||
Set-AsciiFile -Path $localServePs -Content $servePsContent
|
||||
Set-AsciiFile -Path $localServeVbs -Content (New-HiddenPowerShellVbsLauncherContent -PsPath $servePsPath)
|
||||
Set-AsciiFile -Path $localServeCmd -Content (New-WscriptCmdLauncherContent -WorkingDirectory $muninnDir -VbsPath $serveVbsPath)
|
||||
|
||||
$remoteScript = @"
|
||||
`$ErrorActionPreference = "Stop"
|
||||
`$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
if (-not (Test-Path -LiteralPath "$MuninnExe")) {
|
||||
throw "Muninn executable not found at $MuninnExe"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath "$LoopbackScript")) {
|
||||
throw "Muninn loopback script not found at $LoopbackScript"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath "$Ffmpeg")) {
|
||||
throw "FFmpeg executable not found at $Ffmpeg"
|
||||
}
|
||||
foreach (`$path in @(
|
||||
"$servePsPath",
|
||||
"$serveVbsPath"
|
||||
)) {
|
||||
if (-not (Test-Path -LiteralPath `$path)) {
|
||||
throw "Required launcher path not found at `$path"
|
||||
}
|
||||
}
|
||||
|
||||
`$obsoleteActivateTask = Get-ScheduledTask -TaskName "GameCult-Muninn-Activate" -ErrorAction SilentlyContinue
|
||||
if (`$null -ne `$obsoleteActivateTask) {
|
||||
Unregister-ScheduledTask -TaskName "GameCult-Muninn-Activate" -Confirm:`$false | Out-Null
|
||||
}
|
||||
`$obsoleteVideoProofTask = Get-ScheduledTask -TaskName "GameCult-Muninn-VideoProof" -ErrorAction SilentlyContinue
|
||||
if (`$null -ne `$obsoleteVideoProofTask) {
|
||||
Unregister-ScheduledTask -TaskName "GameCult-Muninn-VideoProof" -Confirm:`$false | Out-Null
|
||||
}
|
||||
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object { `$_.Name -like "muninn*.exe" } |
|
||||
ForEach-Object {
|
||||
& cmd.exe /c ("taskkill /PID {0} /T /F >nul 2>&1" -f `$_.ProcessId) | Out-Null
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "$LogRoot" | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path (Split-Path -Parent "$StorePath") | Out-Null
|
||||
& netsh.exe advfirewall firewall delete rule name="GameCult Muninn Capture Command RUDP" | Out-Null 2>`$null
|
||||
if (-not [string]::IsNullOrWhiteSpace("$HidControllerRudpBind")) {
|
||||
`$hidControllerUdpPort = ([string] "$HidControllerRudpBind").Split(':')[-1]
|
||||
& netsh.exe advfirewall firewall delete rule name="GameCult Muninn HID Controller RUDP" | Out-Null 2>`$null
|
||||
& netsh.exe advfirewall firewall add rule name="GameCult Muninn HID Controller RUDP" dir=in action=allow protocol=UDP localport=`$hidControllerUdpPort | Out-Null
|
||||
}
|
||||
|
||||
function Register-HiddenVbsTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath
|
||||
)
|
||||
|
||||
`$taskAction = New-ScheduledTaskAction -Execute "$env:SystemRoot\System32\wscript.exe" -Argument "//B //Nologo ""`$VbsPath""" -WorkingDirectory (Split-Path -Parent `$VbsPath)
|
||||
`$taskTrigger = New-ScheduledTaskTrigger -Once -At ([DateTime]::Today.AddHours(23).AddMinutes(59))
|
||||
`$taskPrincipal = New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Highest
|
||||
`$taskSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew
|
||||
Register-ScheduledTask -TaskName `$TaskName -Action `$taskAction -Trigger `$taskTrigger -Principal `$taskPrincipal -Settings `$taskSettings -Force | Out-Null
|
||||
}
|
||||
|
||||
function Register-HiddenPowerShellTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$PsPath
|
||||
)
|
||||
|
||||
`$taskArguments = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + `$PsPath + '"'
|
||||
`$taskAction = New-ScheduledTaskAction -Execute "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument `$taskArguments -WorkingDirectory (Split-Path -Parent `$PsPath)
|
||||
`$taskTrigger = New-ScheduledTaskTrigger -Once -At ([DateTime]::Today.AddHours(23).AddMinutes(59))
|
||||
`$taskPrincipal = New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Highest
|
||||
`$taskSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew
|
||||
Register-ScheduledTask -TaskName `$TaskName -Action `$taskAction -Trigger `$taskTrigger -Principal `$taskPrincipal -Settings `$taskSettings -Force | Out-Null
|
||||
}
|
||||
|
||||
function Assert-HiddenVbsTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$VbsPath,
|
||||
[Parameter(Mandatory = `$true)] [string] `$PsPath
|
||||
)
|
||||
|
||||
`$task = Get-ScheduledTask -TaskName `$TaskName -ErrorAction Stop
|
||||
`$action = @(`$task.Actions)[0]
|
||||
if (`$action.Execute -notmatch '(^|\\)wscript\.exe$') {
|
||||
throw "`$TaskName action executes `$(`$action.Execute), expected wscript.exe"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*`$VbsPath*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not reference `$VbsPath"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*//B*" -or `$action.Arguments -notlike "*//Nologo*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not force background WScript execution"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath `$PsPath)) {
|
||||
throw "`$TaskName PowerShell launcher not found at `$PsPath"
|
||||
}
|
||||
`$vbs = Get-Content -LiteralPath `$VbsPath -Raw
|
||||
if (`$vbs -match 'cmdPath\s*=') {
|
||||
throw "`$TaskName hidden launcher at `$VbsPath still routes through a cmdPath trampoline"
|
||||
}
|
||||
if (`$vbs -notmatch '\.ps1') {
|
||||
throw "`$TaskName hidden launcher at `$VbsPath does not reference a PowerShell launcher"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-HiddenPowerShellTask {
|
||||
param(
|
||||
[Parameter(Mandatory = `$true)] [string] `$TaskName,
|
||||
[Parameter(Mandatory = `$true)] [string] `$PsPath
|
||||
)
|
||||
|
||||
`$task = Get-ScheduledTask -TaskName `$TaskName -ErrorAction Stop
|
||||
`$action = @(`$task.Actions)[0]
|
||||
if (`$action.Execute -notmatch 'powershell\.exe$') {
|
||||
throw "`$TaskName action executes `$(`$action.Execute), expected powershell.exe"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*`$PsPath*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not reference `$PsPath"
|
||||
}
|
||||
if (`$action.Arguments -notlike "*-File*") {
|
||||
throw "`$TaskName action arguments `$(`$action.Arguments) do not execute a PowerShell launcher"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath `$PsPath)) {
|
||||
throw "`$TaskName PowerShell launcher not found at `$PsPath"
|
||||
}
|
||||
}
|
||||
|
||||
Register-HiddenPowerShellTask -TaskName "GameCult-Muninn" -PsPath "$servePsPath"
|
||||
|
||||
Assert-HiddenPowerShellTask -TaskName "GameCult-Muninn" -PsPath "$servePsPath"
|
||||
|
||||
Start-ScheduledTask -TaskName "GameCult-Muninn"
|
||||
|
||||
`$deadline = [DateTime]::UtcNow.AddSeconds($ServeStartTimeoutSeconds)
|
||||
`$process = `$null
|
||||
do {
|
||||
Start-Sleep -Milliseconds 500
|
||||
`$process = Get-CimInstance Win32_Process |
|
||||
Where-Object { `$_.Name -ieq "muninn.exe" -and `$_.CommandLine -like "*serve*" -and `$_.CommandLine -like "*--host*raven*" } |
|
||||
Select-Object -First 1
|
||||
} while (`$null -eq `$process -and [DateTime]::UtcNow -lt `$deadline)
|
||||
if (`$null -eq `$process) {
|
||||
throw "Muninn serve process did not start on Raven within $ServeStartTimeoutSeconds seconds"
|
||||
}
|
||||
foreach (`$pattern in @(
|
||||
"--idunn-rudp-health",
|
||||
"$IdunnRudpHealth",
|
||||
"--idunn-daemon",
|
||||
"$IdunnDaemon",
|
||||
"--idunn-health-contract",
|
||||
"$IdunnHealthContract"
|
||||
)) {
|
||||
if (`$process.CommandLine -notlike "*`$pattern*") {
|
||||
throw "Muninn Raven serve command line is missing `${pattern}: `$(`$process.CommandLine)"
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
$uploadSpecs = @(
|
||||
@{ LocalPath = $LocalLoopbackScript; RemotePath = ($LoopbackScript -replace "\\", "/") },
|
||||
@{ LocalPath = $localServePs; RemotePath = ($servePsPath -replace "\\", "/") },
|
||||
@{ LocalPath = $localServeVbs; RemotePath = ($serveVbsPath -replace "\\", "/") },
|
||||
@{ LocalPath = $localServeCmd; RemotePath = ($serveCmdPath -replace "\\", "/") }
|
||||
)
|
||||
|
||||
Invoke-RavenUploadedPowerShell -RavenHost $RavenHost -RemoteScriptContent $remoteScript -UploadSpecs $uploadSpecs -TempPrefix "odin-raven-muninn-restart"
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $uploadRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
@echo off
|
||||
if not "%IDUNN_ACTUATOR%"=="1" (
|
||||
echo restart-nightwing-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
if /I not "%IDUNN_COMMAND_AUTHORITY%"=="idunn-daemon" (
|
||||
echo restart-nightwing-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %~dp0restart-nightwing-muninn.ps1 -DiscoverMoveState -ClaimUsbMoves
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
param(
|
||||
[string] $SshTarget = "nightwing",
|
||||
[string] $MuninnExe = "/home/metacrat/.local/bin/muninn",
|
||||
[string] $StorePath = "/home/metacrat/.local/state/gamecult/muninn/muninn.telemetry.cc",
|
||||
[string] $LogRoot = "/home/metacrat/.local/state/gamecult/muninn",
|
||||
[string[]] $MoveState = @(),
|
||||
[switch] $DiscoverMoveState = $true,
|
||||
[switch] $ClaimUsbMoves = $true,
|
||||
[string] $MoveEvidenceStream = "muninn:nightwing:move-evidence",
|
||||
[string[]] $MoveMarkerCamera = @(
|
||||
"nightwing-eye-0=/dev/video2",
|
||||
"nightwing-eye-1=/dev/video3"
|
||||
),
|
||||
[int] $MoveTrackerExposureMilli = 100,
|
||||
[hashtable] $MoveTrackerCameraExposure = @{
|
||||
"nightwing-eye-0" = 300
|
||||
"nightwing-eye-1" = 100
|
||||
},
|
||||
[int] $IntervalSeconds = 15,
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
|
||||
[string] $IdunnDaemon = "nightwing-muninn",
|
||||
[string] $IdunnHealthContract = "muninn.cultnet-rudp-remote-telemetry-and-move-hid",
|
||||
[string] $OdinCultMeshUri = $(if ($env:ODIN_CULTMESH_URI) { $env:ODIN_CULTMESH_URI } else { "cultmesh://odin/rendezvous/provider-catalog" }),
|
||||
[string] $OdinCultMeshRudpEndpoint = $(if ($env:CULTMESH_URI_ODIN_RUDP) { $env:CULTMESH_URI_ODIN_RUDP } else { "10.77.0.2:17871" }),
|
||||
[string] $HidControllerRudpBind = "0.0.0.0:17888",
|
||||
[string] $HidControllerRudpAdvertise = $(if ($env:MUNINN_HID_CONTROLLER_RUDP_ADVERTISE) { $env:MUNINN_HID_CONTROLLER_RUDP_ADVERTISE } else { "10.77.0.3:17888" }),
|
||||
[string] $CommandRudpBind = "0.0.0.0:17889",
|
||||
[string] $CommandRudpAdvertise = "10.77.0.3:17889"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ($env:IDUNN_ACTUATOR -ne "1" -or $env:IDUNN_COMMAND_AUTHORITY -ne "idunn-daemon") {
|
||||
throw "restart-nightwing-muninn.ps1 is an Idunn actuator body. Redeploy by poking Idunn; direct service restart is not an owned path."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
||||
}
|
||||
if ($IdunnRudpHealth -match '^(127\.0\.0\.1|localhost):(\d+)$') {
|
||||
$IdunnRudpHealth = "10.77.0.2:$($Matches[2])"
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind) -and [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
throw "HID controller RUDP advertise endpoint must be supplied by -HidControllerRudpAdvertise or MUNINN_HID_CONTROLLER_RUDP_ADVERTISE when HID RUDP bind is enabled; no Nightwing LAN default is allowed."
|
||||
}
|
||||
|
||||
function Quote-ShSingle([string] $Value) {
|
||||
return "'" + ($Value -replace "'", "'\''") + "'"
|
||||
}
|
||||
|
||||
function Set-AsciiFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $Path,
|
||||
[Parameter(Mandatory = $true)] [string] $Content
|
||||
)
|
||||
|
||||
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`n"), [System.Text.Encoding]::ASCII)
|
||||
}
|
||||
|
||||
function Invoke-UploadedShell {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)] [string] $SshTarget,
|
||||
[Parameter(Mandatory = $true)] [string] $RemoteScriptContent
|
||||
)
|
||||
|
||||
$uploadId = [guid]::NewGuid().ToString("N")
|
||||
$localTempRoot = Join-Path $env:TEMP "odin-nightwing-muninn-restart-$uploadId"
|
||||
$localRemoteScript = Join-Path $localTempRoot "restart-nightwing-muninn-$uploadId.sh"
|
||||
$localSftpBatch = Join-Path $localTempRoot "restart-nightwing-muninn-$uploadId.sftp"
|
||||
$remoteScriptPath = "/tmp/restart-nightwing-muninn-$uploadId.sh"
|
||||
|
||||
try {
|
||||
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
||||
Set-AsciiFile -Path $localRemoteScript -Content $RemoteScriptContent
|
||||
Set-AsciiFile -Path $localSftpBatch -Content ('put "{0}" "{1}"' -f $localRemoteScript, $remoteScriptPath)
|
||||
|
||||
& sftp.exe -b $localSftpBatch $SshTarget
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
& ssh.exe -o BatchMode=yes -o ConnectTimeout=10 $SshTarget "chmod +x '$remoteScriptPath' && bash '$remoteScriptPath'"
|
||||
$remoteExit = $LASTEXITCODE
|
||||
& ssh.exe -o BatchMode=yes -o ConnectTimeout=10 $SshTarget "rm -f '$remoteScriptPath'"
|
||||
exit $remoteExit
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$moveStateSpecs = @($MoveState | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
||||
if ($ClaimUsbMoves.IsPresent -and -not $DiscoverMoveState.IsPresent -and $moveStateSpecs.Count -eq 0) {
|
||||
throw "-ClaimUsbMoves requires -DiscoverMoveState or at least one explicit -MoveState value."
|
||||
}
|
||||
if ($DiscoverMoveState.IsPresent) {
|
||||
$claimScript = Join-Path $PSScriptRoot "claim-nightwing-usb-moves.ps1"
|
||||
if ($ClaimUsbMoves.IsPresent) {
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $claimScript -SshTarget $SshTarget
|
||||
}
|
||||
$sourceScript = Join-Path $PSScriptRoot "get-nightwing-move-state-sources.ps1"
|
||||
$moveStateSpecs = @(& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $sourceScript -SshTarget $SshTarget)
|
||||
if ($moveStateSpecs.Count -eq 0) {
|
||||
throw "Nightwing Move runtime was explicitly requested, but no live Move state sources were discovered."
|
||||
}
|
||||
}
|
||||
|
||||
$moveStateSetLines = ($moveStateSpecs | ForEach-Object {
|
||||
"set -- ""`$@"" --move-state $(Quote-ShSingle $_)"
|
||||
}) -join "`n"
|
||||
$moveRuntimeSetLines = @()
|
||||
if ($moveStateSpecs.Count -gt 0) {
|
||||
$moveRuntimeSetLines += $moveStateSetLines
|
||||
$moveRuntimeSetLines += "set -- ""`$@"" --move-evidence-stream $(Quote-ShSingle $MoveEvidenceStream)"
|
||||
if ($MoveMarkerCamera.Count -gt 0) {
|
||||
foreach ($camera in $MoveMarkerCamera) {
|
||||
$moveRuntimeSetLines += "set -- ""`$@"" --move-marker-camera $(Quote-ShSingle $camera)"
|
||||
}
|
||||
$moveRuntimeSetLines += "set -- ""`$@"" --move-psmoveapi-tracker"
|
||||
$moveRuntimeSetLines += "set -- ""`$@"" --move-tracker-exposure-milli $(Quote-ShSingle ([string]$MoveTrackerExposureMilli))"
|
||||
foreach ($cameraId in @($MoveTrackerCameraExposure.Keys | Sort-Object)) {
|
||||
$moveRuntimeSetLines += "set -- ""`$@"" --move-tracker-camera-exposure $(Quote-ShSingle ("{0}={1}" -f $cameraId, $MoveTrackerCameraExposure[$cameraId]))"
|
||||
}
|
||||
}
|
||||
}
|
||||
$moveRuntimeSetBlock = ($moveRuntimeSetLines -join "`n")
|
||||
$odinCultMeshUriSetBlock = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshUri)) {
|
||||
$odinCultMeshUriSetBlock = "set -- ""`$@"" --odin-cultmesh-uri $(Quote-ShSingle $OdinCultMeshUri)"
|
||||
}
|
||||
$hidControllerRudpSetLines = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind)) {
|
||||
$hidControllerRudpSetLines += "set -- ""`$@"" --hid-controller-rudp-bind $(Quote-ShSingle $HidControllerRudpBind)"
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
$hidControllerRudpSetLines += "set -- ""`$@"" --hid-controller-rudp-advertise $(Quote-ShSingle $HidControllerRudpAdvertise)"
|
||||
}
|
||||
$hidControllerRudpSetBlock = ($hidControllerRudpSetLines -join "`n")
|
||||
$commandRudpSetBlock = "set -- ""`$@"" --command-rudp-bind $(Quote-ShSingle $CommandRudpBind) --command-rudp-advertise $(Quote-ShSingle $CommandRudpAdvertise)"
|
||||
$remoteScript = @"
|
||||
set -eu
|
||||
mkdir -p '$LogRoot'
|
||||
if [ ! -x '$MuninnExe' ]; then
|
||||
echo 'Muninn executable not found at $MuninnExe' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -f '$LogRoot/muninn.pid' ] && kill -0 "`$(cat '$LogRoot/muninn.pid')" 2>/dev/null; then
|
||||
kill "`$(cat '$LogRoot/muninn.pid')" 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
for pid in `$(pgrep -f '[m]uninn move-tracker-worker .*--host nightwing' 2>/dev/null || true); do
|
||||
kill "`$pid" 2>/dev/null || true
|
||||
done
|
||||
for pid in `$(pgrep -f '[m]uninn serve .*--host nightwing' 2>/dev/null || true); do
|
||||
kill "`$pid" 2>/dev/null || true
|
||||
done
|
||||
for pid in `$(pgrep -f '[m]uninn serve .*--host nightwing-eye0' 2>/dev/null || true); do
|
||||
kill "`$pid" 2>/dev/null || true
|
||||
done
|
||||
set -- serve \
|
||||
--store '$StorePath' \
|
||||
--log-root '$LogRoot' \
|
||||
--host nightwing
|
||||
$moveRuntimeSetBlock
|
||||
set -- "`$@" \
|
||||
--interval-seconds '$IntervalSeconds' \
|
||||
--idunn-rudp-health '$IdunnRudpHealth' \
|
||||
--idunn-daemon '$IdunnDaemon' \
|
||||
--idunn-health-contract '$IdunnHealthContract'
|
||||
$odinCultMeshUriSetBlock
|
||||
$hidControllerRudpSetBlock
|
||||
$commandRudpSetBlock
|
||||
CULTNET_RUDP_TRACE=1 CULTMESH_URI_ODIN_RUDP='$OdinCultMeshRudpEndpoint' nohup '$MuninnExe' "`$@" \
|
||||
> '$LogRoot/muninn-serve.out.log' \
|
||||
2> '$LogRoot/muninn-serve.err.log' \
|
||||
< /dev/null &
|
||||
echo `$! > '$LogRoot/muninn.pid'
|
||||
sleep 1
|
||||
muninn_pid="`$(cat '$LogRoot/muninn.pid')"
|
||||
kill -0 "`$muninn_pid" 2>/dev/null
|
||||
muninn_cmdline="`$(tr '\0' ' ' < "/proc/`$muninn_pid/cmdline")"
|
||||
if ! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--host nightwing' >/dev/null 2>&1 ||
|
||||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--idunn-rudp-health $IdunnRudpHealth' >/dev/null 2>&1 ||
|
||||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--move-marker-camera nightwing-eye-0=/dev/video2' >/dev/null 2>&1 ||
|
||||
! printf '%s\n' "`$muninn_cmdline" | grep -F -- '--move-marker-camera nightwing-eye-1=/dev/video3' >/dev/null 2>&1; then
|
||||
echo 'Nightwing Muninn serve command line is missing Idunn health or dual-camera arguments' >&2
|
||||
exit 1
|
||||
fi
|
||||
"@
|
||||
|
||||
Invoke-UploadedShell -SshTarget $SshTarget -RemoteScriptContent $remoteScript
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %~dp0restart-muninn.ps1
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
param(
|
||||
[string] $RavenHost = "raven"
|
||||
)
|
||||
|
||||
$script = Join-Path $PSScriptRoot "restart-muninn.ps1"
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script -RavenHost $RavenHost
|
||||
exit $LASTEXITCODE
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
@echo off
|
||||
if not "%IDUNN_ACTUATOR%"=="1" (
|
||||
echo restart-starfire-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
if /I not "%IDUNN_COMMAND_AUTHORITY%"=="idunn-daemon" (
|
||||
echo restart-starfire-muninn.cmd is an Idunn actuator wrapper. Redeploy by poking Idunn; direct service restart is not an owned path. 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %~dp0restart-starfire-muninn.ps1
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
param(
|
||||
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn-d4d0616.exe",
|
||||
[string] $StorePath = "C:\Meta\Odin\state\starfire.muninn.telemetry.cc",
|
||||
[string] $LogRoot = "C:\Meta\Odin\logs\starfire-muninn",
|
||||
[string] $QuestSerial = "1WMHHB68PG1515",
|
||||
[string] $MoveBluetoothHost = "",
|
||||
[string[]] $MoveState = @(),
|
||||
[switch] $EnableUsbMoveState,
|
||||
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
|
||||
[string] $IdunnDaemon = "starfire-muninn",
|
||||
[string] $IdunnHealthContract = "muninn.cultnet-rudp-local-telemetry-and-quest-access",
|
||||
[string] $OdinCultMeshUri = $(if ($env:ODIN_CULTMESH_URI) { $env:ODIN_CULTMESH_URI } else { "cultmesh://odin/rendezvous/provider-catalog" }),
|
||||
[string] $OdinCultMeshRudpEndpoint = $(if ($env:CULTMESH_URI_ODIN_RUDP) { $env:CULTMESH_URI_ODIN_RUDP } else { "127.0.0.1:17871" }),
|
||||
[string] $HidControllerRudpBind = "",
|
||||
[string] $HidControllerRudpAdvertise = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$restartMutex = [System.Threading.Mutex]::new($false, "Global\GameCult-Starfire-Muninn-Restart")
|
||||
if (-not $restartMutex.WaitOne([TimeSpan]::FromMinutes(2))) {
|
||||
throw "Timed out waiting for the Starfire Muninn restart actuator mutex."
|
||||
}
|
||||
|
||||
if ($env:IDUNN_ACTUATOR -ne "1" -or $env:IDUNN_COMMAND_AUTHORITY -ne "idunn-daemon") {
|
||||
throw "restart-starfire-muninn.ps1 is an Idunn actuator body. Redeploy by poking Idunn; direct service restart is not an owned path."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $MuninnExe)) {
|
||||
throw "Muninn executable not found at $MuninnExe"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
||||
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind) -and [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
throw "HID controller RUDP advertise endpoint must be supplied by -HidControllerRudpAdvertise or MUNINN_HID_CONTROLLER_RUDP_ADVERTISE when HID RUDP bind is enabled; no Starfire LAN default is allowed."
|
||||
}
|
||||
|
||||
Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
$_.Name -like "muninn*.exe" -and
|
||||
$_.CommandLine -like "*serve*" -and
|
||||
$_.CommandLine -like "*--host*starfire*"
|
||||
} |
|
||||
ForEach-Object {
|
||||
& taskkill.exe /PID $_.ProcessId /T /F | Out-Null
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $LogRoot | Out-Null
|
||||
$storeParent = Split-Path -Parent $StorePath
|
||||
if (-not [string]::IsNullOrWhiteSpace($storeParent)) {
|
||||
New-Item -ItemType Directory -Force -Path $storeParent | Out-Null
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($MoveBluetoothHost)) {
|
||||
& $MuninnExe claim-move-host --move-host $MoveBluetoothHost
|
||||
}
|
||||
|
||||
$arguments = @(
|
||||
"serve",
|
||||
"--store", $StorePath,
|
||||
"--log-root", $LogRoot,
|
||||
"--host", "starfire",
|
||||
"--interval-seconds", "15",
|
||||
"--quest-adb",
|
||||
"--quest-serial", $QuestSerial,
|
||||
"--idunn-rudp-health", $IdunnRudpHealth,
|
||||
"--idunn-daemon", $IdunnDaemon,
|
||||
"--idunn-health-contract", $IdunnHealthContract
|
||||
)
|
||||
if (-not [string]::IsNullOrWhiteSpace($OdinCultMeshUri)) {
|
||||
$arguments += @("--odin-cultmesh-uri", $OdinCultMeshUri)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpBind)) {
|
||||
$arguments += @("--hid-controller-rudp-bind", $HidControllerRudpBind)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($HidControllerRudpAdvertise)) {
|
||||
$arguments += @("--hid-controller-rudp-advertise", $HidControllerRudpAdvertise)
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($MoveBluetoothHost)) {
|
||||
$arguments += @("--move-host", $MoveBluetoothHost)
|
||||
}
|
||||
foreach ($source in $MoveState) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($source)) {
|
||||
$arguments += @("--move-state", $source)
|
||||
}
|
||||
}
|
||||
if ($EnableUsbMoveState) {
|
||||
$arguments += @("--move-state", "move-starfire-usb=windows-psmove")
|
||||
}
|
||||
|
||||
$serveOutLog = Join-Path $LogRoot "muninn-serve.out.log"
|
||||
$serveErrLog = Join-Path $LogRoot "muninn-serve.err.log"
|
||||
$pidPath = Join-Path $LogRoot "muninn-serve.pid"
|
||||
$lockPath = "$StorePath.lock"
|
||||
|
||||
function Start-MuninnServeProcess {
|
||||
param([string[]] $ArgumentList)
|
||||
|
||||
$previousOdinEndpoint = $env:CULTMESH_URI_ODIN_RUDP
|
||||
try {
|
||||
$env:CULTMESH_URI_ODIN_RUDP = $OdinCultMeshRudpEndpoint
|
||||
$process = Start-Process `
|
||||
-FilePath $MuninnExe `
|
||||
-ArgumentList $ArgumentList `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru `
|
||||
-RedirectStandardOutput $serveOutLog `
|
||||
-RedirectStandardError $serveErrLog
|
||||
} finally {
|
||||
$env:CULTMESH_URI_ODIN_RUDP = $previousOdinEndpoint
|
||||
}
|
||||
$process.Id | Set-Content -Encoding ASCII -LiteralPath $pidPath
|
||||
return $process
|
||||
}
|
||||
|
||||
function Get-StarfireMuninnServeProcess {
|
||||
return Get-CimInstance Win32_Process |
|
||||
Where-Object {
|
||||
$_.Name -like "muninn*.exe" -and
|
||||
$_.CommandLine -like "*serve*" -and
|
||||
$_.CommandLine -like "*--host*starfire*"
|
||||
} |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
function Reset-CorruptMuninnStore {
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
if (Test-Path -LiteralPath $StorePath) {
|
||||
Move-Item -LiteralPath $StorePath -Destination "$StorePath.corrupt-$timestamp" -Force
|
||||
}
|
||||
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
|
||||
$storeLeaf = Split-Path -Leaf $StorePath
|
||||
Get-ChildItem -LiteralPath $storeParent -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Name -like "$storeLeaf.*.tmp"
|
||||
} | ForEach-Object {
|
||||
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Remove-Item -LiteralPath $lockPath -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$null = Start-MuninnServeProcess -ArgumentList $arguments
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
$processCheck = Get-StarfireMuninnServeProcess
|
||||
if ($null -eq $processCheck) {
|
||||
$serveError = if (Test-Path -LiteralPath $serveErrLog) {
|
||||
Get-Content -LiteralPath $serveErrLog -Raw
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if ($serveError -like "*failed to decode MessagePack*") {
|
||||
Reset-CorruptMuninnStore
|
||||
$null = Start-MuninnServeProcess -ArgumentList $arguments
|
||||
Start-Sleep -Seconds 2
|
||||
$processCheck = Get-StarfireMuninnServeProcess
|
||||
}
|
||||
}
|
||||
if ($null -eq $processCheck) {
|
||||
throw "Starfire Muninn serve process is not running after restart"
|
||||
}
|
||||
foreach ($pattern in @(
|
||||
"--idunn-rudp-health",
|
||||
$IdunnRudpHealth,
|
||||
"--idunn-daemon",
|
||||
$IdunnDaemon,
|
||||
"--idunn-health-contract",
|
||||
$IdunnHealthContract
|
||||
)) {
|
||||
if ($processCheck.CommandLine -notlike "*$pattern*") {
|
||||
throw "Starfire Muninn serve command line is missing ${pattern}: $($processCheck.CommandLine)"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
param(
|
||||
[string] $RestartScript = (Join-Path $PSScriptRoot "restart-muninn.ps1")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Fail-MuninnMediaProfile {
|
||||
param([string] $Message)
|
||||
throw "Muninn media profile verification failed: $Message"
|
||||
}
|
||||
|
||||
function Get-AssignmentAst {
|
||||
param(
|
||||
[System.Management.Automation.Language.Ast] $Ast,
|
||||
[string] $VariableName
|
||||
)
|
||||
|
||||
$matches = @($Ast.FindAll({
|
||||
param($Node)
|
||||
if ($Node -isnot [System.Management.Automation.Language.AssignmentStatementAst]) {
|
||||
return $false
|
||||
}
|
||||
$left = $Node.Left
|
||||
return $left -is [System.Management.Automation.Language.VariableExpressionAst] -and
|
||||
$left.VariablePath.UserPath -eq $VariableName
|
||||
}, $true))
|
||||
|
||||
if ($matches.Count -ne 1) {
|
||||
Fail-MuninnMediaProfile "expected exactly one assignment to `$$VariableName, found $($matches.Count)"
|
||||
}
|
||||
|
||||
return $matches[0]
|
||||
}
|
||||
|
||||
function Get-ConstantAssignmentValue {
|
||||
param(
|
||||
[System.Management.Automation.Language.Ast] $Ast,
|
||||
[string] $VariableName
|
||||
)
|
||||
|
||||
$assignment = Get-AssignmentAst -Ast $Ast -VariableName $VariableName
|
||||
$right = $assignment.Right
|
||||
if ($right -is [System.Management.Automation.Language.CommandExpressionAst]) {
|
||||
$right = $right.Expression
|
||||
}
|
||||
if ($right -isnot [System.Management.Automation.Language.ConstantExpressionAst]) {
|
||||
Fail-MuninnMediaProfile "`$$VariableName is not a constant assignment"
|
||||
}
|
||||
|
||||
return $right.Value
|
||||
}
|
||||
|
||||
function Assert-TokenPair {
|
||||
param(
|
||||
[string[]] $Tokens,
|
||||
[string] $Name,
|
||||
[string] $Value
|
||||
)
|
||||
|
||||
for ($index = 0; $index -lt ($Tokens.Count - 1); $index++) {
|
||||
if ($Tokens[$index] -eq $Name -and $Tokens[$index + 1] -eq $Value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Fail-MuninnMediaProfile "video proof arguments are missing '$Name $Value'"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $RestartScript)) {
|
||||
Fail-MuninnMediaProfile "restart script not found at $RestartScript"
|
||||
}
|
||||
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
||||
(Resolve-Path -LiteralPath $RestartScript),
|
||||
[ref] $tokens,
|
||||
[ref] $errors
|
||||
)
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
Fail-MuninnMediaProfile (($errors | ForEach-Object { $_.ToString() }) -join "; ")
|
||||
}
|
||||
|
||||
$framerate = Get-ConstantAssignmentValue -Ast $ast -VariableName "videoProofFramerate"
|
||||
$bitrateKbps = Get-ConstantAssignmentValue -Ast $ast -VariableName "videoProofBitrateKbps"
|
||||
if ($framerate -ne 30) {
|
||||
Fail-MuninnMediaProfile "video proof framerate is $framerate, expected 30"
|
||||
}
|
||||
if ($bitrateKbps -ne 12000) {
|
||||
Fail-MuninnMediaProfile "video proof bitrate is $bitrateKbps, expected 12000"
|
||||
}
|
||||
|
||||
$videoProofAssignment = Get-AssignmentAst -Ast $ast -VariableName "videoProofArguments"
|
||||
$stringConstants = @($videoProofAssignment.Right.FindAll({
|
||||
param($Node)
|
||||
$Node -is [System.Management.Automation.Language.StringConstantExpressionAst]
|
||||
}, $true))
|
||||
$literalTokens = @($stringConstants | ForEach-Object { $_.Value })
|
||||
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-fflags" -Value "nobuffer"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-flags" -Value "low_delay"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-c:v" -Value "h264_nvenc"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-preset" -Value "p1"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-tune" -Value "ull"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-zerolatency" -Value "1"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-bf" -Value "0"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-delay" -Value "0"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-rc" -Value "cbr"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-rc-lookahead" -Value "0"
|
||||
Assert-TokenPair -Tokens $literalTokens -Name "-forced-idr" -Value "1"
|
||||
|
||||
$videoProofText = $videoProofAssignment.Extent.Text
|
||||
if ($videoProofText -notmatch '\$videoProofVbvKbits') {
|
||||
Fail-MuninnMediaProfile "video proof bufsize is not derived from the one-frame VBV budget"
|
||||
}
|
||||
if ($videoProofText -match '"p4"|''p4''|24000k') {
|
||||
Fail-MuninnMediaProfile "video proof still contains legacy p4 or 24000k latency settings"
|
||||
}
|
||||
|
||||
Write-Host "Muninn media profile verification passed for $RestartScript"
|
||||
Loading…
Reference in a new issue