bootstrap($app); (new LoadConfiguration)->bootstrap($app); $c = config('database.connections.pgsql'); if (($c['url'] ?? null) || $c['database'] !== 'investemoz' || (int) $c['port'] !== 5432) { throw new RuntimeException('Active backup identity configuration rejected.'); } $active = new PDO('pgsql:host='.$c['host'].';port=5432;dbname=investemoz', $c['username'], $c['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $active->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'); $target = $active->query('select current_database() as database, inet_server_port() as port')->fetch(PDO::FETCH_ASSOC); if ($target['database'] !== 'investemoz' || (int) $target['port'] !== 5432) { throw new RuntimeException('Active backup actual identity rejected.'); } $stamp = date('Ymd_His'); $directory = storage_path('app/private/virtual-desk/activation-backup-'.$stamp); mkdir($directory, 0700, true); $dump = $directory.'/database.dump'; $restoreName = 'desk_restore_'.$stamp.'_test'; $bin = 'C:/Program Files/PostgreSQL/17/bin/'; $run = function (array $args, string $password): void { $process = proc_open($args, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, [...getenv(), 'PGPASSWORD' => $password], ['bypass_shell' => true]); if (! is_resource($process)) { throw new RuntimeException('Backup process unavailable.'); } fclose($pipes[0]); stream_get_contents($pipes[1]); fclose($pipes[1]); $error = stream_get_contents($pipes[2]); fclose($pipes[2]); if (proc_close($process) !== 0) { throw new RuntimeException('Backup/restore failed: '.$error); } }; $snapshot = $active->query('select pg_export_snapshot()')->fetchColumn(); $run([$bin.'pg_dump.exe', '-h', $c['host'], '-p', '5432', '-U', $c['username'], '-d', 'investemoz', '-Fc', '--snapshot='.$snapshot, '-f', $dump], $c['password']); $maintenance = new PDO('pgsql:host=127.0.0.1;port=55441;dbname=postgres', 'postgres', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); if ((int) $maintenance->query('select inet_server_port()')->fetchColumn() !== 55441 || ! preg_match('/^desk_restore_\d{8}_\d{6}_test$/', $restoreName)) { throw new RuntimeException('Restore target rejected.'); } $maintenance->exec('CREATE DATABASE "'.$restoreName.'"'); $run([$bin.'pg_restore.exe', '-h', '127.0.0.1', '-p', '55441', '-U', 'postgres', '-d', $restoreName, '--exit-on-error', '--no-owner', '--no-privileges', $dump], ''); $qa = new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$restoreName, 'postgres', '', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); $restored = $qa->query('select current_database() as database, inet_server_port() as port')->fetch(PDO::FETCH_ASSOC); if ($restored['database'] !== $restoreName || (int) $restored['port'] !== 55441) { throw new RuntimeException('Actual restore target rejected.'); } $tables = $active->query("select schemaname,tablename from pg_tables where schemaname not in ('pg_catalog','information_schema') order by schemaname,tablename")->fetchAll(PDO::FETCH_ASSOC); $counts = []; $hashes = []; foreach ($tables as $t) { $name = '"'.str_replace('"', '""', $t['schemaname']).'"."'.str_replace('"', '""', $t['tablename']).'"'; $sql = 'select row_to_json(t)::text from '.$name.' t order by row_to_json(t)::text'; $a = $active->query($sql)->fetchAll(PDO::FETCH_COLUMN); $b = $qa->query($sql)->fetchAll(PDO::FETCH_COLUMN); if ($a !== $b) { throw new RuntimeException('Restored contents differ: '.$name); } $counts[$name] = count($a); $hashes[$name] = hash('sha256', implode("\n", $a)); } $auditEventIds = $active->query('SELECT id FROM audit_events ORDER BY id')->fetchAll(PDO::FETCH_COLUMN); $active->exec('ROLLBACK'); // Preserve exactly the private files referenced by restored document records, plus the runtime key. $files = $directory.'/private-files.zip'; $zip = new ZipArchive; $zip->open($files, ZipArchive::CREATE | ZipArchive::EXCL); $manifest = []; foreach ($qa->query('select storage_key from documents where storage_key is not null')->fetchAll(PDO::FETCH_COLUMN) as $relative) { $path = realpath(storage_path('app/private/'.$relative)); $root = realpath(storage_path('app/private')); if (! $path || ! str_starts_with(strtolower($path), strtolower($root.DIRECTORY_SEPARATOR))) { throw new RuntimeException('Referenced private document missing or outside storage.'); } $name = 'private/'.str_replace('\\', '/', $relative); $zip->addFile($path, $name); $manifest[$name] = hash_file('sha256', $path); } $zip->addFile(base_path('.env'), 'runtime/.env'); $manifest['runtime/.env'] = hash_file('sha256', base_path('.env')); $zip->close(); $read = new ZipArchive; $read->open($files); foreach ($manifest as $name => $hash) { $bytes = $read->getFromName($name); if (! is_string($bytes) || hash('sha256', $bytes) !== $hash) { throw new RuntimeException('Private file restore verification failed.'); } } $read->close(); $proof = ['restore_verified' => true, 'verified_at' => gmdate('c'), 'target' => $target, 'restored_target' => $restored, 'dump_path' => $dump, 'sha256' => hash_file('sha256', $dump), 'table_counts' => $counts, 'table_hashes' => $hashes, 'files_verified' => true, 'files_path' => $files, 'files_sha256' => hash_file('sha256', $files), 'file_hashes' => $manifest, 'audit_event_ids' => $auditEventIds]; file_put_contents(storage_path('app/private/virtual-desk/backup-proof.json'), json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); echo json_encode(['restore_verified' => true, 'tables' => count($tables), 'files' => count($manifest), 'restore_target' => $restored], JSON_THROW_ON_ERROR);