ServerManager project files
This commit is contained in:
168
src/Services/SSHService.php
Executable file
168
src/Services/SSHService.php
Executable file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ServerManager\Services;
|
||||
|
||||
use phpseclib3\Net\SSH2;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use ServerManager\Core\App;
|
||||
use ServerManager\Core\Encryption;
|
||||
use ServerManager\Core\Logger;
|
||||
|
||||
class SSHService
|
||||
{
|
||||
private ?SSH2 $connection = null;
|
||||
private array $serverConfig;
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->logger = Logger::getInstance();
|
||||
}
|
||||
|
||||
public function connect(array $server, bool $forceReconnect = false): bool
|
||||
{
|
||||
if ($this->connection !== null && !$forceReconnect) {
|
||||
return $this->connection->isConnected();
|
||||
}
|
||||
|
||||
$this->serverConfig = $server;
|
||||
$config = App::getConfig()['ssh'];
|
||||
|
||||
try {
|
||||
$host = $server['ip_address'] ?? $server['hostname'];
|
||||
$port = (int) ($server['ssh_port'] ?? 22);
|
||||
|
||||
$this->connection = new SSH2($host, $port, $config['timeout'] ?? 30);
|
||||
|
||||
if (!empty($server['ssh_key'])) {
|
||||
$key = PublicKeyLoader::load($server['ssh_key']);
|
||||
$result = $this->connection->login($server['ssh_user'], $key);
|
||||
} elseif (!empty($server['ssh_password'])) {
|
||||
$result = $this->connection->login($server['ssh_user'], $server['ssh_password']);
|
||||
} else {
|
||||
throw new \RuntimeException('No authentication method configured');
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('SSH authentication failed for ' . $host);
|
||||
}
|
||||
|
||||
if ($config['keepalive_interval'] ?? 0 > 0) {
|
||||
$this->connection->setKeepAlive($config['keepalive_interval']);
|
||||
}
|
||||
|
||||
$this->logger->info("SSH connection established to {$host}");
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error("SSH connection failed: " . $e->getMessage(), [
|
||||
'host' => $host ?? 'unknown',
|
||||
'port' => $port ?? 22,
|
||||
]);
|
||||
$this->connection = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function disconnect(): void
|
||||
{
|
||||
if ($this->connection !== null) {
|
||||
$this->connection->disconnect();
|
||||
$this->connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function exec(string $command, int $timeout = 0): array
|
||||
{
|
||||
if ($this->connection === null || !$this->connection->isConnected()) {
|
||||
throw new \RuntimeException('No active SSH connection');
|
||||
}
|
||||
|
||||
$timeout = $timeout ?: (App::getConfig()['ssh']['command_timeout'] ?? 60);
|
||||
|
||||
$this->connection->setTimeout($timeout);
|
||||
|
||||
$output = $this->connection->exec($command);
|
||||
$exitStatus = $this->connection->getExitStatus();
|
||||
$stderr = $this->connection->getStdError() ?? '';
|
||||
|
||||
$this->logger->info("SSH command executed", [
|
||||
'command' => $command,
|
||||
'exit_status' => $exitStatus,
|
||||
]);
|
||||
|
||||
return [
|
||||
'output' => $output,
|
||||
'exit_status' => $exitStatus,
|
||||
'stderr' => $stderr,
|
||||
];
|
||||
}
|
||||
|
||||
public function write(string $data): void
|
||||
{
|
||||
if ($this->connection !== null && $this->connection->isConnected()) {
|
||||
$this->connection->write($data);
|
||||
}
|
||||
}
|
||||
|
||||
public function read(string $expected = '', int $mode = SSH2::READ_REGEX): string
|
||||
{
|
||||
if ($this->connection === null || !$this->connection->isConnected()) {
|
||||
throw new \RuntimeException('No active SSH connection');
|
||||
}
|
||||
return $this->connection->read($expected, $mode);
|
||||
}
|
||||
|
||||
public function getInteractiveShell(): ?SSH2
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function testConnection(array $server): bool
|
||||
{
|
||||
try {
|
||||
$result = $this->connect($server, true);
|
||||
if ($result) {
|
||||
$this->disconnect();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning("SSH connection test failed", ['error' => $e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSystemInfo(): array
|
||||
{
|
||||
$commands = [
|
||||
'hostname' => 'hostname',
|
||||
'os' => 'cat /etc/os-release | head -1',
|
||||
'kernel' => 'uname -r',
|
||||
'uptime' => 'uptime -p',
|
||||
'cpu_model' => 'cat /proc/cpuinfo | grep "model name" | head -1',
|
||||
'cpu_cores' => 'nproc',
|
||||
'mem_total' => 'free -b | grep Mem | awk \'{print $2}\'',
|
||||
'mem_used' => "free -b | grep Mem | awk '{print $3}'",
|
||||
'mem_avail' => "free -b | grep Mem | awk '{print $7}'",
|
||||
'disk_total' => "df -B1 / | tail -1 | awk '{print $2}'",
|
||||
'disk_used' => "df -B1 / | tail -1 | awk '{print $3}'",
|
||||
'disk_avail' => "df -B1 / | tail -1 | awk '{print $4}'",
|
||||
'disk_percent' => "df / | tail -1 | awk '{print $5}' | sed 's/%//'",
|
||||
];
|
||||
|
||||
$info = [];
|
||||
foreach ($commands as $key => $cmd) {
|
||||
$result = $this->exec($cmd);
|
||||
$info[$key] = trim($result['output']);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->disconnect();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user