Files
stream-control/deploy/install-agent.ps1
jeanotx32 6a45b3063f
All checks were successful
release / build (push) Successful in 16s
release / verify-windows (push) Successful in 1m13s
Fix : widows giea 2
2026-08-11 22:48:15 +02:00

246 lines
10 KiB
PowerShell

<#
ATTENTION : ce fichier doit rester en ASCII pur, sans accent ni typographie.
Il est publie sans BOM et execute par Windows PowerShell 5.1, qui relit alors
un .ps1 dans la page de codes ANSI. Un caractere non-ASCII y devient illisible,
et certains cassent carrement le parseur : le tiret cadratin se relit en CP1252
comme trois caracteres dont le dernier, U+201D, est un delimiteur de chaine
valide en PowerShell. La chaine se ferme en plein milieu de la ligne, et
l'erreur remontee est une accolade manquante ailleurs dans le fichier.
La CI verifie cette regle avant le controle de syntaxe.
.SYNOPSIS
Installe (ou met a jour) l'agent Stream Control sur une VM Windows.
.DESCRIPTION
L'agent est enregistre comme tache planifiee 'a l'ouverture de session', et
non comme service Windows : un service tourne en session 0 et ne verrait ni la
fenetre du navigateur, ni OBS.
Reexecuter le script met a jour le binaire et applique les options fournies.
L'identite de l'agent (jeton permanent, agentId) est preservee : sans cela il
reapparaitrait en double dans le dashboard. Utiliser -ResetIdentity pour forcer
un reenrolement.
.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://<IP_DU_CONTROLE>: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',
# Reenrole avec le -Token fourni : efface l'identite existante, ce qui cree un
# nouvel agent dans le dashboard. Sans ce commutateur, -Token est ignore sur
# une mise a jour.
[switch] $ResetIdentity
)
$ErrorActionPreference = 'Stop'
$Registry = $Registry.TrimEnd('/')
function Info($msg) { Write-Host "- $msg" }
function Die($msg) { Write-Host "[ECHEC] $msg" -ForegroundColor Red; exit 1 }
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) { Die 'A executer 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 premiere installation.' }
if (-not $Token) { Die '-Token est obligatoire pour une premiere 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) {
# On lit `node --version` plutot que d'evaluer une expression JavaScript.
# PowerShell 5.1 perd les guillemets doubles imbriques quand il passe un
# argument a un executable natif : `node -p 'x.split(".")[0]'` arrive cote
# Node comme `x.split(.)[0]`, qui leve une SyntaxError. Le majeur valait
# alors 0 et le script reinstallait Node sur une machine deja equipee.
$nodeRaw = (& node --version 2>$null)
if ($nodeRaw -match '^v(\d+)\.') { $nodeMajor = [int]$Matches[1] }
}
if ($nodeMajor -lt 22) {
Info "Node.js 22+ requis (trouve : $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 rafraichit 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 installe mais absent du PATH - rouvre PowerShell et relance le script.'
}
} else {
Info "Node.js $(& node -v) present"
}
# --- Telechargement du bundle -----------------------------------------------
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
$url = "$Registry/api/packages/$Owner/generic/stream-control-agent/$Version/agent.cjs"
Info "Telechargement depuis $url"
$headers = @{}
if ($PackageToken) { $headers['Authorization'] = "token $PackageToken" }
# Le fichier temporaire doit garder l'extension .cjs : `node --check` deduit le
# format du module de l'extension et rejette tout ce qu'il ne reconnait 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 "Telechargement impossible ($($_.Exception.Message)). Verifie -Registry, -Version, et -PackageToken si le paquet est prive."
}
$checkErr = (& node --check $tmp 2>&1 | Out-String)
if ($LASTEXITCODE -ne 0) {
Write-Host $checkErr
Write-Host " (fichier conserve pour inspection : $tmp)"
Die 'Le fichier telecharge n''est pas un script Node valide - voir l''erreur ci-dessus.'
}
Move-Item -Force $tmp (Join-Path $InstallDir 'agent.cjs')
# --- Configuration ----------------------------------------------------------
if ($isUpgrade) {
# Fusion, et non conservation en bloc : l'identite (jeton, agentId) est
# preservee - l'ecraser ferait reapparaitre l'agent en double dans le
# dashboard - mais toute option explicitement passee doit s'appliquer.
# $PSBoundParameters distingue 'fourni' de 'laisse au defaut'.
$raw = (Get-Content $configFile -Raw) -replace "^\uFEFF", ''
$current = $raw | ConvertFrom-Json
if (-not $current.obs) {
$current | Add-Member -NotePropertyName obs -NotePropertyValue ([pscustomobject]@{}) -Force
}
$applied = @()
# Toujours rafraichie : ce n'est pas de l'identite, c'est de l'infrastructure.
# Sans elle, l'agent ne saurait pas ou chercher sa propre mise a jour.
if ($current.PSObject.Properties.Name -notcontains 'packageUrl') {
$current | Add-Member -NotePropertyName packageUrl -NotePropertyValue $url -Force
$applied += 'packageUrl'
} elseif ($current.packageUrl -ne $url) {
$current.packageUrl = $url
$applied += 'packageUrl'
}
if ($PSBoundParameters.ContainsKey('Server')) { $current.serverUrl = $Server; $applied += 'serverUrl' }
if ($PSBoundParameters.ContainsKey('Name')) { $current.name = $Name; $applied += 'name' }
if ($PSBoundParameters.ContainsKey('ObsHost')) { $current.obs.host = $ObsHost; $applied += 'obs.host' }
if ($PSBoundParameters.ContainsKey('ObsPort')) { $current.obs.port = $ObsPort; $applied += 'obs.port' }
if ($PSBoundParameters.ContainsKey('ObsPassword')) { $current.obs.password = $ObsPassword; $applied += 'obs.password' }
if ($ResetIdentity -and $Token) {
$current.token = $Token
$current.PSObject.Properties.Remove('agentId')
$applied += 'identite reinitialisee'
} elseif ($Token -and $Token -ne $current.token) {
Write-Host ' (-Token ignore : identite conservee. Utilise -ResetIdentity pour forcer.)'
}
$json = $current | ConvertTo-Json -Depth 5
[System.IO.File]::WriteAllText($configFile, $json, (New-Object System.Text.UTF8Encoding($false)))
Write-Host $(if ($applied.Count) { " champs mis a jour : $($applied -join ', ')" }
else { ' aucun changement demande' })
Info 'Configuration mise a jour, identite de l''agent preservee'
} else {
$config = [ordered]@{
serverUrl = $Server
token = $Token
name = $Name
packageUrl = $url
obs = [ordered]@{ host = $ObsHost; port = $ObsPort; password = $ObsPassword }
}
# Surtout pas Set-Content -Encoding UTF8 : sous PowerShell 5.1 il prefixe un
# BOM, et JSON.parse cote Node refuse le fichier.
$json = $config | ConvertTo-Json -Depth 5
[System.IO.File]::WriteAllText($configFile, $json, (New-Object System.Text.UTF8Encoding($false)))
Info "Configuration ecrite dans $configFile"
}
# Le fichier porte un jeton : lecture reservee 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 "! Regle d'acces ignoree pour $identity"
}
}
Set-Acl -Path $configFile -AclObject $acl
# --- Tache planifiee --------------------------------------------------------
$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
# --- Verification -----------------------------------------------------------
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 '[OK] Agent installe et demarre.' -ForegroundColor Green
} else {
Write-Host "! Tache dans l'etat '$state'. Details :" -ForegroundColor Yellow
Get-ScheduledTaskInfo -TaskName $TaskName | Format-List TaskName, LastRunTime, LastTaskResult
}
Write-Host @"
etat : Get-ScheduledTask -TaskName $TaskName
test : node $entry --check
"@