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.
165 lines
5.6 KiB
PHP
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()];
|
|
}
|
|
}
|
|
}
|