54 lines
4.9 KiB
PHP
54 lines
4.9 KiB
PHP
<?php
|
|
|
|
// Read-only decision inventory. No migration, grants, backup or service control.
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
$host = realpath($argv[1] ?? '');
|
|
$candidate = realpath($argv[2] ?? '');
|
|
if (! $host || ! $candidate) {
|
|
fwrite(STDERR, "Explicit host and candidate required.\n");
|
|
exit(1);
|
|
}
|
|
require $host.'/vendor/autoload.php';
|
|
$app = require $host.'/bootstrap/app.php';
|
|
$app->make(Kernel::class)->bootstrap();
|
|
$db = null;
|
|
try {
|
|
$db = DB::connection();
|
|
if ($db->getDriverName() !== 'pgsql') {
|
|
throw new RuntimeException('PostgreSQL expected');
|
|
}
|
|
$db->beginTransaction();
|
|
$db->statement('SET TRANSACTION READ ONLY');
|
|
$identity = $db->selectOne('select current_database() as database, inet_server_addr()::text as address, inet_server_port() as port');
|
|
$tables = array_column($db->select("select tablename from pg_tables where schemaname='public' order by tablename"), 'tablename');
|
|
$required = ['users', 'tenants', 'companies', 'units', 'company_memberships', 'permission_grants', 'identity_audit'];
|
|
$budget = ['cb_centres', 'cb_origins', 'cb_budgets', 'cb_budget_entries', 'cb_budget_consumptions', 'cb_budget_reinforcements', 'cb_audit'];
|
|
$internal = ['ir_requests', 'ir_steps', 'ir_events', 'ir_commands', 'ir_policies', 'ir_policy_versions', 'sgc_installations'];
|
|
$column = $db->selectOne("select character_maximum_length from information_schema.columns where table_schema='public' and table_name='permission_grants' and column_name='permission'");
|
|
$constraint = $db->selectOne("select pg_get_constraintdef(oid) as definition from pg_constraint where conrelid='permission_grants'::regclass and conname='permission_grants_scope_check'");
|
|
$privileges = $db->selectOne("select has_schema_privilege(current_user,'public','CREATE') as can_create,pg_has_role(current_user,(select relowner from pg_class where oid='permission_grants'::regclass),'USAGE') as can_alter_permissions");
|
|
$preserve = [];
|
|
foreach (['permission_grants', 'company_memberships', 'identity_audit'] as $table) {
|
|
$rows = $db->select('select to_jsonb(t)::text as value from "'.$table.'" t order by to_jsonb(t)::text');
|
|
$preserve[$table] = ['rows' => count($rows), 'sha256' => hash('sha256', json_encode(array_column($rows, 'value'), JSON_THROW_ON_ERROR))];
|
|
}
|
|
$counts = ['companies' => $db->table('companies')->count(), 'units' => $db->table('units')->count(), 'company_admin_grants' => $db->table('permission_grants')->where('permission', 'access.manage')->whereNull('unit_id')->count()];
|
|
$roles = $db->selectOne("select current_user as runtime_role,(select pg_get_userbyid(relowner) from pg_class where oid='permission_grants'::regclass) as permissions_owner");
|
|
$ownerCapabilities = $db->selectOne("select rolcanlogin as can_login,has_schema_privilege(rolname,'public','CREATE') as can_create_public from pg_roles where rolname=?", [$roles->permissions_owner]);
|
|
$state = ['checked_at' => gmdate('c'), 'host' => $host, 'app_url' => config('app.url'), 'environment' => $app->environment(), 'php' => PHP_VERSION, 'database' => ['driver' => $db->getDriverName(), 'configured_host' => $db->getConfig('host'), 'configured_port' => $db->getConfig('port'), 'configured_database' => $db->getDatabaseName(), 'actual' => $identity],
|
|
'schema' => ['public_table_count' => count($tables), 'missing_foundation' => array_values(array_diff($required, $tables)), 'existing_budget_tables' => array_values(array_intersect($budget, $tables)), 'existing_integration_tables' => array_values(array_intersect($internal, $tables)), 'budget_ui_support_tables_present' => array_values(array_intersect(['cb_ui_commands', 'cb_consumption_sources'], $tables)), 'permission_column_length' => $column?->character_maximum_length, 'scope_constraint_present' => $constraint !== null, 'scope_constraint_sha256' => $constraint ? hash('sha256', $constraint->definition) : null, 'ddl_privileges' => $privileges],
|
|
'database_roles' => $roles, 'config_cached' => $app->configurationIsCached(),
|
|
'owner_capabilities' => $ownerCapabilities,
|
|
'preservation_baseline' => $preserve, 'organisation_counts' => $counts, 'purchasing_pointer' => json_decode(file_get_contents($host.'/packages/purchasing/current.json'), true, 16, JSON_THROW_ON_ERROR), 'pointer_sha256' => hash_file('sha256', $host.'/packages/purchasing/current.json'),
|
|
'bundle_exists' => is_dir($host.'/packages/internal-requisitions'), 'maintenance_marker_present' => is_file($host.'/storage/framework/down'), 'operational_config_file_present' => is_file($host.'/config/purchasing-operational.php'), 'read_only_transaction' => true, 'installed' => false];
|
|
$db->rollBack();
|
|
echo json_encode($state, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)."\n";
|
|
} catch (Throwable $error) {
|
|
if ($db && $db->transactionLevel() > 0) {
|
|
$db->rollBack();
|
|
}fwrite(STDERR, 'Read-only inventory failed: '.get_class($error)."\n");
|
|
exit(1);
|
|
}
|