Files
Investidor-V2/tools/portal-workflow-activation-services-resume.ps1
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

72 lines
11 KiB
PowerShell

# Resume after ACL completion; no ACL replay, no migrations, no Apache control.
$ErrorActionPreference='Stop'
$taskRoot='C:\laragon\www\portal-investidor';$taskPrivate=Join-Path $taskRoot 'storage\app\private\investmoz-v2\portal-audit'
$utf8=[Text.UTF8Encoding]::new($false);$receipt=Join-Path $taskPrivate 'workflow-activation-services.json'
$proof=Get-Content -LiteralPath $receipt -Raw -Encoding UTF8|ConvertFrom-Json
if($proof.state -ne 'stopped_before_authenticated_installation' -or $proof.tasks_changed.Count -ne 0 -or $proof.acl_changed.Count -lt 100){throw 'Exact stopped service phase required; no ACL replay'}
$plan=Join-Path $taskRoot 'outputs\investmoz-workflow-consolidated-20261011';$manifest=Get-Content -LiteralPath (Join-Path $plan 'manifesto.json') -Raw -Encoding UTF8|ConvertFrom-Json
foreach($p in $manifest.service_proposal.PSObject.Properties){if((Get-FileHash -LiteralPath (Join-Path $plan $p.Name) -Algorithm SHA256).Hash.ToLowerInvariant() -ne $p.Value){throw 'Service proposal differs'}}
$runtime=Join-Path $taskPrivate 'clamav\runtime\clamav-1.5.4.win.x64'
$scheduler=New-Object -ComObject 'Schedule.Service';$scheduler.Connect();$folder=$scheduler.GetFolder('\')
$result=@{at=[DateTime]::UtcNow.ToString('o');revision=$manifest.revision;state='resuming_exact_service_phase';worker_enabled=$false;database_writes=0;apache_control_performed=$false;tasks=@();commands=@()}
function Save-Result{[IO.File]::WriteAllText((Join-Path $taskPrivate 'workflow-activation-services-resumed.json'),($result|ConvertTo-Json -Depth 8),$utf8)}
function Run-OwnTask([string]$name,[int]$seconds){$t=$folder.GetTask($name);$t.Enabled=$true;$null=$t.Run($null);Start-Sleep -Milliseconds 750;$limit=[DateTime]::UtcNow.AddSeconds($seconds);do{if($t.State -ne 4){break};Start-Sleep -Milliseconds 500}while([DateTime]::UtcNow -lt $limit);if($t.State -eq 4 -or $t.LastTaskResult -ne 0){throw ('Authorized task did not complete successfully: '+$name)};return $t.LastTaskResult}
try{
$savedState=$null;$oldReceipt=Join-Path $taskPrivate 'workflow-activation-services-resumed.json';if(Test-Path -LiteralPath $oldReceipt){$savedState=Get-Content -LiteralPath $oldReceipt -Raw -Encoding UTF8|ConvertFrom-Json;$history=Join-Path $taskPrivate ('workflow-activation-services-resumed-before-'+[DateTime]::UtcNow.ToString('yyyyMMdd_HHmmss')+'.json');[IO.File]::Copy($oldReceipt,$history,$false)};Save-Result
if($savedState.tasks.Count -eq 0){foreach($f in Get-ChildItem -LiteralPath $taskPrivate -File -Filter 'workflow-activation-services-resumed-before-*.json' | Sort-Object Name -Descending){$v=Get-Content -LiteralPath $f.FullName -Raw -Encoding UTF8|ConvertFrom-Json;if($v.tasks.Count -eq 3){$savedState=$v;break}}}
if($savedState.tasks.Count -eq 3){
$result.tasks=$savedState.tasks;$result.scanner=$savedState.scanner
foreach($n in @('InvestMoz ClamAV Loopback Scanner','InvestMoz ClamAV Signature Update','InvestMoz Portal Documentary Worker')){$t=$folder.GetTask($n);$user=[string]$t.Definition.Principal.UserId;$sid=if($user -like 'S-1-*'){$user}else{[Security.Principal.NTAccount]::new($user).Translate([Security.Principal.SecurityIdentifier]).Value};if($sid -ne 'S-1-5-19'){throw 'Existing replacement task principal differs'};if($n -eq 'InvestMoz Portal Documentary Worker' -and $t.Enabled){throw 'Worker enabled before verified gate'};if($n -eq 'InvestMoz ClamAV Signature Update' -and $t.Enabled -and $savedState.signature_update_task_result -ne 0){throw 'Updater enabled without a successful controlled invocation'}}
$result.tasks=$savedState.tasks;$result.scanner=$savedState.scanner;$result.scanner_and_tasks_not_reapplied=$true
}else{
foreach($name in @('InvestMoz ClamAV Loopback Scanner','InvestMoz ClamAV Signature Update')){($folder.GetTask($name)).Enabled=$false}
$result.previous_scanner_shutdown_pids=@()
# Windows revealed a second pre-existing3310 listener after the first exited. Only these exact configs qualify.
foreach($attempt in 1..3){$listeners=@(Get-NetTCPConnection -State Listen -LocalPort 3310 -ErrorAction SilentlyContinue);if($listeners.Count -eq 0){break};if($listeners.Count -ne 1 -or $listeners[0].LocalAddress -ne '127.0.0.1'){throw 'Scanner listener differs'};$proc=Get-CimInstance Win32_Process -Filter ('ProcessId='+$listeners[0].OwningProcess)
$knownX64=$proc.ExecutablePath -eq (Join-Path $runtime 'clamd.exe') -and $proc.CommandLine -like '*portal-audit\clamav/clamd.conf*'
$knownArm=$proc.ExecutablePath -eq 'C:\Users\macbook\AppData\Local\InvestMoz\ClamAV\1.5.4\clamav-1.5.4.win.arm64\clamd.exe' -and $proc.CommandLine -like '*clamav-1.5.4.win.arm64\clamd-investmoz.conf*'
if(-not ($knownX64 -or $knownArm)){throw 'Scanner PID/config differs; no shutdown sent'}
$client=[Net.Sockets.TcpClient]::new('127.0.0.1',3310);try{$stream=$client.GetStream();$bytes=[Text.Encoding]::ASCII.GetBytes("nSHUTDOWN`n");$stream.Write($bytes,0,$bytes.Length)}finally{$client.Dispose()}
$result.previous_scanner_shutdown_pids+=$proc.ProcessId;Save-Result
$limit=[DateTime]::UtcNow.AddSeconds(15);do{$still=Get-Process -Id $proc.ProcessId -ErrorAction SilentlyContinue;if(-not $still){break};Start-Sleep -Milliseconds 500}while([DateTime]::UtcNow -lt $limit)
if($still){throw 'Identified scanner did not exit via its own protocol'}
}
$live=@(Get-NetTCPConnection -State Listen -LocalPort 3310 -ErrorAction SilentlyContinue)
if($live.Count -ne 0){throw 'Own scanner shutdown did not release3310'}
foreach($item in @(@('InvestMoz ClamAV Loopback Scanner','InvestMoz-ClamAV-Loopback-Scanner.xml'),@('InvestMoz ClamAV Signature Update','InvestMoz-ClamAV-Signature-Update.xml'),@('InvestMoz Portal Documentary Worker','InvestMoz-Portal-Documentary-Worker.xml'))){
$def=$scheduler.NewTask(0);$xml=[IO.File]::ReadAllText((Join-Path $plan ('servicos\'+$item[1])));$def.XmlText=$xml.Replace('encoding="UTF-8"','encoding="UTF-16"');$def.Settings.Enabled=$false;$def.Principal.UserId='S-1-5-19';$def.Principal.LogonType=5;$def.Principal.RunLevel=0;$def.RegistrationInfo.Description='InvestMoz authorized41caea0f900f B1/B2; LocalService, operating flags OFF.'
foreach($tr in $def.Triggers){if($tr.Type -eq 1){$tr.StartBoundary=[DateTime]::Now.AddMinutes(10).ToString('yyyy-MM-ddTHH:mm:ss')}}
$t=$folder.RegisterTaskDefinition($item[0],$def,6,'S-1-5-19',$null,5,$null);$result.tasks+=@{name=$item[0];user='S-1-5-19';enabled=$false;execution_limit=$t.Definition.Settings.ExecutionTimeLimit}
}
$scanner=$folder.GetTask('InvestMoz ClamAV Loopback Scanner');$scanner.Enabled=$true;$null=$scanner.Run($null)
$limit=[DateTime]::UtcNow.AddSeconds(55);do{$live=@(Get-NetTCPConnection -State Listen -LocalPort 3310 -ErrorAction SilentlyContinue);if($live.Count -gt 0){break};Start-Sleep -Milliseconds 500}while([DateTime]::UtcNow -lt $limit)
if($live.Count -ne 1){throw 'Supervised scanner did not bind3310'}
$proc=Get-CimInstance Win32_Process -Filter ('ProcessId='+$live[0].OwningProcess);if($proc.ExecutablePath -ne (Join-Path $runtime 'clamd.exe')){throw 'Unexpected replacement scanner runtime'};$owner=Invoke-CimMethod -InputObject $proc -MethodName GetOwnerSid;if($owner.Sid -ne 'S-1-5-19'){throw 'Replacement scanner identity differs'};$result.scanner=@{pid=$proc.ProcessId;sid=$owner.Sid;runtime='x64';port=3310}
}
$result.state='updating_official_signatures_worker_disabled';Save-Result
if($savedState.signature_update_task_result -eq 0){$result.signature_update_task_result=0;$result.successful_signature_update_not_repeated=$true}else{$result.signature_update_task_result=Run-OwnTask 'InvestMoz ClamAV Signature Update' 550}
Start-Sleep -Seconds 3
$probeName='InvestMoz Workflow LocalService Preflight 41caea0f900f';$def=$scheduler.NewTask(0);$def.Principal.Id='Service';$def.Principal.UserId='S-1-5-19';$def.Principal.LogonType=5;$def.Principal.RunLevel=0;$def.Actions.Context='Service';$def.Settings.Enabled=$false;$def.Settings.ExecutionTimeLimit='PT3M';$def.Settings.MultipleInstances=2;$def.Settings.DisallowStartIfOnBatteries=$false;$def.Settings.StopIfGoingOnBatteries=$false
$a=$def.Actions.Create(0);$a.Path='C:\laragon\bin\php\php-8.5.11-Win32-vs17-x64\php.exe';$a.Arguments='-d memory_limit=256M tools/portal-workflow-activation-localservice-probe.php';$a.WorkingDirectory=$taskRoot
$priorProbe=Join-Path $taskRoot 'storage\logs\workflow-localservice-preflight.json';if(Test-Path -LiteralPath $priorProbe){[IO.File]::Copy($priorProbe,(Join-Path $taskPrivate ('workflow-activation-localservice-before-'+[DateTime]::UtcNow.ToString('yyyyMMdd_HHmmss')+'.json')),$false)}
$probe=$folder.RegisterTaskDefinition($probeName,$def,2,'S-1-5-19',$null,5,$null)
try{$result.localservice_probe_task_result=Run-OwnTask $probeName 55}finally{$probe.Enabled=$false;if($probe.State -eq 4){$probe.Stop(0)};$folder.DeleteTask($probeName,0)}
$probeProof=Get-Content -LiteralPath (Join-Path $taskRoot 'storage\logs\workflow-localservice-preflight.json') -Raw -Encoding UTF8|ConvertFrom-Json
if($probeProof.result -ne 'passed' -or $probeProof.principal -ne 'S-1-5-19'){throw 'Actual service-account checks failed'}
$result.state='B1_B2_services_verified_waiting_normal_Laragon_restart';Save-Result
# Keep administrative task control alive for the single authorized window; only bounded commands.
$limit=[DateTime]::UtcNow.AddMinutes(45);$lastId=''
while([DateTime]::UtcNow -lt $limit){Start-Sleep -Milliseconds 1000;$commandPath=Join-Path $taskPrivate 'workflow-activation-service-command.json';if(-not (Test-Path -LiteralPath $commandPath)){continue};$c=Get-Content -LiteralPath $commandPath -Raw -Encoding UTF8|ConvertFrom-Json;if($c.id -eq $lastId){continue};$lastId=$c.id;if($c.revision -ne $manifest.revision){throw 'Control command revision differs'}
if($c.action -eq 'close'){break}
if($c.action -ne 'enable-worker'){throw 'Unsupported service command'}
$technical=Get-Content -LiteralPath (Join-Path $taskPrivate 'workflow-activation-technical-ready.json') -Raw|ConvertFrom-Json
$schema=Get-Content -LiteralPath (Join-Path $taskPrivate 'workflow-activation-schema-verified.json') -Raw|ConvertFrom-Json
$queue=Get-Content -LiteralPath (Join-Path $taskPrivate 'workflow-execution-active-queue.json') -Raw|ConvertFrom-Json
if(-not $technical.passed -or $technical.revision -ne $manifest.revision -or -not $schema.resources_installed -or -not $schema.profiles_installed -or $schema.revision -ne $manifest.revision){throw 'Actual technical/schema gate incomplete'}
if($queue.target -ne 'investemoz:5432' -or ([DateTime]::UtcNow-[DateTime]::Parse($queue.at).ToUniversalTime()).TotalSeconds -gt 30 -or $queue.effective_notifications_enabled -or $queue.configured_notification_recipient_count -ne 0){throw 'Immediate queue/notifications gate differs'}
foreach($j in $queue.pending_by_class){if($j.class -notin @('App\Jobs\ScanDocumentForMalware','App\Jobs\RunSimulation','App\Jobs\CompareMneBusiness')){throw 'Unknown or outbound class: preserve queue and keep worker OFF'}}
$result.worker_first_run_result=Run-OwnTask 'InvestMoz Portal Documentary Worker' 185;$result.worker_enabled=$true;$result.state='worker_supervised_operating_flags_OFF';$result.commands+=@{id=$c.id;action=$c.action;at=[DateTime]::UtcNow.ToString('o');result='passed'};Save-Result
}
}catch{$result.state='stopped_worker_not_enabled';$result.error_class=$_.Exception.GetType().FullName;$result.error_line=$_.InvocationInfo.ScriptLineNumber;$result.message=$_.Exception.Message;Save-Result;exit 1}
Save-Result