2026-06-12 16:09:03 +01:00
param (
2026-07-12 21:53:52 +01:00
[ string ] $MuninnExe = " C:\Meta\Odin\Muninn\muninn-d4d0616.exe " ,
2026-06-12 16:09:03 +01:00
[ string ] $StorePath = " C:\Meta\Odin\state\starfire.muninn.telemetry.cc " ,
[ string ] $LogRoot = " C:\Meta\Odin\logs\starfire-muninn " ,
2026-06-14 20:14:55 +01:00
[ string ] $QuestSerial = " 1WMHHB68PG1515 " ,
2026-07-12 21:45:00 +01:00
[ string ] $MoveBluetoothHost = " " ,
2026-06-14 20:14:55 +01:00
[ string[] ] $MoveState = @ ( ) ,
2026-06-16 17:02:50 +01:00
[ switch ] $EnableUsbMoveState ,
2026-07-04 22:38:43 +01:00
[ string ] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH ,
2026-06-16 17:02:50 +01:00
[ string ] $IdunnDaemon = " starfire-muninn " ,
2026-06-23 22:45:39 +01:00
[ string ] $IdunnHealthContract = " muninn.cultnet-rudp-local-telemetry-and-quest-access " ,
2026-07-04 22:38:43 +01:00
[ string ] $OdinCultMeshUri = $ ( if ( $env:ODIN_CULTMESH_URI ) { $env:ODIN_CULTMESH_URI } else { " cultmesh://odin/rendezvous/provider-catalog " } ) ,
2026-07-12 19:49:36 +01:00
[ string ] $OdinCultMeshRudpEndpoint = $ ( if ( $env:CULTMESH_URI_ODIN_RUDP ) { $env:CULTMESH_URI_ODIN_RUDP } else { " 127.0.0.1:17871 " } ) ,
2026-07-12 21:45:00 +01:00
[ string ] $HidControllerRudpBind = " " ,
[ string ] $HidControllerRudpAdvertise = " "
2026-06-12 16:09:03 +01:00
)
$ErrorActionPreference = " Stop "
2026-07-12 21:46:19 +01:00
$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. "
}
2026-07-04 22:38:43 +01:00
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. "
}
2026-06-12 16:09:03 +01:00
if ( -not ( Test-Path -LiteralPath $MuninnExe ) ) {
throw " Muninn executable not found at $MuninnExe "
}
2026-07-04 22:38:43 +01:00
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. "
}
2026-06-12 16:09:03 +01:00
Get-CimInstance Win32_Process |
Where-Object {
2026-07-12 21:45:35 +01:00
$_ . Name -like " muninn*.exe " -and
2026-06-12 16:09:03 +01:00
$_ . 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
}
2026-06-14 21:14:40 +01:00
if ( -not [ string ] :: IsNullOrWhiteSpace ( $MoveBluetoothHost ) ) {
& $MuninnExe claim-move -host - -move -host $MoveBluetoothHost
}
2026-06-12 16:09:03 +01:00
$arguments = @ (
" serve " ,
" --store " , $StorePath ,
" --log-root " , $LogRoot ,
" --host " , " starfire " ,
" --interval-seconds " , " 15 " ,
" --quest-adb " ,
2026-06-16 17:02:50 +01:00
" --quest-serial " , $QuestSerial ,
" --idunn-rudp-health " , $IdunnRudpHealth ,
" --idunn-daemon " , $IdunnDaemon ,
" --idunn-health-contract " , $IdunnHealthContract
2026-06-12 16:09:03 +01:00
)
2026-07-04 22:38:43 +01:00
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 )
2026-06-23 22:45:39 +01:00
}
2026-06-17 03:22:40 +01:00
if ( -not [ string ] :: IsNullOrWhiteSpace ( $MoveBluetoothHost ) ) {
$arguments + = @ ( " --move-host " , $MoveBluetoothHost )
}
2026-06-14 20:14:55 +01:00
foreach ( $source in $MoveState ) {
if ( -not [ string ] :: IsNullOrWhiteSpace ( $source ) ) {
$arguments + = @ ( " --move-state " , $source )
}
}
if ( $EnableUsbMoveState ) {
$arguments + = @ ( " --move-state " , " move-starfire-usb=windows-psmove " )
}
2026-06-12 16:09:03 +01:00
2026-06-16 17:02:50 +01:00
$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 "
2026-06-12 16:09:03 +01:00
2026-06-16 17:02:50 +01:00
function Start-MuninnServeProcess {
param ( [ string[] ] $ArgumentList )
2026-07-12 19:49:36 +01:00
$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
}
2026-06-16 17:02:50 +01:00
$process . Id | Set-Content -Encoding ASCII -LiteralPath $pidPath
return $process
}
function Get-StarfireMuninnServeProcess {
return Get-CimInstance Win32_Process |
Where-Object {
2026-07-12 21:45:35 +01:00
$_ . Name -like " muninn*.exe " -and
2026-06-16 17:02:50 +01:00
$_ . 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
2026-06-12 16:09:03 +01:00
Start-Sleep -Seconds 2
2026-06-16 17:02:50 +01:00
$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 ) "
}
}