Files
Investidor-V2/tools/portal-stability-active-preservation.php
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

34 lines
4.8 KiB
PHP

<?php
// No service providers; all queries on the destination are READ ONLY.
use Illuminate\Foundation\Bootstrap\LoadConfiguration;
use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
use Illuminate\Encryption\Encrypter;
$root=dirname(__DIR__);require $root.'/vendor/autoload.php';ini_set('zend.exception_ignore_args','1');
$app=require $root.'/bootstrap/app.php';(new LoadEnvironmentVariables)->bootstrap($app);(new LoadConfiguration)->bootstrap($app);
$dir=$root.'/storage/app/private/investmoz-v2/portal-audit';
$link=json_decode(file_get_contents($dir.'/country-https-backup-latest.json'),true,512,JSON_THROW_ON_ERROR);
$proof=json_decode(file_get_contents($link['path']),true,512,JSON_THROW_ON_ERROR);
$c=config('database.connections.'.config('database.default'));
throw_unless($c['driver']==='pgsql'&&empty($c['url'])&&(int)$c['port']===5432&&$c['database']==='investemoz',RuntimeException::class,'Exact read-only destination required.');
throw_if(config('portal.organization_country_enabled'),RuntimeException::class,'COUNTRY02 must remain disabled.');
$zip=new ZipArchive;throw_unless($zip->open($proof['files_path'])===true,RuntimeException::class,'Corresponding backup unavailable.');
$runtime=json_decode($zip->getFromName('runtime/effective-runtime.json'),true,512,JSON_THROW_ON_ERROR);$zip->close();
throw_unless($runtime['app_key']===config('app.key')&&$runtime['cipher']===config('app.cipher'),RuntimeException::class,'Destination encryption differs.');
$key=$runtime['app_key'];$key=str_starts_with($key,'base64:')?base64_decode(substr($key,7),true):$key;$crypt=new Encrypter($key,$runtime['cipher']);
$pdo=new PDO('pgsql:host='.$c['host'].';port=5432;dbname=investemoz',$c['username'],$c['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]);$pdo->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
$identity=$pdo->query("SELECT current_database() AS database,inet_server_port() AS port,current_setting('transaction_read_only') AS read_only")->fetch(PDO::FETCH_ASSOC);
throw_unless($identity['database']==='investemoz'&&(int)$identity['port']===5432&&$identity['read_only']==='on',RuntimeException::class,'Read-only target required.');
$tables=[];$changed=[];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 $t){
$name='"'.str_replace('"','""',$t['schemaname']).'"."'.str_replace('"','""',$t['tablename']).'"';$rows=$pdo->query('SELECT row_to_json(t)::text FROM '.$name.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN);$state=['count'=>count($rows),'sha256'=>hash('sha256',implode("\n",$rows))];$tables[$name]=$state;if(($proof['tables'][$name]??null)!==$state)$changed[]=$name;
}
throw_unless(array_keys($tables)===array_keys($proof['tables'])&&array_diff($changed,['"public"."sessions"','"public"."cache"','"public"."cache_locks"'])===[],RuntimeException::class,'Destination business data changed.');
$seq=$pdo->query("SELECT * FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC);throw_unless($seq===$proof['sequences'],RuntimeException::class,'Destination sequences changed.');
$counts=[];foreach(['users','organizations','memberships','migrations','audit_events'] as $table)$counts[$table]=(int)$pdo->query('SELECT count(*) FROM "'.$table.'"')->fetchColumn();
$mfa=0;foreach($pdo->query('SELECT two_factor_secret FROM users WHERE two_factor_secret IS NOT NULL')->fetchAll(PDO::FETCH_COLUMN) as $envelope){throw_unless(is_string($crypt->decrypt($envelope,false)),RuntimeException::class,'Original encrypted MFA unreadable.');$mfa++;}
throw_unless((int)$pdo->query("SELECT count(*) FROM migrations WHERE migration='2026_10_10_050000_extend_organization_country'")->fetchColumn()===0,RuntimeException::class,'Unexpected COUNTRY02 installation.');$pdo->exec('ROLLBACK');
$previous=json_decode(file_get_contents($dir.'/country-https-active-stage-latest.json'),true,512,JSON_THROW_ON_ERROR);
foreach($previous['backup_files'] as $file=>$entry)throw_unless(hash_file('sha256',$file)===$entry['sha256'],RuntimeException::class,'Active code/config differs from reverted snapshot.');
throw_unless(hash_file('sha256',$root.'/public/build/manifest.json')===$proof['active_manifest_sha256'],RuntimeException::class,'Active bundle differs.');
$result=['result'=>'passed','at'=>gmdate('c'),'target'=>$identity,'country_enabled'=>false,'tables'=>count($tables),'counts'=>$counts,'business_rows_preserved'=>true,'sequences_preserved'=>count($seq),'changed_volatile_tables'=>$changed,'original_MFA_envelopes_readable'=>$mfa,'key_cipher_code_config_bundle_preserved'=>true,'installed_050000'=>false,'active_writes'=>0];
file_put_contents($dir.'/portal-stability-active-preservation.json',json_encode($result,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode($result).PHP_EOL;