184 lines
7.2 KiB
PowerShell
184 lines
7.2 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Installe (ou met à jour) l'agent Stream Control sur une VM Windows.
|
|
|
|
.DESCRIPTION
|
|
L'agent est enregistré comme tâche planifiée « à l'ouverture de session », et
|
|
non comme service Windows : un service tourne en session 0 et ne verrait ni la
|
|
fenêtre du navigateur, ni OBS.
|
|
|
|
Réexécuter le script met à jour le binaire sans toucher à l'identité d'un agent
|
|
déjà enrôlé.
|
|
|
|
.EXAMPLE
|
|
& ([scriptblock]::Create((irm 'https://git.jeanbonapp.com/api/packages/jeanbon/generic/stream-control-agent/latest/install-agent.ps1'))) `
|
|
-Registry 'https://git.jeanbonapp.com' `
|
|
-Server 'ws://control.lan:8080/ws/agent' `
|
|
-Token '<JETON_ENROLEMENT>'
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)][string] $Registry,
|
|
[string] $Server = '',
|
|
[string] $Token = '',
|
|
[string] $Name = $env:COMPUTERNAME,
|
|
[string] $Version = 'latest',
|
|
[string] $Owner = 'jeanbon',
|
|
[string] $PackageToken = '',
|
|
[string] $ObsHost = '127.0.0.1',
|
|
[int] $ObsPort = 4455,
|
|
[string] $ObsPassword = '',
|
|
[string] $InstallDir = 'C:\stream-control-agent',
|
|
[string] $RunAsUser = "$env:USERDOMAIN\$env:USERNAME",
|
|
[string] $TaskName = 'StreamControlAgent'
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$Registry = $Registry.TrimEnd('/')
|
|
|
|
function Info($msg) { Write-Host "· $msg" }
|
|
function Die($msg) { Write-Host "✗ $msg" -ForegroundColor Red; exit 1 }
|
|
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
if (-not $isAdmin) { Die 'À exécuter dans un PowerShell administrateur.' }
|
|
|
|
$configFile = Join-Path $InstallDir 'agent.config.json'
|
|
$isUpgrade = Test-Path $configFile
|
|
|
|
if (-not $isUpgrade) {
|
|
if (-not $Server) { Die '-Server est obligatoire pour une première installation.' }
|
|
if (-not $Token) { Die '-Token est obligatoire pour une première installation.' }
|
|
}
|
|
|
|
Write-Host ''
|
|
Write-Host 'Installation de l''agent Stream Control'
|
|
Write-Host " compte : $RunAsUser"
|
|
Write-Host " destination : $InstallDir"
|
|
Write-Host " version : $Version"
|
|
Write-Host ''
|
|
|
|
# --- Node.js ----------------------------------------------------------------
|
|
$nodeMajor = 0
|
|
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
|
if ($nodeCmd) {
|
|
$nodeMajor = [int]((& node -p 'process.versions.node.split(".")[0]') 2>$null)
|
|
}
|
|
|
|
if ($nodeMajor -lt 22) {
|
|
Info "Node.js 22+ requis (trouvé : $nodeMajor) — installation via winget"
|
|
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
|
|
Die 'winget introuvable. Installe Node.js 22+ manuellement puis relance : https://nodejs.org/'
|
|
}
|
|
& winget install --id OpenJS.NodeJS.LTS --silent --accept-source-agreements --accept-package-agreements
|
|
# winget ne rafraîchit pas le PATH de la session courante.
|
|
$env:Path = [Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' +
|
|
[Environment]::GetEnvironmentVariable('Path', 'User')
|
|
if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
|
|
Die 'Node.js installé mais absent du PATH — rouvre PowerShell et relance le script.'
|
|
}
|
|
} else {
|
|
Info "Node.js $(& node -v) présent"
|
|
}
|
|
|
|
# --- Téléchargement du bundle -----------------------------------------------
|
|
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
|
|
$url = "$Registry/api/packages/$Owner/generic/stream-control-agent/$Version/agent.cjs"
|
|
Info "Téléchargement depuis $url"
|
|
|
|
$headers = @{}
|
|
if ($PackageToken) { $headers['Authorization'] = "token $PackageToken" }
|
|
|
|
# Le fichier temporaire doit garder l'extension .cjs : `node --check` déduit le
|
|
# format du module de l'extension et rejette tout ce qu'il ne reconnaît pas
|
|
# (« Unknown file extension .tmp »), quel que soit le contenu.
|
|
$tmp = Join-Path $InstallDir 'agent-new.cjs'
|
|
try {
|
|
Invoke-WebRequest -Uri $url -Headers $headers -OutFile $tmp -UseBasicParsing
|
|
} catch {
|
|
Die "Téléchargement impossible ($($_.Exception.Message)). Vérifie -Registry, -Version, et -PackageToken si le paquet est privé."
|
|
}
|
|
|
|
$checkErr = (& node --check $tmp 2>&1 | Out-String)
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host $checkErr
|
|
Write-Host " (fichier conservé pour inspection : $tmp)"
|
|
Die 'Le fichier téléchargé n''est pas un script Node valide — voir l''erreur ci-dessus.'
|
|
}
|
|
|
|
Move-Item -Force $tmp (Join-Path $InstallDir 'agent.cjs')
|
|
|
|
# --- Configuration ----------------------------------------------------------
|
|
if ($isUpgrade) {
|
|
# Un agent déjà enrôlé détient un jeton permanent : l'écraser le ferait
|
|
# réapparaître comme un second agent dans le dashboard.
|
|
Info 'Configuration existante conservée (identité de l''agent préservée)'
|
|
} else {
|
|
$config = [ordered]@{
|
|
serverUrl = $Server
|
|
token = $Token
|
|
name = $Name
|
|
obs = [ordered]@{ host = $ObsHost; port = $ObsPort; password = $ObsPassword }
|
|
}
|
|
# Surtout pas Set-Content -Encoding UTF8 : sous PowerShell 5.1 il préfixe un
|
|
# BOM, et JSON.parse côté Node refuse le fichier.
|
|
$json = $config | ConvertTo-Json -Depth 5
|
|
[System.IO.File]::WriteAllText($configFile, $json, (New-Object System.Text.UTF8Encoding($false)))
|
|
Info "Configuration écrite dans $configFile"
|
|
}
|
|
|
|
# Le fichier porte un jeton : lecture réservée au compte de l'agent et aux admins.
|
|
$acl = Get-Acl $configFile
|
|
$acl.SetAccessRuleProtection($true, $false)
|
|
foreach ($identity in @($RunAsUser, 'BUILTIN\Administrators', 'NT AUTHORITY\SYSTEM')) {
|
|
try {
|
|
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
|
|
$identity, 'FullControl', 'Allow')))
|
|
} catch {
|
|
Write-Host "! Règle d'accès ignorée pour $identity"
|
|
}
|
|
}
|
|
Set-Acl -Path $configFile -AclObject $acl
|
|
|
|
# --- Tâche planifiée --------------------------------------------------------
|
|
$nodePath = (Get-Command node).Source
|
|
$entry = Join-Path $InstallDir 'agent.cjs'
|
|
|
|
$action = New-ScheduledTaskAction -Execute $nodePath -Argument "`"$entry`"" -WorkingDirectory $InstallDir
|
|
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $RunAsUser
|
|
$principal = New-ScheduledTaskPrincipal -UserId $RunAsUser -LogonType Interactive -RunLevel Limited
|
|
$settings = New-ScheduledTaskSettingsSet `
|
|
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
|
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
|
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
|
|
|
|
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
|
-Principal $principal -Settings $settings -Force | Out-Null
|
|
|
|
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
|
Start-ScheduledTask -TaskName $TaskName
|
|
|
|
# --- Vérification -----------------------------------------------------------
|
|
Write-Host ''
|
|
$env:AGENT_CONFIG = $configFile
|
|
& node $entry --check
|
|
Write-Host ''
|
|
|
|
Start-Sleep -Seconds 2
|
|
$state = (Get-ScheduledTask -TaskName $TaskName).State
|
|
if ($state -eq 'Running') {
|
|
Write-Host '✓ Agent installé et démarré.' -ForegroundColor Green
|
|
} else {
|
|
Write-Host "! Tâche dans l'état « $state ». Détails :" -ForegroundColor Yellow
|
|
Get-ScheduledTaskInfo -TaskName $TaskName | Format-List TaskName, LastRunTime, LastTaskResult
|
|
}
|
|
|
|
Write-Host @"
|
|
|
|
état : Get-ScheduledTask -TaskName $TaskName
|
|
test : node $entry --check
|
|
|
|
"@
|