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 "bash -c 'exec > >(tee ) 2>&1; '" $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 "" 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']);