167 lines
7.1 KiB
PHP
167 lines
7.1 KiB
PHP
<?php
|
|
// OPUS5 — SSH multiplexé tmux pour commandes longues (doctrine 79)
|
|
// Actions: create_session, send_cmd, capture, list_sessions, kill_session
|
|
// Usage: long apt upgrades, batch imports, audits complets sur S95
|
|
header('Content-Type: application/json');
|
|
$R = ['ts'=>date('c'), 'source'=>'opus5-ssh-tmux-stream'];
|
|
|
|
$S95_HOST = '10.1.0.3';
|
|
$S95_PORT = '49222';
|
|
$S95_USER = 'root';
|
|
|
|
// Whitelist commands (safety)
|
|
$ALLOWED_CMDS = ['ls', 'find', 'grep', 'cat', 'head', 'tail', 'wc', 'ps', 'df', 'du',
|
|
'curl', 'wget', 'apt-get', 'apt', 'systemctl', 'journalctl', 'netstat', 'ss',
|
|
'psql', 'redis-cli', 'tmux', 'echo', 'date', 'uname',
|
|
'docker', 'pgrep', 'pkill', 'kill', 'pkill',
|
|
'awk', 'sed', 'sort', 'uniq', 'cut', 'tr',
|
|
'rsync', 'tar', 'zcat', 'gzip', 'gunzip',
|
|
'python3', 'php', 'bash', 'sh'];
|
|
|
|
function ssh_exec($cmd, $S95_USER, $S95_HOST, $S95_PORT, $timeout = 10) {
|
|
$ec = escapeshellcmd($cmd);
|
|
$ssh = "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=3 -p $S95_PORT $S95_USER@$S95_HOST ";
|
|
return shell_exec("timeout $timeout $ssh " . escapeshellarg($cmd) . " 2>&1");
|
|
}
|
|
|
|
function is_cmd_allowed($cmd, $allowed) {
|
|
$tokens = preg_split('/[\s|&;]+/', trim($cmd));
|
|
foreach ($tokens as $t) {
|
|
$t = trim($t);
|
|
if (!$t || $t[0] === '-' || $t[0] === '$') continue;
|
|
$t = basename($t);
|
|
if (in_array($t, $allowed)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$raw = file_get_contents('php://input');
|
|
$d = json_decode($raw, true) ?: [];
|
|
$action = $_GET['action'] ?? ($d['action'] ?? 'list');
|
|
|
|
if ($action === 'health') {
|
|
$out = ssh_exec('tmux -V', $S95_USER, $S95_HOST, $S95_PORT, 5);
|
|
$R['s95_reachable'] = (bool)$out && strpos($out, 'tmux') !== false;
|
|
$R['s95_tmux_version'] = trim((string)$out);
|
|
$R['host'] = $S95_HOST;
|
|
echo json_encode($R, JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'list_sessions') {
|
|
$out = ssh_exec('tmux ls 2>/dev/null || echo NONE', $S95_USER, $S95_HOST, $S95_PORT, 8);
|
|
$R['raw'] = trim((string)$out);
|
|
$sessions = [];
|
|
if ($R['raw'] && $R['raw'] !== 'NONE') {
|
|
foreach (explode("\n", $R['raw']) as $line) {
|
|
if (preg_match('/^([a-z0-9_-]+):\s+(\d+)\s+windows/i', $line, $m)) {
|
|
$sessions[] = ['name' => $m[1], 'windows' => (int)$m[2]];
|
|
}
|
|
}
|
|
}
|
|
$R['sessions'] = $sessions;
|
|
$R['count'] = count($sessions);
|
|
echo json_encode($R, JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'create_session' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$name = preg_replace('/[^a-z0-9_-]/i', '', (string)($d['name'] ?? 'wevia_' . bin2hex(random_bytes(3))));
|
|
$cmd = (string)($d['cmd'] ?? 'htop'); // default : just open a session
|
|
|
|
if (!is_cmd_allowed($cmd, $ALLOWED_CMDS)) {
|
|
http_response_code(403);
|
|
echo json_encode(['err'=>'cmd_not_whitelisted', 'cmd'=>$cmd]);
|
|
exit;
|
|
}
|
|
|
|
// Log file on S95 pour capture ultérieure
|
|
$log = "/tmp/wevia_tmux_$name.log";
|
|
// tmux new -d -s <name> "bash -c 'exec > >(tee <log>) 2>&1; <cmd>'"
|
|
$tmux_cmd = "tmux new -d -s $name 'bash -c \"exec >$log 2>&1; $cmd; sleep 1\"'";
|
|
$out = ssh_exec($tmux_cmd, $S95_USER, $S95_HOST, $S95_PORT, 10);
|
|
|
|
// Verify
|
|
$verify = ssh_exec("tmux has-session -t $name 2>&1 && echo EXISTS || echo MISSING", $S95_USER, $S95_HOST, $S95_PORT, 5);
|
|
$R['session'] = $name;
|
|
$R['cmd'] = $cmd;
|
|
$R['log_path_on_s95'] = $log;
|
|
$R['verify'] = trim((string)$verify);
|
|
$R['created'] = trim((string)$verify) === 'EXISTS';
|
|
$R['create_raw'] = trim((string)$out);
|
|
|
|
// Log in PG task table aussi (unifié)
|
|
try {
|
|
$pdo = new PDO('pgsql:host=10.1.0.3;port=5432;dbname=adx_system;user=admin;password=admin123', null, null, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT=>3]);
|
|
$tid = 'ssh_tmux_' . date('YmdHis') . '_' . substr($name, -6);
|
|
$stmt = $pdo->prepare("INSERT INTO admin.wevia_tasks (task_id, type, status, input, session, started_at) VALUES (?, 'ssh_tmux', 'running', ?, ?, NOW())");
|
|
$stmt->execute([$tid, $cmd, $name]);
|
|
$R['task_id'] = $tid;
|
|
} catch (Throwable $e) { $R['pg_warn'] = substr($e->getMessage(), 0, 100); }
|
|
|
|
echo json_encode($R, JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'capture') {
|
|
$name = preg_replace('/[^a-z0-9_-]/i', '', (string)($_GET['name'] ?? $d['name'] ?? ''));
|
|
if (!$name) { http_response_code(400); echo json_encode(['err'=>'no_session_name']); exit; }
|
|
$lines = (int)($_GET['lines'] ?? 100);
|
|
$lines = max(10, min(1000, $lines));
|
|
|
|
// 2 captures : pane content + log file tail
|
|
$pane = ssh_exec("tmux capture-pane -t $name -p 2>/dev/null | tail -$lines", $S95_USER, $S95_HOST, $S95_PORT, 8);
|
|
$log_tail = ssh_exec("tail -$lines /tmp/wevia_tmux_$name.log 2>/dev/null || echo '[no log]'", $S95_USER, $S95_HOST, $S95_PORT, 8);
|
|
|
|
$R['session'] = $name;
|
|
$R['pane_output'] = (string)$pane;
|
|
$R['log_tail'] = (string)$log_tail;
|
|
$R['lines'] = $lines;
|
|
// Check if still alive
|
|
$alive = ssh_exec("tmux has-session -t $name 2>&1 && echo ALIVE || echo DEAD", $S95_USER, $S95_HOST, $S95_PORT, 5);
|
|
$R['alive'] = trim((string)$alive) === 'ALIVE';
|
|
|
|
echo json_encode($R, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'send_cmd' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$name = preg_replace('/[^a-z0-9_-]/i', '', (string)($d['name'] ?? ''));
|
|
$cmd = (string)($d['cmd'] ?? '');
|
|
if (!$name || !$cmd) { http_response_code(400); echo json_encode(['err'=>'missing name or cmd']); exit; }
|
|
if (!is_cmd_allowed($cmd, $ALLOWED_CMDS)) {
|
|
http_response_code(403);
|
|
echo json_encode(['err'=>'cmd_not_whitelisted', 'cmd'=>$cmd]);
|
|
exit;
|
|
}
|
|
|
|
// tmux send-keys "<cmd>" Enter
|
|
$esc = str_replace('"', '\\"', $cmd);
|
|
$out = ssh_exec("tmux send-keys -t $name \"$esc\" Enter", $S95_USER, $S95_HOST, $S95_PORT, 8);
|
|
$R['session'] = $name;
|
|
$R['cmd_sent'] = $cmd;
|
|
$R['raw'] = trim((string)$out);
|
|
$R['ok'] = !$out || strpos((string)$out, 'error') === false;
|
|
echo json_encode($R, JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
if ($action === 'kill_session' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$name = preg_replace('/[^a-z0-9_-]/i', '', (string)($d['name'] ?? ''));
|
|
if (!$name) { http_response_code(400); echo json_encode(['err'=>'no_session_name']); exit; }
|
|
$out = ssh_exec("tmux kill-session -t $name 2>&1", $S95_USER, $S95_HOST, $S95_PORT, 5);
|
|
$R['session'] = $name;
|
|
$R['killed'] = true;
|
|
$R['raw'] = trim((string)$out);
|
|
// Update PG task
|
|
try {
|
|
$pdo = new PDO('pgsql:host=10.1.0.3;port=5432;dbname=adx_system;user=admin;password=admin123', null, null, [PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT=>3]);
|
|
$pdo->prepare("UPDATE admin.wevia_tasks SET status='killed', finished_at=NOW() WHERE session=? AND status='running'")->execute([$name]);
|
|
} catch (Throwable $e) {}
|
|
echo json_encode($R, JSON_PRETTY_PRINT);
|
|
exit;
|
|
}
|
|
|
|
http_response_code(400);
|
|
echo json_encode(['err'=>'unknown_action', 'available'=>['health','list_sessions','create_session','send_cmd','capture','kill_session'], 'doctrine'=>'76 — SSH multiplexé tmux']);
|