Files
html/api/opus5-page-api-swap.php
2026-04-17 17:05:02 +02:00

138 lines
4.3 KiB
PHP

<?php
// OPUS5 — Helper universel swap API endpoint in HTML page
// Usage: POST {"page":"ethica-chatbot.html","old":"/api/weval-ia-fast.php","new":"/api/opus5-weval-ia-fast-safe.php"}
// Sécurité: validation stricte, GOLD backup, chattr handling, rollback auto sur lint fail
header('Content-Type: application/json');
$R = ['ts'=>date('c'), 'source'=>'opus5-page-api-swap', 'actions'=>[]];
$raw = file_get_contents('php://input');
$d = json_decode($raw, true) ?: [];
$page = $d['page'] ?? '';
$old = $d['old'] ?? '';
$new = $d['new'] ?? '';
$dry = !empty($d['dry_run']);
// Validation stricte
if (!$page || !$old || !$new) {
http_response_code(400);
echo json_encode(['err'=>'missing_params', 'need'=>['page','old','new']]);
exit;
}
// Whitelist pages (sécurité)
$whitelist = ['ethica-chatbot.html', 'wevia-master.html', 'wevia-widget.html'];
if (!in_array($page, $whitelist)) {
http_response_code(403);
echo json_encode(['err'=>'page_not_whitelisted', 'whitelist'=>$whitelist]);
exit;
}
// Whitelist API endpoints (pattern /api/*.php seulement)
if (!preg_match('#^/api/[a-z0-9\-_]+\.php$#i', $old) || !preg_match('#^/api/[a-z0-9\-_]+\.php$#i', $new)) {
http_response_code(400);
echo json_encode(['err'=>'invalid_api_pattern', 'expected'=>'/api/*.php']);
exit;
}
// Vérifier que new endpoint existe et est testable
$newFile = '/var/www/html' . $new;
if (!file_exists($newFile)) {
http_response_code(404);
echo json_encode(['err'=>'new_endpoint_not_found', 'path'=>$newFile]);
exit;
}
$F = '/var/www/html/' . $page;
if (!file_exists($F)) {
http_response_code(404);
echo json_encode(['err'=>'page_not_found', 'path'=>$F]);
exit;
}
$content_before = file_get_contents($F);
$R['page_size_before'] = strlen($content_before);
// Compte occurrences
$R['old_occurrences'] = substr_count($content_before, $old);
if ($R['old_occurrences'] === 0) {
http_response_code(404);
echo json_encode(['err'=>'old_pattern_not_found', 'old'=>$old]);
exit;
}
// DRY RUN — retourne preview sans exec
if ($dry) {
$preview = str_replace($old, $new, $content_before);
$R['dry_run'] = true;
$R['preview_diff'] = $R['old_occurrences'] . ' occurrences replaced';
$R['size_after'] = strlen($preview);
echo json_encode($R, JSON_PRETTY_PRINT);
exit;
}
// Check chattr
$attr = trim(shell_exec("lsattr $F 2>&1 | awk '{print \$1}'"));
$R['chattr_before'] = $attr;
$had_immutable = strpos($attr, 'i') !== false;
// GOLD backup (doctrine 3)
$GOLD = $F . '.GOLD-' . date('Ymd-His') . '-pre-opus5-api-swap';
$gold_ok = @copy($F, $GOLD);
$R['gold'] = $gold_ok ? $GOLD : 'FAILED';
if (!$gold_ok) {
http_response_code(500);
echo json_encode(['err'=>'gold_backup_failed']);
exit;
}
// Remove chattr via sudo (NOPASSWD www-data)
if ($had_immutable) {
$o = shell_exec("sudo chattr -i $F 2>&1");
$R['chattr_removed'] = trim($o) === '' ? 'OK' : trim($o);
if (strpos($R['chattr_removed'], 'Operation not permitted') !== false) {
$R['err'] = 'chattr_remove_failed';
echo json_encode($R, JSON_PRETTY_PRINT);
exit;
}
}
// Do the swap
$content_after = str_replace($old, $new, $content_before);
$written = file_put_contents($F, $content_after);
$R['written_bytes'] = $written;
$R['page_size_after'] = strlen($content_after);
// Verify swap ok
$verify = file_get_contents($F);
$R['new_present_after'] = substr_count($verify, $new);
$R['old_remaining'] = substr_count($verify, $old);
// Lint HTML basique (check no <?php syntax break, not HTML lint real)
$R['first_line'] = trim(substr($verify, 0, 30));
// Re-chattr+i
if ($had_immutable) {
shell_exec("sudo chattr +i $F 2>&1");
$attr_after = trim(shell_exec("lsattr $F 2>&1 | awk '{print \$1}'"));
$R['chattr_after'] = $attr_after;
}
// Rollback si something broken (detection simple)
if ($R['new_present_after'] === 0 || strpos($verify, '<!DOCTYPE') === false) {
// Restore GOLD
if ($had_immutable) shell_exec("sudo chattr -i $F 2>&1");
@copy($GOLD, $F);
if ($had_immutable) shell_exec("sudo chattr +i $F 2>&1");
$R['err'] = 'verification_failed_rollback_done';
$R['rolled_back'] = true;
http_response_code(500);
echo json_encode($R, JSON_PRETTY_PRINT);
exit;
}
$R['doctrine'] = '66 — swap API endpoint safe (whitelist + GOLD + chattr + verify + rollback)';
$R['success'] = true;
echo json_encode($R, JSON_PRETTY_PRINT);