make(Kernel::class)->bootstrap(); $active = DB::connection(); $identity = $active->selectOne('select current_database() as database, inet_server_port() as port'); if ($identity->database !== 'investemoz' || (int) $identity->port !== 5432) { throw new RuntimeException('Unexpected active backup target.'); } $directory = storage_path('app/private/partnership-exchange/backup-'.date('Ymd-His')); mkdir($directory, 0700, true); $dump = $directory.'/database.dump'; $qaName = 'partnership_restore_'.date('Ymd_His').'_test'; $bin = 'C:/Program Files/PostgreSQL/17/bin/'; $run = function (array $command, array $environment = []) { $process = proc_open($command, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, null, [...getenv(), ...$environment]); if (! is_resource($process)) { throw new RuntimeException('Backup process unavailable.'); } fclose($pipes[0]); $output = stream_get_contents($pipes[1]); fclose($pipes[1]); $errors = stream_get_contents($pipes[2]); fclose($pipes[2]); if (proc_close($process) !== 0) { throw new RuntimeException('Backup/restore command failed: '.$errors); } return $output; }; $proof = $active->transaction(function () use ($active, $run, $bin, $qaName, $dump, $identity) { $active->statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ ONLY'); $snapshot = $active->selectOne('select pg_export_snapshot() as snapshot')->snapshot; $run([$bin.'pg_dump.exe', '-h', $active->getConfig('host'), '-p', '5432', '-U', $active->getConfig('username'), '-d', 'investemoz', '-Fc', '--snapshot='.$snapshot, '-f', $dump], ['PGPASSWORD' => $active->getConfig('password')]); $run([$bin.'createdb.exe', '-h', '127.0.0.1', '-p', '55441', '-U', 'postgres', $qaName], ['PGPASSWORD' => '']); $run([$bin.'pg_restore.exe', '-h', '127.0.0.1', '-p', '55441', '-U', 'postgres', '-d', $qaName, '--exit-on-error', '--no-owner', '--no-privileges', $dump], ['PGPASSWORD' => '']); config(['database.connections.partnership_restore' => [...config('database.connections.pgsql'), 'host' => '127.0.0.1', 'port' => 55441, 'database' => $qaName, 'username' => 'postgres', 'password' => '', 'url' => null]]); $restore = DB::connection('partnership_restore'); $restored = $restore->selectOne('select current_database() as database, inet_server_port() as port'); if ($restored->database !== $qaName || ! str_ends_with($qaName, '_test') || (int) $restored->port !== 55441) { throw new RuntimeException('QA identity rejected.'); } $counts = []; $hashes = []; foreach ($active->select("select tablename from pg_tables where schemaname='public' order by tablename") as $table) { $name = $table->tablename; $quoted = '"'.str_replace('"', '""', $name).'"'; $rows = fn ($connection) => array_map(fn ($row) => $row->value, $connection->select('select row_to_json(t)::text as value from public.'.$quoted.' as t order by row_to_json(t)::text')); $a = $rows($active); $b = $rows($restore); if ($a !== $b) { throw new RuntimeException('Restored contents differ: '.$name); } $counts[$name] = count($a); $hashes[$name] = hash('sha256', implode("\n", $a)); } return ['restore_verified' => true, 'verified_at' => now()->toIso8601String(), 'target' => $identity, 'restored_target' => $restored, 'dump_path' => $dump, 'sha256' => hash_file('sha256', $dump), 'table_counts' => $counts, 'table_hashes' => $hashes]; }); file_put_contents(storage_path('app/private/partnership-exchange/backup-proof.json'), json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); $audit = $active->table('audit_events')->where('source_system', 'documented-relations-repair')->get(['actor_user_id', 'created_at', 'action']); echo json_encode(['restore_verified' => true, 'tables' => count($proof['table_counts']), 'project_locations' => $proof['table_counts']['project_locations'], 'restored_target' => $proof['restored_target'], 'relationship_audit' => $audit], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL;