Files
server-manager/src/Services/AgentService.php
Agent fd3cc6825c fix: dual-mode installer — works with or without sudo
Installer now detects at runtime if it can get root access:
- Root/NOPASSWD sudo: installs to system directories with systemd
- No root: installs to ~/.local/bin with @reboot cron job

Self-elevation uses 'script -qc sudo bash' for requiretty, or falls
back to user mode without hanging on password prompts.

Uninstaller checks both system and user paths.
2026-06-11 19:12:57 -04:00

165 lines
5.6 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 2>/dev/null && chmod +x /tmp/_sm_install.sh && bash /tmp/_sm_install.sh %s %s 2>&1; rc=$?; rm -f /tmp/_sm_install.sh 2>/dev/null; exit $rc',
escapeshellarg($b64),
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;
if ($exitStatus !== 0) {
$lastLines = implode("\n", array_slice(explode("\n", $output), -20));
return [
'success' => false,
'error' => 'Agent installation failed on remote server',
'output' => $lastLines,
];
}
$path = '/usr/local/bin/servermanager-agent';
if (preg_match('/Binary:\s*(\S+)/', $output, $m)) {
$path = $m[1];
}
$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 2>/dev/null && chmod +x /tmp/_sm_uninstall.sh && bash /tmp/_sm_uninstall.sh 2>&1; rc=$?; rm -f /tmp/_sm_uninstall.sh 2>/dev/null; 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()];
}
}
}