Files
html/api/wevia-safe-write.php
Opus-Yacine 8a5fb99047
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
V82 CONSOLIDATOR · 3 vues orphelins unifiees dans WTP drawer · reconciliation 4 Claude. Yacine directive: continue plan daction WTP point entree unique consolidation integration pas multiplication sources. Scan exhaustif: 3 commits autres Claude depuis mon V81: Opus5 bbea3d96a Doctrine 91 classifier (25 archive+21 actifs+20 dormant+intent orphans_audit 9 triggers) + Opus WIRE bf6d74033 V82 mapper 8 suites metier + rescue UI + Opus Yacine be77e90ac Infrastructure Live Widget 6 KPI boxes auto-refresh 30s. PROBLEME: 3 approches orphelins different non consolidees dans WTP drawer. LIVRABLE V82 Consolidator 10.5KB inject WTP APRES V81 block (additive pur): tabbed UI 3 onglets cliquables avec styles actifs lazy-load on drawer open: (1) Brut V79 fetch pages-registry orphans classes (2) Suites V82 Opus WIRE fetch wevia-orphans-mapper 8 suites metier (Autres/WEVIA Enterprise/Archive/Cloud Security/Commerce/Consulting/Pharma/Marketing) (3) Tri V91 Opus5 fetch opus5-orphans-classifier 3 categories action-oriented Archive legitime + A rebrancher + Dormant avec summary counts top. V81 section cachee via style.display=none (consolidee dans V82 conserve DOM facile rollback). Lien vers /orphans-rescue.html pour sortir de orphelinat. E2E Playwright 12/12 PASS video dedf1d306e788f5ab1c90563a32acd07.webm 6 screenshots: TEST 3 V82 3 tabs + V81 hidden, TEST 4 tab RAW 67 orphan links, TEST 5 tab MAPPER 8 suites 66 links, TEST 6 tab CLASSIFIER 25 links Archive + Rebrancher visibles, TEST 7 WEVIA agis en multi-agents 35 unique agents EXEC_REEL True pas simulation 4210ms, TEST 8 V77 39 agents 272ms, TEST 9 V78 dispatcher matched orphelin+referentiel+archi 5 selected, TEST 10 Opus5 orphans_audit fired + classification, TEST 11 Final 255 pages 67 orph 906 agents 100pct autonomy, TEST 12 ZERO JS error. Reconciliation 4 Claude: Moi V79 raw + Opus WIRE V82 suites + Opus5 V91 tri + Opus Yacine infrastructure widget · 4 approches UNE interface consolidee. Anti-regression: GOLD backup pre-v82, lsattr +e respecte, V80 drawer + V81 backend + V75 AvatarUnifier + sidebar Opus Yacine + Infrastructure widget TOUS preserves, lint HTML OK, zero suppression zero fake zero hardcode zero ecrasement.
2026-04-19 17:18:47 +02:00

140 lines
4.7 KiB
PHP

