feat: add verified development live handoff
This commit is contained in:
238
scripts/Initialize-DevelopmentLiveConfig.ps1
Normal file
238
scripts/Initialize-DevelopmentLiveConfig.ps1
Normal file
@@ -0,0 +1,238 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet('127.0.0.1', '::1')]
|
||||
[string] $PlayoutHost,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateRange(1, 65535)]
|
||||
[int] $PlayoutPort,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^[0-9A-Fa-f]{64}$')]
|
||||
[string] $NativeSha256,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidatePattern('^[0-9A-Fa-f]{64}$')]
|
||||
[string] $InteropSha256,
|
||||
|
||||
[ValidateRange(0, 2147483647)]
|
||||
[Nullable[int]] $OutputChannel = $null,
|
||||
|
||||
[switch] $Force,
|
||||
|
||||
[string] $ConfigurationDirectory = (
|
||||
Join-Path $env:LOCALAPPDATA 'MBN_STOCK_WEBVIEW\Config')
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$requiredAuthorization = 'I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH'
|
||||
$sceneAllowlist = @(
|
||||
'5001', '5006', '5011', '5016', '50160', '5023', '5024', '5025',
|
||||
'5026', '5029', '5032', '5037', '5068', '5070', '5072', '5074',
|
||||
'5076', '5077', '5078', '5079', '5080', '5081', '5082', '5083',
|
||||
'5084', '5085', '5086', '50860', '5087', '5088', '6001', '6067',
|
||||
'8001', '8002', '8003', '8018', '8032', '8035', '8040', '8046',
|
||||
'8051', '8056', '8061', '8067', 'N5001'
|
||||
)
|
||||
|
||||
function Assert-LocalAppDataPath {
|
||||
param([Parameter(Mandatory)][string] $Path)
|
||||
|
||||
$localRoot = [IO.Path]::GetFullPath(
|
||||
[Environment]::GetFolderPath(
|
||||
[Environment+SpecialFolder]::LocalApplicationData))
|
||||
$fullPath = [IO.Path]::GetFullPath($Path)
|
||||
$rootPrefix = $localRoot.TrimEnd(
|
||||
[IO.Path]::DirectorySeparatorChar,
|
||||
[IO.Path]::AltDirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
|
||||
|
||||
if (-not $fullPath.StartsWith(
|
||||
$rootPrefix,
|
||||
[StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw 'Development Live configuration must stay under LocalAppData.'
|
||||
}
|
||||
|
||||
return $fullPath
|
||||
}
|
||||
|
||||
function Assert-NoReparsePointChain {
|
||||
param(
|
||||
[Parameter(Mandatory)][string] $Directory,
|
||||
[Parameter(Mandatory)][string] $StopDirectory
|
||||
)
|
||||
|
||||
$current = [IO.Path]::GetFullPath($Directory)
|
||||
$stop = [IO.Path]::GetFullPath($StopDirectory)
|
||||
while (-not [string]::Equals(
|
||||
$current,
|
||||
$stop,
|
||||
[StringComparison]::OrdinalIgnoreCase)) {
|
||||
$item = Get-Item -LiteralPath $current -Force
|
||||
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw 'Development Live configuration directories cannot be reparse points.'
|
||||
}
|
||||
|
||||
$parent = [IO.Path]::GetDirectoryName($current)
|
||||
if ([string]::IsNullOrWhiteSpace($parent) -or
|
||||
[string]::Equals(
|
||||
$parent,
|
||||
$current,
|
||||
[StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw 'The LocalAppData configuration directory chain is invalid.'
|
||||
}
|
||||
|
||||
$current = $parent
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-WritableTarget {
|
||||
param([Parameter(Mandatory)][string] $Path)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
return
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $Path -Force
|
||||
if ($item.PSIsContainer -or
|
||||
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw 'Development Live configuration targets must be ordinary files.'
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
throw "Configuration already exists. Review it and rerun with -Force: $Path"
|
||||
}
|
||||
}
|
||||
|
||||
function Set-CurrentUserOnlyAcl {
|
||||
param([Parameter(Mandatory)][string] $Path)
|
||||
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
$acl = [Security.AccessControl.FileSecurity]::new()
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$rule = [Security.AccessControl.FileSystemAccessRule]::new(
|
||||
$identity,
|
||||
[Security.AccessControl.FileSystemRights]::FullControl,
|
||||
[Security.AccessControl.AccessControlType]::Allow)
|
||||
$acl.AddAccessRule($rule)
|
||||
[IO.File]::SetAccessControl($Path, $acl)
|
||||
}
|
||||
|
||||
function Invalidate-ExistingAuthorization {
|
||||
param([Parameter(Mandatory)][string] $Path)
|
||||
|
||||
if (-not [IO.File]::Exists($Path)) {
|
||||
return
|
||||
}
|
||||
|
||||
$invalidatedPath = (
|
||||
$Path + '.invalidated.' + [Guid]::NewGuid().ToString('N'))
|
||||
[IO.File]::Move($Path, $invalidatedPath)
|
||||
try {
|
||||
Set-CurrentUserOnlyAcl $invalidatedPath
|
||||
[IO.File]::Delete($invalidatedPath)
|
||||
}
|
||||
catch {
|
||||
throw (
|
||||
'The previous Development Live authorization was invalidated but ' +
|
||||
"could not be removed. Remove this protected file before retrying: $invalidatedPath")
|
||||
}
|
||||
|
||||
if ([IO.File]::Exists($Path)) {
|
||||
throw 'The previous Development Live authorization could not be invalidated.'
|
||||
}
|
||||
}
|
||||
|
||||
function Write-ProtectedJson {
|
||||
param(
|
||||
[Parameter(Mandatory)][string] $Path,
|
||||
[Parameter(Mandatory)][string] $Json
|
||||
)
|
||||
|
||||
$temporaryPath = "$Path.tmp.$([Guid]::NewGuid().ToString('N'))"
|
||||
try {
|
||||
[IO.File]::WriteAllText(
|
||||
$temporaryPath,
|
||||
$Json,
|
||||
[Text.UTF8Encoding]::new($false))
|
||||
Set-CurrentUserOnlyAcl $temporaryPath
|
||||
Move-Item -LiteralPath $temporaryPath -Destination $Path -Force
|
||||
}
|
||||
finally {
|
||||
if ([IO.File]::Exists($temporaryPath)) {
|
||||
Remove-Item -LiteralPath $temporaryPath -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$configurationRoot = Assert-LocalAppDataPath $ConfigurationDirectory
|
||||
[IO.Directory]::CreateDirectory($configurationRoot) | Out-Null
|
||||
$localApplicationData = [IO.Path]::GetFullPath(
|
||||
[Environment]::GetFolderPath(
|
||||
[Environment+SpecialFolder]::LocalApplicationData))
|
||||
Assert-NoReparsePointChain $configurationRoot $localApplicationData
|
||||
|
||||
$playoutPath = Join-Path $configurationRoot 'playout.local.json'
|
||||
$authorizationPath = Join-Path $configurationRoot 'playout.development-live.local.json'
|
||||
Assert-WritableTarget $playoutPath
|
||||
Assert-WritableTarget $authorizationPath
|
||||
|
||||
$playout = [ordered]@{
|
||||
mode = 'DryRun'
|
||||
host = $PlayoutHost
|
||||
port = $PlayoutPort
|
||||
tcpMode = 1
|
||||
clientPort = 0
|
||||
sceneDirectory = $null
|
||||
outputChannel = $OutputChannel
|
||||
layoutIndex = 10
|
||||
legacySceneFadeDuration = 6
|
||||
legacySceneBackgroundKind = 'None'
|
||||
legacySceneBackgroundAssetPath = $null
|
||||
legacySceneBackgroundVideoLoopCount = 2004
|
||||
legacySceneBackgroundVideoLoopInfinite = $true
|
||||
legacyBackgroundDirectory = $null
|
||||
testProcessWindowTitlePattern = $null
|
||||
testSceneAllowlist = $sceneAllowlist
|
||||
trustedLiveOutputEnabled = $true
|
||||
queueCapacity = 64
|
||||
connectTimeoutMilliseconds = 5000
|
||||
operationTimeoutMilliseconds = 5000
|
||||
disconnectTimeoutMilliseconds = 3000
|
||||
processPollIntervalMilliseconds = 1000
|
||||
reconnectDelayMilliseconds = 1000
|
||||
maximumReconnectAttempts = 0
|
||||
reconnectEnabled = $false
|
||||
maximumAutomaticRefreshesPerTakeIn = 0
|
||||
}
|
||||
|
||||
$authorization = [ordered]@{
|
||||
schemaVersion = 1
|
||||
mode = 'Live'
|
||||
authorization = $requiredAuthorization
|
||||
nativeSha256 = $NativeSha256.ToUpperInvariant()
|
||||
interopSha256 = $InteropSha256.ToUpperInvariant()
|
||||
}
|
||||
|
||||
$playoutJson = $playout | ConvertTo-Json -Depth 6
|
||||
$authorizationJson = $authorization | ConvertTo-Json -Depth 3
|
||||
|
||||
# In Force mode, invalidate the old authorization before changing the DryRun
|
||||
# base. Any later failure therefore leaves no valid authorization at the exact
|
||||
# path consumed by the Debug-only bootstrap.
|
||||
Invalidate-ExistingAuthorization -Path $authorizationPath
|
||||
|
||||
# Write the safe DryRun base first and the new one-launch authorization last.
|
||||
# Merely creating these files does not connect: Debug, the exact
|
||||
# --development-live argument, vendor hash verification and runtime gates remain
|
||||
# mandatory in the application.
|
||||
Write-ProtectedJson $playoutPath $playoutJson
|
||||
Write-ProtectedJson $authorizationPath $authorizationJson
|
||||
|
||||
Write-Host "Development Live base configuration created at: $playoutPath"
|
||||
Write-Host "Development Live launch authorization created at: $authorizationPath"
|
||||
Write-Warning (
|
||||
'The base file remains DryRun. Use only Debug|x64 with the exact ' +
|
||||
'Development Live (Package) profile after confirming the local development PGM target.')
|
||||
Reference in New Issue
Block a user