241 lines
8.9 KiB
PHP
Executable File
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;
|
|
}
|