69 lines
4.2 KiB
PHP
69 lines
4.2 KiB
PHP
<?php
|
|
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
// Emergency read-only preservation, explicitly NOT a verified database backup.
|
|
require dirname(__DIR__).'/vendor/autoload.php';
|
|
require __DIR__.'/portal-qa-environment.php';
|
|
$target = portalQaEnvironment();
|
|
$app = require dirname(__DIR__).'/bootstrap/app.php';
|
|
$app->make(Kernel::class)->bootstrap();
|
|
portalQaAssertTarget($target);
|
|
$pdo = DB::connection()->getPdo();
|
|
$directory = storage_path('app/private/investmoz-v2/portal-qa-preservation/'.gmdate('Ymd_His'));
|
|
try {
|
|
$pdo->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
|
$identity = $pdo->query("SELECT current_database() AS database, inet_server_port() AS port, current_setting('data_directory') AS data_directory")->fetch(PDO::FETCH_ASSOC);
|
|
if ((int) $identity['port'] !== 55441 || $identity['database'] !== $target['database']) {
|
|
throw new RuntimeException('QA identity changed.');
|
|
}
|
|
mkdir($directory, 0700, true);
|
|
$tables = [];
|
|
$quote = fn (string $name): string => '"'.str_replace('"', '""', $name).'"';
|
|
foreach ($pdo->query("SELECT schemaname,tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,tablename")->fetchAll(PDO::FETCH_ASSOC) as $table) {
|
|
$name = $quote($table['schemaname']).'.'.$quote($table['tablename']);
|
|
$file = sprintf('%04d.ndjson', count($tables) + 1);
|
|
$stream = fopen($directory.'/'.$file, 'xb');
|
|
$statement = $pdo->query('SELECT row_to_json(t)::text FROM '.$name.' t ORDER BY row_to_json(t)::text');
|
|
$rows = 0;
|
|
while (($row = $statement->fetchColumn()) !== false) {
|
|
if (fwrite($stream, $row."\n") !== strlen($row) + 1) {
|
|
throw new RuntimeException('Private preservation write incomplete.');
|
|
}
|
|
$rows++;
|
|
}
|
|
fclose($stream);
|
|
$tables[$name] = ['file' => $file, 'rows' => $rows, 'sha256' => hash_file('sha256', $directory.'/'.$file)];
|
|
}
|
|
$sequences = [];
|
|
foreach ($pdo->query("SELECT schemaname,sequencename FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC) as $sequence) {
|
|
$name = $quote($sequence['schemaname']).'.'.$quote($sequence['sequencename']);
|
|
$sequences[$name] = $pdo->query('SELECT last_value,is_called FROM '.$name)->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
$missing = [];
|
|
foreach ($pdo->query("SELECT oid,relname,pg_relation_filepath(oid) AS path FROM pg_class WHERE relisshared AND relkind IN ('r','i','t') ORDER BY oid")->fetchAll(PDO::FETCH_ASSOC) as $relation) {
|
|
if (! is_file($identity['data_directory'].'/'.$relation['path'])) {
|
|
$missing[] = $relation;
|
|
}
|
|
}
|
|
$pdo->exec('ROLLBACK');
|
|
$proof = ['created_at' => gmdate('c'), 'target' => $identity, 'transaction_read_only' => true,
|
|
'tables' => $tables, 'sequences' => $sequences, 'missing_shared_catalog_files' => $missing,
|
|
'active_access' => false, 'database_writes' => 0, 'restore_verified' => false,
|
|
'scope' => 'Readable table bytes and sequence values preserved privately; schema/roles/catalog integrity and restoration NOT certified. Sequence values are not MVCC snapshots.'];
|
|
file_put_contents($directory.'/manifest.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
$receipt = ['created_at' => $proof['created_at'], 'target' => ['database' => $identity['database'], 'port' => $identity['port']],
|
|
'directory' => $directory, 'tables' => count($tables), 'rows' => array_sum(array_column($tables, 'rows')),
|
|
'sequences' => count($sequences), 'missing_shared_files' => count($missing),
|
|
'transaction_read_only' => true, 'database_writes' => 0, 'active_access' => false, 'restore_verified' => false];
|
|
file_put_contents(storage_path('app/private/investmoz-v2/portal-audit/qa-readable-preservation.json'), json_encode($receipt, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
|
echo json_encode($receipt, JSON_THROW_ON_ERROR).PHP_EOL;
|
|
} catch (Throwable $error) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
fwrite(STDERR, 'QA read-only preservation failed; no verified backup receipt produced.'.PHP_EOL);
|
|
exit(1);
|
|
}
|