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

112 lines
8.3 KiB
PowerShell

$ErrorActionPreference = 'Stop'
$taskRoot = 'C:\laragon\www\portal-investidor'
$privateRoot = Join-Path $taskRoot 'storage\app\private\investmoz-v2\portal-audit'
$snapshotRoot = Join-Path $privateRoot ('workflow-execution-services-' + [DateTime]::UtcNow.ToString('yyyyMMdd_HHmmss'))
if (Test-Path -LiteralPath $snapshotRoot) { throw 'An existing snapshot must not be replaced.' }
New-Item -ItemType Directory -Path $snapshotRoot | Out-Null
$utf8 = New-Object System.Text.UTF8Encoding($false)
$files = New-Object 'System.Collections.Generic.List[object]'
function Save-PrivateSnapshotFile {
param([string]$Source,[string]$Entry)
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw 'Required service recovery file is absent.' }
if ($Entry.Contains('..') -or $Entry.Contains(':') -or $Entry.StartsWith('/')) { throw 'Snapshot entry refused.' }
$destination = Join-Path $snapshotRoot $Entry.Replace('/', '\')
New-Item -ItemType Directory -Path (Split-Path -Parent $destination) -Force | Out-Null
$before = (Get-FileHash -LiteralPath $Source -Algorithm SHA256).Hash.ToLowerInvariant()
Copy-Item -LiteralPath $Source -Destination $destination
$saved = (Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant()
$after = (Get-FileHash -LiteralPath $Source -Algorithm SHA256).Hash.ToLowerInvariant()
if ($before -ne $saved -or $before -ne $after) { throw 'A live recovery file changed during the read-only snapshot.' }
$files.Add([pscustomobject]@{ source=$Source; entry=$Entry; sha256=$saved })
}
$service = New-Object -ComObject 'Schedule.Service'
$service.Connect()
$taskFolder = $service.GetFolder('\')
$tasks = @()
foreach ($name in @('InvestMoz ClamAV Loopback Scanner','InvestMoz ClamAV Signature Update','InvestMoz Portal Documentary Worker')) {
$existing = $null
try { $existing = $taskFolder.GetTask($name) } catch {
if ($name -ne 'InvestMoz Portal Documentary Worker') { throw 'A required existing ClamAV task cannot be read.' }
if ($_.Exception.HResult -ne -2147024894) { throw 'Cannot distinguish an absent worker task from an inaccessible task.' }
}
if ($null -eq $existing) { $tasks += [pscustomobject]@{name=$name;exists=$false}; continue }
$entry = 'tasks/' + $name.Replace(' ','-') + '-before.xml'
$xmlPath = Join-Path $snapshotRoot $entry.Replace('/', '\')
New-Item -ItemType Directory -Path (Split-Path -Parent $xmlPath) -Force | Out-Null
[System.IO.File]::WriteAllText($xmlPath,$existing.Xml.Replace('encoding="UTF-16"','encoding="UTF-8"'),$utf8)
[xml]$parsed = [System.IO.File]::ReadAllText($xmlPath)
$tasks += [pscustomobject]@{
name=$name;exists=$true;state=$existing.State;enabled=$existing.Enabled
entry=$entry;sha256=(Get-FileHash -LiteralPath $xmlPath -Algorithm SHA256).Hash.ToLowerInvariant()
security_descriptor=$existing.GetSecurityDescriptor(7);xml_roundtrip=$null -ne $parsed.Task
}
$files.Add([pscustomobject]@{source=$null;entry=$entry;sha256=(Get-FileHash -LiteralPath $xmlPath -Algorithm SHA256).Hash.ToLowerInvariant()})
}
$clamRoot = Join-Path $privateRoot 'clamav'
$clamRuntime = Join-Path $clamRoot 'runtime\clamav-1.5.4.win.x64'
Save-PrivateSnapshotFile (Join-Path $clamRoot 'clamd.conf') 'scanner/clamd.conf'
Save-PrivateSnapshotFile (Join-Path $clamRoot 'freshclam.conf') 'scanner/freshclam.conf'
$clamConfigText = [System.IO.File]::ReadAllText((Join-Path $clamRoot 'clamd.conf'))
$databaseMatch = [regex]::Match($clamConfigText,'(?m)^\s*DatabaseDirectory\s+([^\r\n#]+)')
if (-not $databaseMatch.Success) { throw 'The actual scanner database directory is not explicit.' }
$signatureRoot = $databaseMatch.Groups[1].Value.Trim().Trim('"')
if (-not [System.IO.Path]::IsPathRooted($signatureRoot)) { throw 'Scanner signature directory must be absolute.' }
foreach ($file in Get-ChildItem -LiteralPath $clamRuntime -File) {
Save-PrivateSnapshotFile $file.FullName ('scanner/runtime/' + $file.Name)
}
$signatureFiles = @(Get-ChildItem -LiteralPath $signatureRoot -File | Where-Object { $_.Extension -in @('.cvd','.cld','.dat') })
if ($signatureFiles.Count -lt 3) { throw 'Scanner signature snapshot is incomplete.' }
foreach ($file in $signatureFiles) { Save-PrivateSnapshotFile $file.FullName ('scanner/signatures/' + $file.Name) }
Save-PrivateSnapshotFile 'C:\laragon\bin\php\php-8.5.11-Win32-vs17-x64\php.ini' 'php/php.ini'
Save-PrivateSnapshotFile 'C:\laragon\etc\apache2\mod_php.conf' 'apache/mod_php.conf'
# Read DACL/owner metadata only. No Set-Acl, registration, stopping or service command.
$aclPaths = @(
'C:\laragon','C:\laragon\www',$taskRoot,(Join-Path $taskRoot '.env'),
(Join-Path $taskRoot 'app'),(Join-Path $taskRoot 'vendor'),(Join-Path $taskRoot 'config'),
(Join-Path $taskRoot 'routes'),(Join-Path $taskRoot 'resources'),(Join-Path $taskRoot 'database'),
(Join-Path $taskRoot 'public'),(Join-Path $taskRoot 'public\build'),
(Join-Path $taskRoot 'bootstrap'),(Join-Path $taskRoot 'bootstrap\cache'),
(Join-Path $taskRoot 'storage'),(Join-Path $taskRoot 'storage\app'),
(Join-Path $taskRoot 'storage\app\private'),(Join-Path $taskRoot 'storage\framework'),
(Join-Path $taskRoot 'storage\logs'),(Join-Path $taskRoot 'storage\app\private\documents'),
(Join-Path $taskRoot 'storage\app\private\quarantine'),
(Join-Path $taskRoot 'storage\app\private\investmoz-v2'),$privateRoot,
$clamRoot,$clamRuntime,$signatureRoot,(Join-Path $clamRoot 'clamd.conf'),(Join-Path $clamRoot 'freshclam.conf'),
'C:\laragon\bin\php\php-8.5.11-Win32-vs17-x64','C:\laragon\bin\php\php-8.5.11-Win32-vs17-x64\php.ini',
'C:\laragon\etc\apache2\mod_php.conf','C:\laragon\etc\apache2\sites-enabled\portal-investidor.test.conf'
)
foreach ($dir in @('storage\framework','storage\app\private\documents','storage\app\private\quarantine')) {
$path = Join-Path $taskRoot $dir
if (Test-Path -LiteralPath $path -PathType Container) {
$aclPaths += @(Get-ChildItem -LiteralPath $path -Directory -Recurse | ForEach-Object { $_.FullName })
}
}
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $taskRoot 'storage\app\private') -File -Filter '*scan*') { $aclPaths += $file.FullName }
$acls = @()
foreach ($path in @($aclPaths | Sort-Object -Unique)) {
if (-not (Test-Path -LiteralPath $path)) { $acls += [pscustomobject]@{path=$path;exists=$false};continue }
$acl = Get-Acl -LiteralPath $path
$sddl = $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
$check = New-Object System.Security.AccessControl.RawSecurityDescriptor($sddl)
if ($check.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All) -ne $sddl) { throw 'DACL metadata cannot round-trip.' }
$acls += [pscustomobject]@{path=$path;exists=$true;owner=$acl.Owner;sddl=$sddl;inheritance_protected=$acl.AreAccessRulesProtected;roundtrip=$true}
}
$aclFile = Join-Path $snapshotRoot 'acl-before.json'
[System.IO.File]::WriteAllText($aclFile,($acls | ConvertTo-Json -Depth 7),$utf8)
$files.Add([pscustomobject]@{source=$null;entry='acl-before.json';sha256=(Get-FileHash -LiteralPath $aclFile -Algorithm SHA256).Hash.ToLowerInvariant()})
$proof = [pscustomobject]@{
at=[DateTime]::UtcNow.ToString('o');directory=$snapshotRoot;files=$files;tasks=$tasks
acl_objects=@($acls | Where-Object { $_.exists }).Count;acl_metadata_roundtrip=$true
acl_scope='Explicit targeted roots, ancestors, runtime/config and operational document/cache directory metadata; extend exact affected descendant exports before any recursive future ACL change.'
acl_restore_applied_on_destination=$false;signature_files=$signatureFiles.Count;signature_directory=$signatureRoot
listeners=@(Get-NetTCPConnection -State Listen | Where-Object { $_.LocalPort -in @(80,443,3310) } | Select-Object LocalAddress,LocalPort,OwningProcess)
active_services_changes=0;registered_tasks=0;active_acl_changes=0
}
[System.IO.File]::WriteAllText((Join-Path $snapshotRoot 'snapshot.json'),($proof | ConvertTo-Json -Depth 10),$utf8)
[System.IO.File]::WriteAllText((Join-Path $privateRoot 'workflow-execution-services-latest.json'),($proof | ConvertTo-Json -Depth 10),$utf8)
@{result='read_only_services_snapshot';files=$files.Count;signature_files=$signatureFiles.Count;acl_objects=$proof.acl_objects;existing_tasks=@($tasks | Where-Object { $_.exists }).Count;services_changed=0;acl_changes=0} | ConvertTo-Json -Compress