Files
Investidor-V2/tools/territory-demarcation-recovery-qa.php

65 lines
4.9 KiB
PHP

<?php
// Rehearse recovery with the existing importer. Never accepts an active database.
require dirname(__DIR__).'/vendor/autoload.php';
$app = require dirname(__DIR__).'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$target = Illuminate\Support\Facades\DB::selectOne('select current_database() as database,inet_server_port() as port');
throw_unless($target->database === 'simulator_fiscal_delta_20261021_test' && (int) $target->port === 55441
&& config('database.connections.pgsql.host') === '127.0.0.1', RuntimeException::class, 'Isolated QA only.');
$app->detectEnvironment(fn () => 'testing');
config(['geometry-import.enabled' => true, 'portal.managers' => ['qa-content-promoter@example.test'],
'services.malware.scanner' => 'clamd', 'services.malware.socket' => null,
'services.malware.host' => '127.0.0.1', 'services.malware.port' => 3310]);
$actor = App\Models\User::where('email', 'qa-content-promoter@example.test')->firstOrFail();
$directory = config('geometry-import.directory');
$out = storage_path('app/private/territory-demarcation-recovery');
Illuminate\Support\Facades\File::ensureDirectoryExists($out, 0700);
$manifest = [];
foreach (glob($directory.'/moz-adm*-ine-*') as $file) {
$manifest[] = ['file' => basename($file), 'bytes' => filesize($file), 'sha256' => hash_file('sha256', $file)];
}
file_put_contents($out.'/source-inventory.json', json_encode($manifest, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
$scanner = app(App\Services\Malware\MalwareScanner::class);
$health = $scanner->health();
throw_unless($health['available'], RuntimeException::class, 'Current scanner unavailable.');
$scans = [];
foreach ([1, 2, 3] as $level) {
$file = $directory.'/moz-adm'.$level.'-ine-20190607.geojson';
// Scope the stream budget to this retained GIS original in this process only.
config(['services.malware.max_file_bytes' => filesize($file), 'services.malware.max_stream_bytes' => filesize($file)]);
$scan = $scanner->scan($file, filesize($file), storage_path('app/private'));
$scan['signatures_at'] = $scan['signatures_at']?->format(DATE_ATOM);
if ($level === 3 && $scan['reason_code'] === 'scanner_decompression_limit') {
$binary = getenv('GIS_CLAMSCAN_BINARY');
$database = getenv('GIS_CLAMSCAN_DATABASE');
throw_unless(is_file($binary ?: '') && is_dir($database ?: ''), RuntimeException::class, 'GIS scanner paths required.');
$process = new Symfony\Component\Process\Process([$binary, '--database='.$database, '--max-filesize=40M', '--max-scansize=100M', '--no-summary', $file]);
$process->setTimeout(120)->mustRun();
throw_unless(str_ends_with(trim($process->getOutput()), ': OK'), RuntimeException::class, 'GIS original scan incomplete.');
$version = new Symfony\Component\Process\Process([$binary, '--database='.$database, '--version']);
$version->mustRun();
$parts = explode('/', trim($version->getOutput()));
$signatureTime = strtotime(end($parts));
throw_unless($signatureTime && time() - $signatureTime < 48 * 3600, RuntimeException::class, 'Current GIS signatures required.');
$scan = ['result' => 'clean', 'engine' => 'ClamAV', 'engine_version' => trim($version->getOutput()),
'signatures_at' => gmdate(DATE_ATOM, $signatureTime), 'scope' => 'retained GIS original only',
'original_sha256' => hash_file('sha256', $file), 'stream_attempt' => $scan, 'exit_code' => $process->getExitCode()];
}
$scans[] = ['file' => basename($file), 'sha256' => hash_file('sha256', $file), 'scan' => $scan];
echo json_encode(['level' => $level, 'scan' => $scan]).PHP_EOL;
throw_unless($scan['result'] === 'clean', RuntimeException::class, 'Original scan incomplete.');
}
file_put_contents($directory.'/geometry-source-scans.json', json_encode(['captured_at' => gmdate('c'), 'files' => $scans], JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
$service = app(App\Services\TerritoryGeometryImport::class);
foreach ([1, 2, 3] as $level) {
$plan = $service->preview($actor, $level);
file_put_contents($out.'/preview-'.$level.'.json', json_encode($plan, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode(['level' => $level, 'resolved' => $plan['resolved'], 'pending' => $plan['pending']]).PHP_EOL;
if (! in_array('--apply', $argv, true)) break; // Lower levels require imported parent correspondences.
$result = $service->apply($actor, ['level' => $level, 'hash' => $plan['hash'], 'idempotency_key' => (string) Illuminate\Support\Str::uuid(),
'reason' => 'Recuperação QA da demarcação anteriormente demonstrada, com originais e correspondências aceites.', 'confirm' => true]);
file_put_contents($out.'/result-'.$level.'.json', json_encode($result, JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode(['level' => $level, 'inserted' => $result['inserted'], 'reused' => $result['reused']]).PHP_EOL;
}