feat: ssh auth, protocol management, and cleanup

This commit is contained in:
infosave2007
2026-01-23 17:55:40 +03:00
parent 4995147bad
commit ea82b78a7d
70 changed files with 16225 additions and 986 deletions
+375
View File
@@ -0,0 +1,375 @@
<?php
/**
* ScenarioController
* Manages protocol installation scenarios (CRUD operations)
* Allows administrators to view, create, edit, and delete VPN protocol deployment scenarios
*/
class ScenarioController {
/**
* List all protocol scenarios
* GET /admin/scenarios
*/
public function listScenarios() {
requireAdmin();
$scenarios = InstallProtocolManager::getAll();
View::render('settings/scenarios.twig', [
'scenarios' => $scenarios,
'section' => 'scenarios'
]);
}
/**
* View single scenario details
* GET /admin/scenario/:id
*/
public function viewScenario($id) {
requireAdmin();
$scenario = InstallProtocolManager::getById((int)$id);
if (!$scenario) {
http_response_code(404);
View::render('404.twig');
return;
}
$definition = $scenario['definition'] ?? [];
View::render('settings/scenario_view.twig', [
'scenario' => $scenario,
'definition' => $definition,
'section' => 'scenarios'
]);
}
/**
* Show form to create new scenario
* GET /admin/scenario/create
*/
public function createScenarioForm() {
requireAdmin();
$templateDefinition = [
'engine' => 'shell',
'metadata' => [
'container_name' => 'custom-container',
'config_path' => '/opt/amnezia/custom'
],
'scripts' => [
'detect' => 'echo \'{"status":"absent","message":"Custom protocol not found"}\'',
'install' => 'echo \'{"success":true,"message":"Custom protocol installed"}\'',
'restore' => 'echo \'{"success":true,"message":"Custom protocol restored"}\''
]
];
View::render('settings/scenario_form.twig', [
'scenario' => null,
'templateDefinition' => json_encode($templateDefinition, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
'section' => 'scenarios'
]);
}
/**
* Show form to edit existing scenario
* GET /admin/scenario/:id/edit
*/
public function editScenarioForm($id) {
requireAdmin();
$scenario = InstallProtocolManager::getById((int)$id);
if (!$scenario) {
http_response_code(404);
View::render('404.twig');
return;
}
$definitionJson = json_encode($scenario['definition'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
View::render('settings/scenario_form.twig', [
'scenario' => $scenario,
'templateDefinition' => $definitionJson,
'section' => 'scenarios'
]);
}
/**
* Save scenario (create or update)
* POST /admin/scenario
*/
public function saveScenario() {
requireAdmin();
$data = $_POST;
// Validate required fields
if (empty($data['slug']) || empty($data['name'])) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Slug and name are required'
]);
return;
}
// Parse definition JSON
if (!empty($data['definition'])) {
if (is_string($data['definition'])) {
$definition = json_decode($data['definition'], true);
if (json_last_error() !== JSON_ERROR_NONE) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Invalid JSON in definition: ' . json_last_error_msg()
]);
return;
}
} else {
$definition = $data['definition'];
}
} else {
$definition = [];
}
// Validate definition structure
if (empty($definition['engine'])) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Definition must have "engine" field'
]);
return;
}
$saveData = [
'slug' => $data['slug'],
'name' => $data['name'],
'description' => $data['description'] ?? null,
'definition' => $definition,
'is_active' => isset($data['is_active']) ? (int)$data['is_active'] : 1
];
if (!empty($data['id'])) {
$saveData['id'] = (int)$data['id'];
}
try {
$id = InstallProtocolManager::save($saveData);
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'id' => $id,
'message' => 'Scenario saved successfully',
'redirect' => '/admin/scenarios'
]);
} catch (Exception $e) {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Error saving scenario: ' . $e->getMessage()
]);
}
}
/**
* Delete scenario
* DELETE /admin/scenario/:id or POST /admin/scenario/:id/delete
*/
public function deleteScenario($id) {
requireAdmin();
$id = (int)$id;
// Prevent deletion of default scenario
$scenario = InstallProtocolManager::getById($id);
if (!$scenario) {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode([
'success' => false,
'message' => 'Scenario not found'
]);
return;
}
if ($scenario['slug'] === InstallProtocolManager::getDefaultSlug()) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Cannot delete default scenario'
]);
return;
}
try {
InstallProtocolManager::delete($id);
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => 'Scenario deleted successfully',
'redirect' => '/admin/scenarios'
]);
} catch (Exception $e) {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Error deleting scenario: ' . $e->getMessage()
]);
}
}
/**
* Test scenario script (detection)
* POST /admin/scenario/:id/test
*/
public function testScenario($id) {
requireAdmin();
$scenario = InstallProtocolManager::getById((int)$id);
if (!$scenario) {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode([
'success' => false,
'message' => 'Scenario not found'
]);
return;
}
$serverId = (int)($_POST['server_id'] ?? 0);
if (!$serverId) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Server ID required'
]);
return;
}
$server = new VpnServer($serverId);
if (!$server->getData()) {
header('Content-Type: application/json');
http_response_code(404);
echo json_encode([
'success' => false,
'message' => 'Server not found'
]);
return;
}
try {
// Test SSH connection first
if (!$server->testConnection()) {
throw new Exception('SSH connection to server failed');
}
// Run detection script
$result = InstallProtocolManager::runDetection($server, $scenario);
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'result' => $result
]);
} catch (Exception $e) {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Error testing scenario: ' . $e->getMessage()
]);
}
}
/**
* Export scenario as JSON
* GET /admin/scenario/:id/export
*/
public function exportScenario($id) {
requireAdmin();
$scenario = InstallProtocolManager::getById((int)$id);
if (!$scenario) {
http_response_code(404);
return;
}
header('Content-Type: application/json');
header('Content-Disposition: attachment; filename="scenario-' . $scenario['slug'] . '-' . date('Y-m-d') . '.json"');
// Remove database IDs from export
unset($scenario['id']);
echo json_encode($scenario, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
/**
* Import scenario from JSON
* POST /admin/scenario/import
*/
public function importScenario() {
requireAdmin();
$file = $_FILES['file'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'No file provided'
]);
return;
}
$contents = file_get_contents($file['tmp_name']);
$scenario = json_decode($contents, true);
if (!is_array($scenario)) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode([
'success' => false,
'message' => 'Invalid JSON file'
]);
return;
}
try {
// Remove ID so it creates a new entry
unset($scenario['id']);
// Ensure required fields exist
if (empty($scenario['slug']) || empty($scenario['name'])) {
throw new Exception('Imported scenario must have slug and name');
}
$id = InstallProtocolManager::save($scenario);
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'id' => $id,
'message' => 'Scenario imported successfully',
'redirect' => '/admin/scenarios'
]);
} catch (Exception $e) {
header('Content-Type: application/json');
http_response_code(500);
echo json_encode([
'success' => false,
'message' => 'Error importing scenario: ' . $e->getMessage()
]);
}
}
}