Files
wevads-platform/scripts/api_vault-guard.php
2026-02-26 04:53:11 +01:00

241 lines
8.9 KiB
PHP
Executable File

<?php
/**
* ╔═══════════════════════════════════════════════════════════════╗
* ║ 🔒 VAULT INTEGRITY GUARD — Anti-Régression Automatique ║
* ║ Vérifie vault gold vs live, auto-restore si troncation ║
* ║ Cron: toutes les 6h | API: ?action=check|restore|status ║
* ╚═══════════════════════════════════════════════════════════════╝
*/
header('Content-Type: application/json');
error_reporting(0);
define('VAULT_DIR', '/opt/wevads/vault');
define('LOG_FILE', '/var/log/vault-guard.log');
try {
$pdo = new PDO('pgsql:host=localhost;dbname=adx_system', 'admin', 'admin123');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(Exception $e) {
echo json_encode(['error'=>'DB: '.$e->getMessage()]); exit;
}
// Init table
$pdo->exec("CREATE TABLE IF NOT EXISTS admin.vault_guard_log (
id SERIAL PRIMARY KEY,
file_path TEXT,
vault_path TEXT,
issue_type VARCHAR(50),
vault_size INT,
live_size INT,
action_taken VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW()
)");
function vaultToLive($vaultFile) {
// __opt__wevads-arsenal__public__api__sentinel-brain.php -> /opt/wevads-arsenal/public/api/sentinel-brain.php
$name = basename($vaultFile, '.gold');
$name = preg_replace('/\.gold$/', '', $name);
$path = str_replace('__', '/', $name);
if ($path[0] !== '/') $path = '/' . $path;
return $path;
}
function checkIntegrity() {
$vault = VAULT_DIR;
$results = ['checked'=>0, 'healthy'=>0, 'truncated'=>0, 'missing'=>0, 'restored'=>0, 'details'=>[]];
foreach(glob("$vault/*") as $vf) {
if (is_dir($vf)) continue;
$bn = basename($vf);
if (strpos($bn, '.gold') !== false) {
$livePath = vaultToLive(str_replace('.gold', '', $bn));
} else {
$livePath = vaultToLive($bn);
}
if (!file_exists($livePath)) {
$results['missing']++;
$results['details'][] = ['file'=>$livePath, 'status'=>'MISSING', 'vault_size'=>filesize($vf)];
continue;
}
$results['checked']++;
$vaultSize = filesize($vf);
$liveSize = filesize($livePath);
// Check for truncation (live is significantly smaller than vault)
if ($liveSize < $vaultSize * 0.9) {
$results['truncated']++;
$results['details'][] = [
'file'=>$livePath, 'status'=>'TRUNCATED',
'vault_size'=>$vaultSize, 'live_size'=>$liveSize,
'loss_pct'=>round((1 - $liveSize/$vaultSize) * 100, 1)
];
}
// Check for PHP syntax errors in live file
elseif (pathinfo($livePath, PATHINFO_EXTENSION) === 'php') {
$check = shell_exec("php -l " . escapeshellarg($livePath) . " 2>&1");
if (strpos($check, 'No syntax errors') === false) {
$results['truncated']++;
$results['details'][] = [
'file'=>$livePath, 'status'=>'SYNTAX_ERROR',
'vault_size'=>$vaultSize, 'live_size'=>$liveSize,
'error'=>trim($check)
];
} else {
$results['healthy']++;
}
} else {
$results['healthy']++;
}
}
return $results;
}
function autoRestore($pdo) {
$vault = VAULT_DIR;
$fixed = 0;
$details = [];
foreach(glob("$vault/*") as $vf) {
if (is_dir($vf)) continue;
$bn = basename($vf);
if (strpos($bn, '.gold') !== false) {
$livePath = vaultToLive(str_replace('.gold', '', $bn));
} else {
$livePath = vaultToLive($bn);
}
if (!file_exists($livePath)) continue;
$vaultSize = filesize($vf);
$liveSize = filesize($livePath);
$needRestore = false;
$issue = '';
// Truncation check
if ($liveSize < $vaultSize * 0.9) {
$needRestore = true;
$issue = 'truncated';
}
// PHP syntax check
elseif (pathinfo($livePath, PATHINFO_EXTENSION) === 'php') {
$check = shell_exec("php -l " . escapeshellarg($livePath) . " 2>&1");
if (strpos($check, 'No syntax errors') === false) {
$needRestore = true;
$issue = 'syntax_error';
}
}
if ($needRestore) {
// Backup broken file
copy($livePath, $livePath . '.broken_' . time());
// Restore from vault
copy($vf, $livePath);
// Verify fix
$newCheck = shell_exec("php -l " . escapeshellarg($livePath) . " 2>&1");
$success = strpos($newCheck, 'No syntax errors') !== false || pathinfo($livePath, PATHINFO_EXTENSION) !== 'php';
if ($success) {
$fixed++;
$details[] = ['file'=>$livePath, 'issue'=>$issue, 'action'=>'RESTORED', 'vault_size'=>$vaultSize, 'was_size'=>$liveSize];
// Log to DB
$pdo->prepare("INSERT INTO admin.vault_guard_log(file_path,vault_path,issue_type,vault_size,live_size,action_taken) VALUES(?,?,?,?,?,?)")
->execute([$livePath, $vf, $issue, $vaultSize, $liveSize, 'auto_restored']);
// Log to file
file_put_contents(LOG_FILE, date('Y-m-d H:i:s') . " RESTORED $livePath ($issue: $liveSize→$vaultSize)\n", FILE_APPEND);
}
}
}
return ['restored'=>$fixed, 'details'=>$details];
}
function snapshotToVault() {
// Snapshot critical files TO vault (for files not yet vaulted)
$critical = [
'/opt/wevads-arsenal/public/api/sentinel-brain.php',
'/opt/wevads-arsenal/public/api/hamid-engine.php',
'/opt/wevads-arsenal/public/api/hamid-chef.php',
'/opt/wevads-arsenal/public/api/hamid-ia.php',
'/opt/wevads-arsenal/public/api/weval-mind-core.php',
'/opt/wevads-arsenal/public/api/brain-unified-send.php',
'/opt/wevads-arsenal/public/api/warmup-engine.php',
'/opt/wevads-arsenal/public/command-center.html',
'/opt/wevads/public/js/brain-inject.js',
'/opt/wevads/public/brain-inject-api.php',
'/opt/wevads/public/brain-unified-send.php',
];
$saved = 0;
foreach($critical as $f) {
if (!file_exists($f)) continue;
// Only snapshot if syntax is OK
if (pathinfo($f, PATHINFO_EXTENSION) === 'php') {
$check = shell_exec("php -l " . escapeshellarg($f) . " 2>&1");
if (strpos($check, 'No syntax errors') === false) continue;
}
$vaultName = str_replace('/', '__', ltrim($f, '/'));
$dest = VAULT_DIR . '/' . $vaultName;
// Only overwrite if live is >= vault size (no truncation)
if (file_exists($dest) && filesize($f) < filesize($dest) * 0.9) continue;
copy($f, $dest);
$saved++;
}
return ['snapshot_count'=>$saved];
}
function getStatus($pdo) {
$lastRestore = $pdo->query("SELECT * FROM admin.vault_guard_log ORDER BY created_at DESC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
$totalRestores = $pdo->query("SELECT COUNT(*) FROM admin.vault_guard_log")->fetchColumn();
$vaultFiles = count(glob(VAULT_DIR . '/*'));
$check = checkIntegrity();
return [
'status'=>$check['truncated']===0 ? 'healthy' : 'issues_found',
'vault_files'=>$vaultFiles,
'checked'=>$check['checked'],
'healthy'=>$check['healthy'],
'truncated'=>$check['truncated'],
'missing'=>$check['missing'],
'total_restores'=>(int)$totalRestores,
'last_restores'=>$lastRestore,
'issues'=>$check['details']
];
}
// Router
$action = $_GET['action'] ?? 'status';
switch($action) {
case 'check':
echo json_encode(checkIntegrity(), JSON_PRETTY_PRINT);
break;
case 'restore':
echo json_encode(autoRestore($pdo), JSON_PRETTY_PRINT);
break;
case 'snapshot':
echo json_encode(snapshotToVault(), JSON_PRETTY_PRINT);
break;
case 'cron':
// Full cycle: check → restore → snapshot
$check = checkIntegrity();
$restore = ['restored'=>0];
if ($check['truncated'] > 0) {
$restore = autoRestore($pdo);
}
$snap = snapshotToVault();
echo json_encode([
'cycle'=>'complete',
'checked'=>$check['checked'],
'truncated'=>$check['truncated'],
'restored'=>$restore['restored'],
'snapshot'=>$snap['snapshot_count'],
'timestamp'=>date('Y-m-d H:i:s')
]);
break;
case 'status':
default:
echo json_encode(getStatus($pdo), JSON_PRETTY_PRINT);
break;
}