- Revert SSHService exec() back to original (remove PTY parameter) - AgentService now writes installer to /tmp/ first, then runs via script -q -c to create a PTY for sudo (handles requiretty) - Falls back to direct sudo bash if script is unavailable - Cleans up temp files after execution - Added mkdir -p for /usr/local/bin in installer - Added file verification and byte-count after writing agent binary
163 lines
5.7 KiB
PHP
163 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ServerManager\Services;
|
|
|
|
use ServerManager\Core\App;
|
|
use ServerManager\Core\Database;
|
|
use ServerManager\Core\Logger;
|
|
|
|
class AgentService
|
|
{
|
|
private Database $db;
|
|
private Logger $logger;
|
|
private string $basePath;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = Database::getInstance();
|
|
$this->logger = Logger::getInstance();
|
|
$this->basePath = dirname(__DIR__, 2);
|
|
}
|
|
|
|
public function install(int $serverId): array
|
|
{
|
|
$server = $this->db->fetch(
|
|
'SELECT * FROM servers WHERE id = ? AND status = \'active\' AND is_active = 1',
|
|
[$serverId]
|
|
);
|
|
|
|
if (!$server) {
|
|
return ['success' => false, 'error' => 'Server not found or inactive'];
|
|
}
|
|
|
|
$server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']);
|
|
|
|
$agentKey = $server['agent_key'];
|
|
$serverUrl = rtrim((App::getConfig()['app']['url'] ?? ''), '/');
|
|
|
|
if (empty($serverUrl)) {
|
|
$serverUrl = ($_SERVER['HTTPS'] ?? '' === 'on' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'localhost');
|
|
}
|
|
|
|
$scriptPath = $this->basePath . '/install/agent-install.sh';
|
|
|
|
if (!file_exists($scriptPath)) {
|
|
return ['success' => false, 'error' => 'Agent install script not found'];
|
|
}
|
|
|
|
$script = file_get_contents($scriptPath);
|
|
|
|
$b64 = base64_encode($script);
|
|
$remoteCommand = sprintf(
|
|
'echo %s | base64 -d > /tmp/_sm_install.sh && chmod +x /tmp/_sm_install.sh && (script -q -c "sudo bash /tmp/_sm_install.sh %s %s" /dev/null < /dev/null 2>/dev/null || sudo bash /tmp/_sm_install.sh %s %s) 2>&1; rc=$?; rm -f /tmp/_sm_install.sh; exit $rc',
|
|
escapeshellarg($b64),
|
|
escapeshellarg($serverUrl),
|
|
escapeshellarg($agentKey),
|
|
escapeshellarg($serverUrl),
|
|
escapeshellarg($agentKey)
|
|
);
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server, true)) {
|
|
return ['success' => false, 'error' => 'SSH connection failed'];
|
|
}
|
|
|
|
$result = $ssh->exec($remoteCommand, 120);
|
|
$ssh->disconnect();
|
|
|
|
$output = trim($result['output']);
|
|
$exitStatus = $result['exit_status'] ?? -1;
|
|
$path = '/usr/local/bin/servermanager-agent';
|
|
|
|
if ($exitStatus !== 0) {
|
|
$lastLines = implode("\n", array_slice(explode("\n", $output), -20));
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Agent installation failed on remote server',
|
|
'output' => $lastLines,
|
|
];
|
|
}
|
|
|
|
$serverModel = new \ServerManager\Models\Server();
|
|
$serverModel->setAgentInstalled($serverId, true, $path);
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->log('agent_installed', 'server', $serverId, [
|
|
'name' => $server['name'],
|
|
'path' => $path,
|
|
]);
|
|
|
|
return ['success' => true, 'path' => $path, 'output' => $output];
|
|
} catch (\Throwable $e) {
|
|
$this->logger->error("Agent install failed for server {$serverId}: " . $e->getMessage());
|
|
return ['success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
|
|
public function uninstall(int $serverId): array
|
|
{
|
|
$server = $this->db->fetch(
|
|
'SELECT * FROM servers WHERE id = ? AND status = \'active\' AND is_active = 1',
|
|
[$serverId]
|
|
);
|
|
|
|
if (!$server) {
|
|
return ['success' => false, 'error' => 'Server not found or inactive'];
|
|
}
|
|
|
|
$server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']);
|
|
|
|
$scriptPath = $this->basePath . '/install/agent-uninstall.sh';
|
|
|
|
if (!file_exists($scriptPath)) {
|
|
return ['success' => false, 'error' => 'Agent uninstall script not found'];
|
|
}
|
|
|
|
$script = file_get_contents($scriptPath);
|
|
|
|
$remoteCommand = sprintf(
|
|
'echo %s | base64 -d > /tmp/_sm_uninstall.sh && chmod +x /tmp/_sm_uninstall.sh && (script -q -c "sudo bash /tmp/_sm_uninstall.sh" /dev/null < /dev/null 2>/dev/null || sudo bash /tmp/_sm_uninstall.sh) 2>&1; rc=$?; rm -f /tmp/_sm_uninstall.sh; exit $rc',
|
|
escapeshellarg(base64_encode($script))
|
|
);
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server, true)) {
|
|
return ['success' => false, 'error' => 'SSH connection failed'];
|
|
}
|
|
|
|
$result = $ssh->exec($remoteCommand, 60);
|
|
$ssh->disconnect();
|
|
|
|
$output = trim($result['output']);
|
|
$exitStatus = $result['exit_status'] ?? -1;
|
|
|
|
$serverModel = new \ServerManager\Models\Server();
|
|
$serverModel->setAgentInstalled($serverId, false, null);
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->log('agent_uninstalled', 'server', $serverId, [
|
|
'name' => $server['name'],
|
|
]);
|
|
|
|
if ($exitStatus !== 0) {
|
|
return [
|
|
'success' => false,
|
|
'error' => 'Agent uninstallation may have failed on remote server',
|
|
'output' => $output,
|
|
];
|
|
}
|
|
|
|
return ['success' => true, 'output' => $output];
|
|
} catch (\Throwable $e) {
|
|
$this->logger->error("Agent uninstall failed for server {$serverId}: " . $e->getMessage());
|
|
$serverModel = new \ServerManager\Models\Server();
|
|
$serverModel->setAgentInstalled($serverId, false, null);
|
|
return ['success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
}
|