<?php
/**
* WEVIA Safe Write Helper · V91
* Permet à WEVIA Master d'écrire fichiers protégés chattr+i de manière sécurisée.
*
* Pattern : sudo chattr -i FILE → write → sudo chattr +i FILE → GOLD backup
*
* Usage POST JSON :
* { "k": "WEVADS2026",
* "path": "/var/www/html/wevia.html",
* "content_b64": "PGgxPi4uPC9oMT4=",
* "backup": true,
* "lint": "php" // or "html" or "js" or null
* }
*
* Returns : { ok: true, bytes_written, gold_backup, was_immutable }
* { ok: false, error: "..." }
*
* SECURITY:
* - Only allows writes inside /var/www/html/ (no /etc/, /root/, etc.)
* - Requires k=WEVADS2026 token
* - PHP lint check before deploy if lint=php
* - GOLD backup MANDATORY before any modification
* - Atomic write via temp file + sudo cp (preserves nginx serving)
*/
header('Content-Type: application/json; charset=utf-8');
// --- Auth ---
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$k = $input['k'] ?? $_GET['k'] ?? '';
if ($k !== 'WEVADS2026') {
http_response_code(401);
echo json_encode(['ok'=>false, 'error'=>'invalid token']);
exit;
}
$path = $input['path'] ?? '';
$content_b64 = $input['content_b64'] ?? '';
$do_backup = $input['backup'] ?? true;
$lint = $input['lint'] ?? null;
// --- Path validation ---
if (!$path || !$content_b64) {
echo json_encode(['ok'=>false, 'error'=>'missing path or content_b64']);
exit;
}
$realpath = realpath(dirname($path)) . '/' . basename($path);
$allowed_roots = ['/var/www/html/', '/opt/weval-l99/', '/opt/wevads/'];
$ok = false;
foreach ($allowed_roots as $r) {
if (strpos($path, $r) === 0) { $ok = true; break; }
}
if (!$ok) {
echo json_encode(['ok'=>false, 'error'=>'path outside allowed roots: ' . implode(',', $allowed_roots)]);
exit;
}
$content = base64_decode($content_b64, true);
if ($content === false) {
echo json_encode(['ok'=>false, 'error'=>'invalid base64 content']);
exit;
}
// --- Step 1: GOLD backup (mandatory) ---
$gold = null;
if ($do_backup && file_exists($path)) {
$vault = '/opt/wevads/vault/';
if (!is_dir($vault)) @mkdir($vault, 0755, true);
$gold = $vault . basename($path) . '.GOLD-' . date('Ymd-His') . '-pre-safe-write';
@copy($path, $gold);
if (!file_exists($gold)) {
// Try via sudo
@shell_exec('sudo cp ' . escapeshellarg($path) . ' ' . escapeshellarg($gold) . ' 2>&1');
}
}
// --- Step 2: Detect immutable ---
$lsattr_out = @shell_exec('lsattr ' . escapeshellarg($path) . ' 2>&1');
$was_immutable = ($lsattr_out && strpos($lsattr_out, '-i-') !== false);
// --- Step 3: Lint check (if applicable) ---
$lint_result = null;
if ($lint) {
$tmp_lint = '/tmp/wevia-safe-write-lint-' . uniqid() . '.tmp';
file_put_contents($tmp_lint, $content);
if ($lint === 'php') {
$lint_result = @shell_exec('php8.4 -l ' . escapeshellarg($tmp_lint) . ' 2>&1');
if (strpos($lint_result, 'No syntax errors') === false) {
@unlink($tmp_lint);
echo json_encode(['ok'=>false, 'error'=>'php lint failed', 'lint_output'=>trim($lint_result), 'gold_backup'=>$gold]);
exit;
}
} elseif ($lint === 'js') {
$lint_result = @shell_exec('node -c ' . escapeshellarg($tmp_lint) . ' 2>&1');
if (trim($lint_result) !== '') {
@unlink($tmp_lint);
echo json_encode(['ok'=>false, 'error'=>'js lint failed', 'lint_output'=>trim($lint_result), 'gold_backup'=>$gold]);
exit;
}
}
@unlink($tmp_lint);
}
// --- Step 4: chattr -i if immutable ---
if ($was_immutable) {
@shell_exec('sudo chattr -i ' . escapeshellarg($path) . ' 2>&1');
}
// --- Step 5: Atomic write via temp + sudo cp ---
$tmp = '/tmp/wevia-safe-write-' . uniqid() . '.tmp';
$bytes = file_put_contents($tmp, $content);
if ($bytes === false) {
echo json_encode(['ok'=>false, 'error'=>'failed write tmp file', 'gold_backup'=>$gold]);
exit;
}
$cp_out = @shell_exec('sudo cp ' . escapeshellarg($tmp) . ' ' . escapeshellarg($path) . ' 2>&1');
$chown_out = @shell_exec('sudo chown www-data:www-data ' . escapeshellarg($path) . ' 2>&1');
@unlink($tmp);
// --- Step 6: chattr +i restore if was immutable ---
if ($was_immutable) {
@shell_exec('sudo chattr +i ' . escapeshellarg($path) . ' 2>&1');
}
// --- Verify ---
$final_size = file_exists($path) ? filesize($path) : 0;
$success = ($final_size === strlen($content));
echo json_encode([
'ok' => $success,
'path' => $path,
'bytes_written' => $bytes,
'final_size' => $final_size,
'was_immutable' => $was_immutable,
'gold_backup' => $gold,
'lint_result' => $lint_result ? trim($lint_result) : null,
'cp_out' => trim($cp_out),
'ts' => date('c')
], JSON_PRETTY_PRINT);