From 4aa3a766c67f98582bd0593d8bbf6ce039769cab Mon Sep 17 00:00:00 2001 From: Urvish Patel Date: Wed, 9 Jul 2025 08:30:46 +0200 Subject: [PATCH] Refactor code --- backend/app/.env | 6 + backend/app/README.md | 56 +++--- backend/app/composer.json | 3 +- backend/app/config.json | 1 - backend/app/public/index.php | 25 +-- .../last-access-logs/container-mitul.txt | 1 + .../app/src/Controllers/ProxyController.php | 23 ++- backend/app/src/Services/LxdService.php | 182 ++++++++++++------ 8 files changed, 193 insertions(+), 104 deletions(-) create mode 100644 backend/app/.env diff --git a/backend/app/.env b/backend/app/.env new file mode 100644 index 0000000..249b96d --- /dev/null +++ b/backend/app/.env @@ -0,0 +1,6 @@ +MAIN_DOMAIN=lxdapp.local +MAIN_COOKIE_DOMAIN=.lxdapp.local +LXD_API_URL=https://localhost:8443 +LXD_CLIENT_CERT=/etc/ssl/lxdapp/client.crt +LXD_CLIENT_KEY=/etc/ssl/lxdapp/client.key +LXD_IMAGE_FINGERPRINT=2edfd84b1396 diff --git a/backend/app/README.md b/backend/app/README.md index 5d8edb7..6ac61fa 100644 --- a/backend/app/README.md +++ b/backend/app/README.md @@ -35,9 +35,6 @@ backend/ └── README.md ---- - - --- ## ⚙️ Requirements @@ -46,9 +43,10 @@ backend/ - LXD installed and configured - PHP-FPM + NGINX - Composer -- `guzzlehttp/guzzle` - `slim/slim` - `slim/psr7` +- `guzzlehttp/guzzle` +- `vlucas/phpdotenv` --- @@ -57,41 +55,41 @@ backend/ 1. **put the folder in /var/www/html** 2. **Install dependencies** - + ```bash composer install 3. **Ensure PHP and NGINX are configured** NGINX config example: + ```bash + server { + listen 80; + server_name *.lxdapp.local; -server { - listen 80; - server_name *.lxdapp.local; + root /var/www/html/lxd-app/backend/public; + index index.php; - root /var/www/html/lxd-app/backend/public; - index index.php; + location / { + try_files $uri /index.php?$query_string; + } - location / { - try_files $uri /index.php?$query_string; + location ~ \.php$ { + fastcgi_pass unix:/run/php/php8.4-fpm.sock; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_read_timeout 300; + } } - location ~ \.php$ { - fastcgi_pass unix:/run/php/php8.4-fpm.sock; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_read_timeout 300; - } -} - 4. **Map domain in /etc/hosts** - - 127.0.0.1 customer1.lxdapp.local + ```bash + 127.0.0.1 mitul.lxdapp.local 5. **Make sure LXD is working** - + ```bash lxc list 6. **🔐 CAPTCHA Protection** @@ -106,7 +104,7 @@ server { POST /api/v1/proxy **Request Body:** - + ```bash { "source": "login", "panswer": "abc123" @@ -114,19 +112,16 @@ server { **Headers:** - Origin: http://customer1.lxdapp.local + Origin: http://mitul.lxdapp.local **Response:** - + ```bash { "status": "success", "ip": "10.210.189.24" } - - - **🧠 Notes** Container names are auto-generated using the subdomain prefix. @@ -144,6 +139,7 @@ server { public/last-access-logs/container-*.txt NGINX error logs (for debugging): + ```bash tail -f /var/log/nginx/error.log diff --git a/backend/app/composer.json b/backend/app/composer.json index d0baf24..52def26 100644 --- a/backend/app/composer.json +++ b/backend/app/composer.json @@ -4,7 +4,8 @@ "slim/psr7": "^1.7", "php-di/php-di": "^7.0", "guzzlehttp/guzzle": "^7.9", - "nyholm/psr7": "^1.8" + "nyholm/psr7": "^1.8", + "vlucas/phpdotenv": "^5.6" }, "autoload": { "psr-4": { diff --git a/backend/app/config.json b/backend/app/config.json index a8b3f8f..7a73a41 100644 --- a/backend/app/config.json +++ b/backend/app/config.json @@ -1,3 +1,2 @@ { - "customer1.lxdapp.local": "container-customer1" } \ No newline at end of file diff --git a/backend/app/public/index.php b/backend/app/public/index.php index 41e12a8..41f3055 100644 --- a/backend/app/public/index.php +++ b/backend/app/public/index.php @@ -1,7 +1,18 @@ load(); + +$domain = $_ENV['MAIN_COOKIE_DOMAIN'] ?? '.lxdapp.local'; session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', @@ -14,13 +25,6 @@ session_set_cookie_params([ if (session_status() === PHP_SESSION_NONE) { session_start(); } -use DI\ContainerBuilder; -use Slim\Factory\AppFactory; - -require __DIR__ . '/../vendor/autoload.php'; - -error_reporting(E_ALL); -ini_set('display_errors', '1'); // Build Container using PHP-DI $containerBuilder = new ContainerBuilder(); @@ -44,11 +48,11 @@ AppFactory::setContainer($container); // Create App $app = AppFactory::create(); +// 🔹 CORS middleware $app->add(function ($request, $handler) { $response = $handler->handle($request); - - // $origin = $request->getHeaderLine('Origin') ?: '*'; $origin = $_SERVER['HTTP_ORIGIN'] ?? '*'; + return $response ->withHeader('Access-Control-Allow-Origin', $origin) ->withHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization') @@ -56,7 +60,6 @@ $app->add(function ($request, $handler) { ->withHeader('Access-Control-Allow-Credentials', 'true'); }); - // Register middleware (require __DIR__ . '/../src/Bootstrap/middleware.php')($app); diff --git a/backend/app/public/last-access-logs/container-mitul.txt b/backend/app/public/last-access-logs/container-mitul.txt index 827dbe7..a27696c 100644 --- a/backend/app/public/last-access-logs/container-mitul.txt +++ b/backend/app/public/last-access-logs/container-mitul.txt @@ -7,3 +7,4 @@ 2025-07-08 15:12:49 : http://10.110.90.95:80 2025-07-08 15:23:59 : http://10.110.90.147:80 2025-07-08 15:24:26 : http://10.110.90.147:80 +2025-07-09 06:15:42 : http://10.110.90.248:80 diff --git a/backend/app/src/Controllers/ProxyController.php b/backend/app/src/Controllers/ProxyController.php index eb6edf1..1824def 100644 --- a/backend/app/src/Controllers/ProxyController.php +++ b/backend/app/src/Controllers/ProxyController.php @@ -12,12 +12,15 @@ use App\lib\PCaptcha; class ProxyController { + /** + * Handles forwarding requests to the appropriate container. + */ public function forward(Request $request, Response $response): Response { - $mainDomain = 'lxdapp.local'; + $mainDomain = $_ENV['MAIN_DOMAIN'] ?? 'lxdapp.local'; $origin = $request->getHeaderLine('Origin'); - $domain = parse_url($origin, PHP_URL_HOST); // e.g. customer1.lxdapp.local + $domain = parse_url($origin, PHP_URL_HOST); // e.g. mitul.lxdapp.local $params = (array)$request->getParsedBody(); $configPath = __DIR__ . '/../../config.json'; @@ -95,6 +98,9 @@ class ProxyController return $this->proxyToContainer($request, $response, $ip, $name); } + /** + * Maps a domain to a container name. + */ private function mapDomainToContainer(string $domain): ?string { $configPath = __DIR__ . '/../../config.json'; @@ -108,7 +114,9 @@ class ProxyController return $config[$domain] ?? null; } - + /** + * Sends a JSON response. + */ protected function json($response, array $data, int $status = 200) { $payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); @@ -121,6 +129,9 @@ class ProxyController ->withStatus($status); } + /** + * Generates a sanitized container name based on a subdomain. + */ public function generateContainerName(string $subdomain): string { // Convert to lowercase, remove unsafe characters @@ -130,6 +141,9 @@ class ProxyController return "container-{$sanitized}"; } + /** + * Proxies the request to the container. + */ private function proxyToContainer(Request $request, Response $response, string $ip, string $name): Response { $target = $ip ? "http://$ip:80" : "http://127.0.0.1:3000"; @@ -153,6 +167,9 @@ class ProxyController } + /** + * Logs the last access to a container. + */ protected function writeLastAccessLog(string $name, string $uri): void { // Dynamically resolve the log directory relative to the current file $logDir = realpath(__DIR__ . '/../../public/last-access-logs'); diff --git a/backend/app/src/Services/LxdService.php b/backend/app/src/Services/LxdService.php index bbe207a..792c807 100644 --- a/backend/app/src/Services/LxdService.php +++ b/backend/app/src/Services/LxdService.php @@ -5,30 +5,41 @@ use Exception; class LxdService { - private string $baseUrl; - private string $socketPath; - + private string $baseUrl; + private string $imageFingerprint; + public function __construct() { - // $this->baseUrl = "http://unix.socket"; - $this->baseUrl = "https://localhost:8443"; - $this->socketPath = "/var/snap/lxd/common/lxd/unix.socket"; + $this->baseUrl = $_ENV['LXD_API_URL'] ?? 'https://localhost:8443'; + $this->imageFingerprint = $_ENV['LXD_IMAGE_FINGERPRINT'] ?? '2edfd84b1396'; } - + + + + /** + * Sends an HTTP request to the LXD API. + * + * @param string $method HTTP method (GET, POST, PUT, etc.) + * @param string $endpoint API endpoint + * @param array $body Request body (optional) + * @return array Response from the API + * @throws Exception + */ private function request(string $method, string $endpoint, array $body = []): array { + + if (!isset($_ENV['LXD_CLIENT_CERT'], $_ENV['LXD_CLIENT_KEY'])) { + throw new \Exception("LXD_CLIENT_CERT and LXD_CLIENT_KEY must be set in .env"); + } + $ch = curl_init("{$this->baseUrl}{$endpoint}"); - // Paths to your client cert and key - $clientCert = '/etc/ssl/lxdapp/client.crt'; - $clientKey = '/etc/ssl/lxdapp/client.key'; + + // Paths to client certificate and key for TLS authentication + $clientCert = $_ENV['LXD_CLIENT_CERT'] ?? '/etc/ssl/lxdapp/client.crt'; + $clientKey = $_ENV['LXD_CLIENT_KEY'] ?? '/etc/ssl/lxdapp/client.key'; - //curl_setopt($ch, CURLOPT_UNIX_SOCKET_PATH, $this->socketPath); - // Specify your client certificate and key for TLS authentication curl_setopt($ch, CURLOPT_SSLCERT, $clientCert); curl_setopt($ch, CURLOPT_SSLKEY, $clientKey); - // For testing only: disable peer verification (use carefully in production!) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - - // curl_setopt($ch, CURLOPT_URL, "{$this->baseUrl}{$endpoint}"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); @@ -59,15 +70,12 @@ class LxdService return $json; } - public function getStatus(string $name): ?array - { - try { - return $this->request('GET', "/1.0/instances/$name/state"); - } catch (\Throwable $e) { - return null; - } - } - + /** + * Retrieves a container. + * + * @param string $name Container name + * @return array|null Container or null if an error occurs + */ public function getContainer(string $name) { try { return $this->request('GET', "/1.0/instances/$name"); @@ -76,6 +84,12 @@ class LxdService } } + /** + * Retrieves the status of a container. + * + * @param string $name Container name + * @return array|null Container status or null if an error occurs + */ public function getContainerState(string $name) { try { return $this->request('GET', "/1.0/instances/$name/state"); @@ -84,10 +98,23 @@ class LxdService } } + /** + * Checks if a container exists. + * + * @param string $name Container name + * @return bool True if the container exists, false otherwise + */ public function containerExists(string $name): bool { return $this->getContainerState($name) !== null; } + /** + * Starts a container. + * + * @param string $name Container name + * @return array Response from the API + * @throws Exception + */ public function startContainer(string $name): array { $startResponse = $this->request('PUT', "/1.0/instances/$name/state", [ "action" => "start", @@ -117,6 +144,12 @@ class LxdService return $containerResponse['metadata'] ?? []; } + /** + * Stops a container. + * + * @param string $name Container name + * @return array Response from the API + */ public function stopContainer(string $name): array { $response = $this->request('PUT', "/1.0/instances/$name/state", [ "action" => "stop", @@ -126,19 +159,32 @@ class LxdService return $response; } - - public function createContainer(string $name, string $fingerprint = "2edfd84b1396"): array { + + /** + * Creates a new container. + * + * @param string $name Container name + * @param string $fingerprint Image fingerprint + * @return array Response from the API + */ + public function createContainer(string $name): array { $response = $this->request('POST', "/1.0/instances", [ "name" => $name, "source" => [ "type" => "image", - "fingerprint" => $fingerprint + "fingerprint" => $this->imageFingerprint ] ]); sleep(5); return $response; } + /** + * Installs packages inside a container. + * + * @param string $name Container name + * @throws Exception + */ public function installPackages(string $name) { $log = ''; @@ -152,49 +198,44 @@ class LxdService file_put_contents('/tmp/lxd_install.log', $log); - // Wait for nginx service to be active - $nginxActive = false; - for ($i = 0; $i < 10; $i++) { - $status = shell_exec("/snap/bin/lxc exec $name -- systemctl is-active nginx"); - if (trim($status) === 'active') { - $nginxActive = true; - break; - } - sleep(2); // wait 2 seconds before retry - } + // Wait for services to start + $this->waitForService($name, 'nginx'); + $this->waitForService($name, 'mysql'); + } - // Wait for mysql service to be active - $mysqlActive = false; + /** + * Waits for a service to become active inside a container. + * + * @param string $name Container name + * @param string $service Service name + * @throws Exception + */ + private function waitForService(string $name, string $service) + { for ($i = 0; $i < 10; $i++) { - $status = shell_exec("/snap/bin/lxc exec $name -- systemctl is-active mysql"); + $status = shell_exec("/snap/bin/lxc exec $name -- systemctl is-active $service"); if (trim($status) === 'active') { - $mysqlActive = true; - break; + return; } sleep(2); } - if (!$nginxActive || !$mysqlActive) { - file_put_contents('/tmp/lxd_install.log', "Service(s) failed to start: nginx active=$nginxActive, mysql active=$mysqlActive\n", FILE_APPEND); - throw new \Exception("Failed to start nginx or mysql inside container $name"); - } - } - - - - public function getContainerIP($name) - { - $container = $this->getStatus($name); - return $this->getIPv4FromMetadata($container['metadata']); + throw new Exception("Failed to start $service inside container $name"); } + /** + * Creates a new container and wait for start. + * + * @param string $name Container name + * @param string $fingerprint Image fingerprint + * @return array Response from the API + */ public function createContainerAndWait(string $name, array $config = []): array { - // 1. Prepare the creation request body $body = [ "name" => $name, "source" => array_merge([ "type" => "image", - "fingerprint" => "2edfd84b1396" + "fingerprint" => $this->imageFingerprint ]), ]; @@ -225,7 +266,27 @@ class LxdService } - public function waitForPort(string $ip, int $port = 80, int $timeout = 30): bool + /** + * Retrieves the IPv4 address of a container. + * + * @param string $name Container name + * @return string|null IPv4 address or null if not found + */ + public function getContainerIP($name) + { + $container = $this->getContainerState($name); + return $this->getIPv4FromMetadata($container['metadata']); + } + + /** + * Waits for a specific port to become available. + * + * @param string $ip IP address + * @param int $port Port number + * @param int $timeout Timeout in seconds + * @return bool True if the port is available, false otherwise + */ + public function waitForPort(string $ip, int $port = 80, int $timeout = 10): bool { $startTime = time(); @@ -244,7 +305,12 @@ class LxdService } - // Function to get IPv4 address from metadata + /** + * Extracts the IPv4 address from container metadata. + * + * @param array $metadata Container metadata + * @return string|null IPv4 address or null if not found + */ public function getIPv4FromMetadata(array $metadata): ?string { if (