140 lines
4.7 KiB
PHP
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);
|