fix: deploy syncs Sac-Client bundle, SAC dual settings, and ServerDisplayName hint (1.2.20-SAC)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-05-29 15:55:16 +10:00
parent 1f27272883
commit c249451eba
5 changed files with 201 additions and 37 deletions
+184 -31
View File
@@ -9,6 +9,8 @@
СТРУКТУРА НА ШАРЕ (пример):
\\dc\share\RDP-login-monitor\Login_Monitor.ps1
\\dc\share\RDP-login-monitor\Sac-Client.ps1 — обязателен для SAC
\\dc\share\RDP-login-monitor\login_monitor.settings.example.ps1
\\dc\share\RDP-login-monitor\version.txt — одна строка, например: 1.3.0
\\dc\share\RDP-login-monitor\Deploy-LoginMonitor.ps1
@@ -51,7 +53,9 @@ $VersionStampPath = Join-Path $InstallRoot "deployed_version.txt"
$DeployUpdateMarkerPath = Join-Path $InstallRoot "deploy_last_update.txt"
$DeployLogPath = Join-Path $InstallRoot "Logs\deploy.log"
$ScriptName = "Login_Monitor.ps1"
$SacClientName = "Sac-Client.ps1"
$VersionFileName = "version.txt"
$DeployBundleFiles = @($ScriptName, $SacClientName)
$PsExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$Utf8Bom = New-Object System.Text.UTF8Encoding $true
@@ -194,13 +198,49 @@ function Get-RdpMonitorSettingsRaw {
}
}
function Test-RdpMonitorSettingsNeedsSacBootstrap {
param(
[string]$SettingsPath,
[string]$SacClientPath
)
if (-not (Test-Path -LiteralPath $SacClientPath)) { return $true }
function Get-DeployFileSha256 {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return $null }
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256 -ErrorAction Stop).Hash
}
function Test-RdpMonitorDeployBundleNeedsSync {
param([string]$ShareRoot)
foreach ($rel in $DeployBundleFiles) {
$src = Join-Path $ShareRoot $rel
if (-not (Test-Path -LiteralPath $src)) {
if ($rel -eq $SacClientName) {
Write-DeployLog "ОШИБКА: на шаре нет обязательного $SacClientName — опубликуйте пакет (update-rdp-monitor.ps1)."
}
continue
}
$dst = Join-Path $InstallRoot $rel
if (-not (Test-Path -LiteralPath $dst)) { return $true }
$hs = Get-DeployFileSha256 -Path $src
$hd = Get-DeployFileSha256 -Path $dst
if ($hs -ne $hd) { return $true }
}
return $false
}
function Copy-RdpMonitorDeployBundle {
param([string]$ShareRoot)
foreach ($rel in $DeployBundleFiles) {
$src = Join-Path $ShareRoot $rel
$dst = Join-Path $InstallRoot $rel
if (-not (Test-Path -LiteralPath $src)) {
if ($rel -eq $SacClientName) {
Write-DeployLog "Предупреждение: на шаре нет $SacClientName — SAC недоступен до публикации файла на шару."
}
continue
}
Copy-Item -LiteralPath $src -Destination $dst -Force
Write-DeployLog "Файл скопирован: $dst"
}
}
function Test-RdpMonitorSettingsNeedsSacBootstrap {
param([string]$SettingsPath)
$c = Get-RdpMonitorSettingsRaw -Path $SettingsPath
if ([string]::IsNullOrWhiteSpace($c)) { return $true }
@@ -210,10 +250,112 @@ function Test-RdpMonitorSettingsNeedsSacBootstrap {
}
if ($c -match '(?m)^\s*\$SacApiKey\s*=\s*[''"]\s*[''"]') { return $true }
if ($c -notmatch '(?m)^\s*\$SacApiKey\s*=\s*[''"]sac_[^''"]+[''"]') { return $true }
if ($c -notmatch '(?m)^\s*\$SacUrl\s*=\s*[''"]https?://[^''"]+[''"]') { return $true }
return $false
}
function Test-RdpMonitorSettingsNeedsServerDisplayNameHint {
param([string]$SettingsPath)
if (-not (Test-Path -LiteralPath $SettingsPath)) { return $false }
$c = Get-RdpMonitorSettingsRaw -Path $SettingsPath
if ([string]::IsNullOrWhiteSpace($c)) { return $false }
if ($c -match '(?m)^\s*(\#\s*)?\$ServerDisplayName\s*=') { return $false }
return $true
}
function Update-RdpMonitorSettingsServerDisplayNameHintIfMissing {
param([string]$LocalSettings)
if (-not (Test-Path -LiteralPath $LocalSettings)) { return $false }
if (-not (Test-RdpMonitorSettingsNeedsServerDisplayNameHint -SettingsPath $LocalSettings)) {
return $false
}
$c = Get-RdpMonitorSettingsRaw -Path $LocalSettings
if ([string]::IsNullOrWhiteSpace($c)) { return $false }
$hostLabel = if ([string]::IsNullOrWhiteSpace($env:COMPUTERNAME)) { 'New-PC' } else { $env:COMPUTERNAME.Trim() }
$hintBlock = @(
'# --- Подпись сервера в Telegram и SAC (host.display_name); раскомментируйте при необходимости ---'
"# `$ServerDisplayName = '$hostLabel'"
'# --- Явный IPv4 хоста для SAC (опционально; иначе автоопределение) ---'
'# $ServerIPv4 = '''
) -join "`r`n"
$bak = "$LocalSettings.bak.$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -LiteralPath $LocalSettings -Destination $bak -Force
Write-DeployLog "Добавление закомментированного `$ServerDisplayName в login_monitor.settings.ps1; резервная копия: $bak"
$insertBefore = '(?m)^\s*#\s*---\s*Security Alert Center'
if ($c -match $insertBefore) {
$newContent = [regex]::Replace($c, $insertBefore, ($hintBlock + "`r`n`r`n" + '$0'), 1)
} else {
$newContent = ($c.TrimEnd() + "`r`n`r`n" + $hintBlock + "`r`n")
}
[System.IO.File]::WriteAllText($LocalSettings, $newContent.TrimEnd() + "`r`n", $Utf8Bom)
Write-DeployLog "login_monitor.settings.ps1: добавлена подсказка # `$ServerDisplayName = '$hostLabel'"
return $true
}
function Get-RdpMonitorSacBlockFromExample {
param([string]$ExamplePath)
if (-not (Test-Path -LiteralPath $ExamplePath)) { return $null }
$ex = Get-RdpMonitorSettingsRaw -Path $ExamplePath
if ([string]::IsNullOrWhiteSpace($ex)) { return $null }
if ($ex -match '(?ms)(#\s*---\s*Security Alert Center.*?)(?=\r?\n#\s*---|\z)') {
return $Matches[1].TrimEnd()
}
return @(
'# --- Security Alert Center (SAC) ---'
'$UseSAC = ''dual'''
'$SacUrl = ''https://sac.kalinamall.ru'''
'$SacApiKey = ''sac_CHANGE_ME'''
) -join "`r`n"
}
function Update-RdpMonitorSettingsSacBlockIfMissing {
param(
[string]$LocalSettings,
[string]$ExampleOnShare
)
if (-not (Test-Path -LiteralPath $LocalSettings)) { return $false }
$c = Get-RdpMonitorSettingsRaw -Path $LocalSettings
if ([string]::IsNullOrWhiteSpace($c)) { return $false }
if ($c -match '(?m)^\s*\$UseSAC\s*=' -and $c -match '(?m)^\s*\$SacApiKey\s*=\s*[''"]sac_[^''"]+[''"]') {
if ($c -notmatch '(?m)^\s*\$UseSAC\s*=\s*[''"]off[''"]') {
Write-DeployLog "login_monitor.settings.ps1: блок SAC уже задан, файл не меняем."
return $false
}
}
$sacBlock = Get-RdpMonitorSacBlockFromExample -ExamplePath $ExampleOnShare
if ([string]::IsNullOrWhiteSpace($sacBlock)) {
Write-DeployLog "Предупреждение: не удалось извлечь блок SAC из example — пропуск patch settings."
return $false
}
$bak = "$LocalSettings.bak.$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -LiteralPath $LocalSettings -Destination $bak -Force
Write-DeployLog "Добавление блока SAC (UseSAC=dual) в login_monitor.settings.ps1; резервная копия: $bak"
if ($c -match '(?m)^\s*\$UseSAC\s*=') {
$c = [regex]::Replace($c, '(?m)^\s*\$UseSAC\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacUrl\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacApiKey\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacSpoolDir\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacTimeoutSec\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacTlsSkipVerify\s*=.*$', '')
$c = [regex]::Replace($c, '(?m)^\s*\$SacFallbackFailures\s*=.*$', '')
}
$newContent = ($c.TrimEnd() + "`r`n`r`n" + $sacBlock + "`r`n").TrimEnd() + "`r`n"
[System.IO.File]::WriteAllText($LocalSettings, $newContent, $Utf8Bom)
Write-DeployLog "login_monitor.settings.ps1: добавлен блок SAC (UseSAC=dual из example на шаре)."
return $true
}
function Sync-RdpMonitorSettingsFromShare {
param(
[string]$ExampleOnShare,
@@ -224,12 +366,12 @@ function Sync-RdpMonitorSettingsFromShare {
return
}
$localSac = Join-Path $InstallRoot 'Sac-Client.ps1'
$needsCreate = -not (Test-Path -LiteralPath $LocalSettings)
$needsBootstrap = $needsCreate -or (Test-RdpMonitorSettingsNeedsSacBootstrap -SettingsPath $LocalSettings -SacClientPath $localSac)
$needsBootstrap = $needsCreate -or (Test-RdpMonitorSettingsNeedsSacBootstrap -SettingsPath $LocalSettings)
if (-not $needsBootstrap) {
Write-DeployLog "login_monitor.settings.ps1: SAC уже настроен, файл не перезаписываем."
Update-RdpMonitorSettingsServerDisplayNameHintIfMissing -LocalSettings $LocalSettings | Out-Null
return
}
@@ -239,13 +381,21 @@ function Sync-RdpMonitorSettingsFromShare {
if ($needsCreate) {
Write-DeployLog "Создан login_monitor.settings.ps1 из example (Telegram + SAC dual)."
} else {
$bak = "$LocalSettings.bak.$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -LiteralPath $LocalSettings -Destination $bak -Force
Write-DeployLog "Апгрейд с версии без SAC: резервная копия $bak, применяем example с шары."
Copy-Item -LiteralPath $ExampleOnShare -Destination $LocalSettings -Force
Update-RdpMonitorSettingsServerDisplayNameHintIfMissing -LocalSettings $LocalSettings | Out-Null
return
}
if (Update-RdpMonitorSettingsSacBlockIfMissing -LocalSettings $LocalSettings -ExampleOnShare $ExampleOnShare) {
Update-RdpMonitorSettingsServerDisplayNameHintIfMissing -LocalSettings $LocalSettings | Out-Null
return
}
$bak = "$LocalSettings.bak.$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -LiteralPath $LocalSettings -Destination $bak -Force
Write-DeployLog "Апгрейд settings: резервная копия $bak, применяем example с шары."
Copy-Item -LiteralPath $ExampleOnShare -Destination $LocalSettings -Force
Update-RdpMonitorSettingsServerDisplayNameHintIfMissing -LocalSettings $LocalSettings | Out-Null
}
function Stop-RdpLoginMonitorMainProcesses {
@@ -317,10 +467,14 @@ try {
$localVerRaw = Get-LocalDeployedVersion
Write-DeployLog "Версия на шаре: $shareVerRaw; локально установлено: $(if ($localVerRaw) { $localVerRaw } else { '(нет)' })."
$localSacPath = Join-Path $InstallRoot 'Sac-Client.ps1'
$sourceSacOnShare = Join-Path $shareRoot 'Sac-Client.ps1'
$needsSacBootstrap = (Test-RdpMonitorSettingsNeedsSacBootstrap -SettingsPath $settingsLocal -SacClientPath $localSacPath) `
-or ((-not (Test-Path -LiteralPath $localSacPath)) -and (Test-Path -LiteralPath $sourceSacOnShare))
if (-not (Test-Path -LiteralPath (Join-Path $shareRoot $SacClientName))) {
Write-DeployLog "ОШИБКА: на шаре отсутствует $SacClientName — выполните update-rdp-monitor.ps1 на сервере публикации."
}
$needsSettingsBootstrap = Test-RdpMonitorSettingsNeedsSacBootstrap -SettingsPath $settingsLocal
$needsDisplayNameHint = Test-RdpMonitorSettingsNeedsServerDisplayNameHint -SettingsPath $settingsLocal
$needsBundleSync = Test-RdpMonitorDeployBundleNeedsSync -ShareRoot $shareRoot
$needsSacBootstrap = $needsSettingsBootstrap -or $needsBundleSync -or $needsDisplayNameHint
$cmp = Compare-VersionStrings -Left $shareVerRaw -Right $localVerRaw
if ($null -ne $cmp) {
@@ -329,7 +483,13 @@ try {
Write-DeployLog "Актуально, копирование не требуется."
exit 0
}
Write-DeployLog "Версия совпадает ($shareVerRaw), но нужна донастройка SAC — продолжаем деплой."
if ($needsBundleSync) {
Write-DeployLog "Версия совпадает ($shareVerRaw), но пакет файлов (Login_Monitor/Sac-Client) отличается — продолжаем деплой."
} elseif ($needsSettingsBootstrap) {
Write-DeployLog "Версия совпадает ($shareVerRaw), но нужна донастройка SAC в settings — продолжаем деплой."
} elseif ($needsDisplayNameHint) {
Write-DeployLog "Версия совпадает ($shareVerRaw), но нет подсказки `$ServerDisplayName в settings — продолжаем деплой."
}
}
if ($cmp -lt 0 -and -not $AllowDowngrade) {
Write-DeployLog "На шаре версия старше локальной — пропуск (используйте -AllowDowngrade для отката)."
@@ -346,10 +506,13 @@ try {
}
if ($WhatIf) {
if ($needsSacBootstrap) {
Write-DeployLog "[WhatIf] Применить login_monitor.settings.ps1 из example (SAC/Telegram)."
if ($needsSettingsBootstrap) {
Write-DeployLog "[WhatIf] Донастроить login_monitor.settings.ps1 (SAC dual из example)."
}
Write-DeployLog "[WhatIf] Скопировать $sourceScript -> $LocalScript; InstallTasks; версия $shareVerRaw"
if ($needsBundleSync) {
Write-DeployLog "[WhatIf] Синхронизировать пакет: $($DeployBundleFiles -join ', ')."
}
Write-DeployLog "[WhatIf] InstallTasks; версия $shareVerRaw"
exit 0
}
@@ -360,17 +523,7 @@ try {
Stop-RdpLoginMonitorMainProcesses
Copy-Item -LiteralPath $sourceScript -Destination $LocalScript -Force
Write-DeployLog "Файл скопирован: $LocalScript"
$sourceSac = Join-Path $shareRoot 'Sac-Client.ps1'
$localSac = Join-Path $InstallRoot 'Sac-Client.ps1'
if (Test-Path -LiteralPath $sourceSac) {
Copy-Item -LiteralPath $sourceSac -Destination $localSac -Force
Write-DeployLog "Файл скопирован: $localSac"
} else {
Write-DeployLog "Предупреждение: на шаре нет Sac-Client.ps1 — SAC будет недоступен до следующего деплоя."
}
Copy-RdpMonitorDeployBundle -ShareRoot $shareRoot
Sync-RdpMonitorSettingsFromShare -ExampleOnShare $settingsExampleShare -LocalSettings $settingsLocal
+7 -4
View File
@@ -30,9 +30,11 @@
2. Читается **`version.txt`** на шаре и сравнивается с локальной меткой **`deployed_version.txt`**. Если метки нет — подтягивается **`$ScriptVersion`** из установленного **`Login_Monitor.ps1`**.
3. Версия на шаре **совпадает** с локальной — выход без копирования.
3. Версия на шаре **совпадает** с локальной — выход **без копирования**, **кроме** случаев:
- на ПК **нет** `Sac-Client.ps1` или его SHA256 **отличается** от шары;
- в `login_monitor.settings.ps1` **нет** блока SAC (`$UseSAC`, `$SacUrl`, `$SacApiKey`) — тогда добавляется **`UseSAC = 'dual'`** из example (без перезаписи Telegram, если возможно).
4. Версия на шаре **новее** — остановка процессов монитора → копирование **`Login_Monitor.ps1`** и **`Sac-Client.ps1`** → **`Login_Monitor.ps1 -InstallTasks`** → **`deployed_version.txt`** → запуск монитора (если не **`-SkipStartMonitorAfterUpdate`**).
4. Версия на шаре **новее** (или сработало условие выше) — остановка процессов → копирование **`Login_Monitor.ps1`** и **`Sac-Client.ps1`** → настройка SAC в settings при необходимости → **`Login_Monitor.ps1 -InstallTasks`** → **`deployed_version.txt`** → запуск монитора.
5. Версия на шаре **старее** — откат блокируется, пока не указан **`-AllowDowngrade`**.
@@ -44,8 +46,9 @@
- Секреты и параметры сайта (Telegram, SMTP, **4740**, `$IgnoreAdvapiNetworkLogonSourceIps`) — **только** в этом файле.
- Образец в репозитории и на шаре: **`login_monitor.settings.example.ps1`**.
- **`Deploy-LoginMonitor.ps1`** **не перезаписывает** `login_monitor.settings.ps1` при обновлении скрипта.
- Если файла нет, Deploy **один раз** копирует example → settings (дальше правки только локально).
- **`Deploy-LoginMonitor.ps1`** **не перезаписывает** существующий `login_monitor.settings.ps1`, если SAC уже настроен (`UseSAC` не `off`, заданы `$SacUrl` и `$SacApiKey`).
- Если файла нет Deploy **один раз** копирует example → settings (**`UseSAC = 'dual'`**).
- Если файл есть, но **нет блока SAC** — Deploy **дописывает** блок из example (Telegram/SMTP не трогает).
```powershell
$root = 'C:\ProgramData\RDP-login-monitor'
+1 -1
View File
@@ -80,7 +80,7 @@ $script:MonitorLoopInitialized = $false
# строки ниже, если правки «мелкие» и вы не хотите менять отображаемую версию в логах).
# Рекомендация: при значимых релизах меняйте и $ScriptVersion, и version.txt одинаково; при только
# исправлениях на шаре — достаточно поднять patch в version.txt (например 1.3.0.1).
$ScriptVersion = "1.2.18-SAC"
$ScriptVersion = "1.2.20-SAC"
# Логи (все под InstallRoot)
$LogFile = Join-Path $script:InstallRoot "Logs\login_monitor.log"
+8
View File
@@ -0,0 +1,8 @@
$errs = $null
[void][System.Management.Automation.Language.Parser]::ParseFile(
(Join-Path $PSScriptRoot '..\Deploy-LoginMonitor.ps1'),
[ref]$null,
[ref]$errs
)
if ($errs) { $errs | ForEach-Object { $_.ToString() }; exit 1 }
Write-Output 'OK'
+1 -1
View File
@@ -1 +1 @@
1.2.18-SAC
1.2.20-SAC