forked from urvishpatelce/lxd-app
fix: update service api call
This commit is contained in:
@ -15,49 +15,62 @@ class LoginController
|
||||
*/
|
||||
public function index(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
|
||||
try {
|
||||
$mainDomain = $_ENV['MAIN_DOMAIN'] ?? 'lxdapp.local';
|
||||
|
||||
$origin = $request->getHeaderLine('Origin');
|
||||
if (!empty($origin)) {
|
||||
$domain = parse_url($origin, PHP_URL_HOST);
|
||||
} else {
|
||||
$domain = $request->getHeaderLine('Host');
|
||||
}
|
||||
$domain = !empty($origin) ? parse_url($origin, PHP_URL_HOST) : $request->getHeaderLine('Host');
|
||||
|
||||
$configPath = __DIR__ . '/../../config.json';
|
||||
$config = file_exists($configPath) ? json_decode(file_get_contents($configPath), true) : [];
|
||||
$name = $config[$domain] ?? null;
|
||||
$params = (array)$request->getParsedBody();
|
||||
if(!$name){
|
||||
return $this->json($response, [
|
||||
'status' => 'not_found',
|
||||
'message' => 'Container not found',
|
||||
], 404);
|
||||
}
|
||||
$captcha = new PCaptcha();
|
||||
|
||||
if (!$captcha->validate_captcha($params['panswer'])) {
|
||||
$params = (array)$request->getParsedBody();
|
||||
|
||||
if (!$name) {
|
||||
return $this->json($response, [
|
||||
'status' => 'not_found',
|
||||
'message' => 'Container not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$captcha = new PCaptcha();
|
||||
if (!$captcha->validate_captcha($params['panswer'] ?? '')) {
|
||||
return $this->json($response, ['status' => 'error', 'message' => 'Invalid CAPTCHA'], 400);
|
||||
}
|
||||
|
||||
$lxd = new LxdService();
|
||||
|
||||
$status = $lxd->getContainerState($name)['metadata']['status'] ?? 'Stopped';
|
||||
if ($status !== 'Running') {
|
||||
$lxd->startContainer($name);
|
||||
}
|
||||
|
||||
// Write log
|
||||
LogWriterHelper::write($name, $request->getUri());
|
||||
// Log path only (avoid leaking query in logs)
|
||||
$uri = $request->getUri();
|
||||
LogWriterHelper::write($name, $uri->getPath());
|
||||
|
||||
$redirect = '/waiting?name='.$name .'&redirect='.urlencode($params['redirect']);
|
||||
// Login success
|
||||
return $this->json($response, ['status' => 'success', 'message' => 'Container started!', 'redirect' => $redirect]);
|
||||
// ---- NEW: create one-time handoff and store creds server-side ----
|
||||
$handoffId = bin2hex(random_bytes(16));
|
||||
$_SESSION["handoff:$name:$handoffId"] = [
|
||||
'username' => (string)($params['username'] ?? ''),
|
||||
'password' => (string)($params['password'] ?? ''),
|
||||
'created_at' => time(),
|
||||
];
|
||||
|
||||
// sanitize the container path (not a full URL!)
|
||||
$path = $this->sanitizeRedirectPath($params['redirect'] ?? '/login');
|
||||
|
||||
// Client goes to waiting page; when ready it will be sent to the bridge
|
||||
$redirect = '/waiting?name=' . rawurlencode($name)
|
||||
. '&handoff=' . rawurlencode($handoffId)
|
||||
. '&path=' . rawurlencode($path);
|
||||
|
||||
return $this->json($response, [
|
||||
'status' => 'success',
|
||||
'message' => 'Container started!',
|
||||
'redirect' => $redirect
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->json($response, [
|
||||
'status' => 'error',
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
@ -70,7 +83,7 @@ class LoginController
|
||||
|
||||
if (empty($name)) {
|
||||
return $this->json($response, [
|
||||
'status' => 'error',
|
||||
'status' => 'error',
|
||||
'message' => 'Missing container name.',
|
||||
], 400);
|
||||
}
|
||||
@ -80,7 +93,7 @@ class LoginController
|
||||
$state = $lxd->getContainerState($name);
|
||||
if (!$state) {
|
||||
return $this->json($response, [
|
||||
'status' => 'not_found',
|
||||
'status' => 'not_found',
|
||||
'message' => 'Container not found',
|
||||
], 404);
|
||||
}
|
||||
@ -88,46 +101,44 @@ class LoginController
|
||||
$status = $state['metadata']['status'] ?? 'Stopped';
|
||||
if ($status !== 'Running') {
|
||||
return $this->json($response, [
|
||||
'status' => 'starting',
|
||||
'status' => 'starting',
|
||||
'message' => 'Container is not yet running',
|
||||
]);
|
||||
}
|
||||
|
||||
$ip = $lxd->getContainerIP($name);
|
||||
$nginx = $lxd->isServiceRunning($name, 'nginx');
|
||||
$mysql = $lxd->isServiceRunning($name, 'mysql');
|
||||
$nginx = $lxd->getContainerServiceStatus($name, 'nginx');
|
||||
$mysql = $lxd->getContainerServiceStatus($name, 'mysql');
|
||||
|
||||
if ($ip && $nginx === 'active' && $mysql === 'active') {
|
||||
// ---- CHANGED: do NOT return fields/creds here ----
|
||||
return $this->json($response, [
|
||||
'status' => 'ready',
|
||||
'ip' => $ip,
|
||||
'status' => 'ready',
|
||||
'ip' => $ip,
|
||||
'message' => 'Container is ready',
|
||||
]);
|
||||
}
|
||||
|
||||
if($nginx === 'failed'){
|
||||
if ($nginx === 'failed') {
|
||||
return $this->json($response, [
|
||||
'status' => 'failed',
|
||||
'status' => 'failed',
|
||||
'message' => 'Failed to start web server service in container',
|
||||
], 400);
|
||||
}
|
||||
|
||||
if($mysql === 'failed'){
|
||||
if ($mysql === 'failed') {
|
||||
return $this->json($response, [
|
||||
'status' => 'failed',
|
||||
'status' => 'failed',
|
||||
'message' => 'Failed to start mysql service in container',
|
||||
], 400);
|
||||
}
|
||||
|
||||
return $this->json($response, [
|
||||
'status' => 'running',
|
||||
'status' => 'running',
|
||||
'message' => 'Container is running, waiting for services...',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends a JSON response.
|
||||
*/
|
||||
protected function json($response, array $data, int $status = 200)
|
||||
{
|
||||
$payload = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
@ -140,4 +151,20 @@ class LoginController
|
||||
->withStatus($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow only known relative paths (never full URLs)
|
||||
*/
|
||||
private function sanitizeRedirectPath(string $raw): string
|
||||
{
|
||||
// deny absolute URLs
|
||||
if (preg_match('#^https?://#i', $raw)) return '/login';
|
||||
if (!str_starts_with($raw, '/')) return '/login';
|
||||
|
||||
$path = parse_url($raw, PHP_URL_PATH) ?? '/login';
|
||||
|
||||
// allowlist of container paths you expect
|
||||
$allow = ['/', '/login', '/signin'];
|
||||
return in_array($path, $allow, true) ? $path : '/login';
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +59,7 @@ class LxdService
|
||||
}
|
||||
|
||||
$json = json_decode($response, true);
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
throw new Exception("LXD API Error: " . ($json['error'] ?? 'Unknown'));
|
||||
}
|
||||
@ -66,6 +67,43 @@ class LxdService
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GET request and returns raw (non-JSON) response.
|
||||
*
|
||||
* @param string $endpoint API endpoint
|
||||
* @return string Raw response body
|
||||
* @throws Exception
|
||||
*/
|
||||
private function requestRaw(string $endpoint): string {
|
||||
$url = "{$this->baseUrl}{$endpoint}";
|
||||
$ch = curl_init($url);
|
||||
|
||||
$clientCert = $_ENV['LXD_CLIENT_CERT'] ?? '/etc/ssl/lxdapp/client.crt';
|
||||
$clientKey = $_ENV['LXD_CLIENT_KEY'] ?? '/etc/ssl/lxdapp/client.key';
|
||||
|
||||
curl_setopt($ch, CURLOPT_SSLCERT, $clientCert);
|
||||
curl_setopt($ch, CURLOPT_SSLKEY, $clientKey);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false || $response === null) {
|
||||
throw new \Exception("Raw LXD API call failed: $curlError");
|
||||
}
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
throw new \Exception("LXD raw API returned HTTP $httpCode");
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the status of a container.
|
||||
*
|
||||
@ -162,11 +200,48 @@ class LxdService
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isServiceRunning(string $container, string $service): string
|
||||
// public function isServiceRunning(string $container, string $service): string
|
||||
// {
|
||||
// $lxcPath = $_ENV['LXC_PATH'] ?: 'lxc';
|
||||
// $cmd = "$lxcPath exec {$container} -- systemctl is-active {$service} 2>&1";
|
||||
// $output = shell_exec($cmd);
|
||||
// return trim($output);
|
||||
// }
|
||||
|
||||
public function getContainerServiceStatus(string $container, string $service): string
|
||||
{
|
||||
$lxcPath = $_ENV['LXC_PATH'] ?: 'lxc';
|
||||
$cmd = "$lxcPath exec {$container} -- systemctl is-active {$service} 2>&1";
|
||||
$output = shell_exec($cmd);
|
||||
return trim($output);
|
||||
// Step 1: Prepare exec command
|
||||
$execPayload = [
|
||||
"command" => ["systemctl", "is-active", $service],
|
||||
"wait-for-websocket" => false,
|
||||
"record-output" => true,
|
||||
"interactive" => false
|
||||
];
|
||||
|
||||
// Step 2: Start exec operation
|
||||
$execResponse = $this->request('POST', "/1.0/instances/{$container}/exec", $execPayload);
|
||||
$operationUrl = $execResponse['operation'] ?? null;
|
||||
|
||||
if (!$operationUrl) {
|
||||
throw new \Exception("Failed to create exec operation");
|
||||
}
|
||||
|
||||
// Step 3: Wait for operation to complete
|
||||
$waitResponse = $this->request('GET', "{$operationUrl}/wait?timeout=10");
|
||||
$metadata = $waitResponse['metadata']['metadata'] ?? [];
|
||||
|
||||
$outputPaths = $metadata['output'] ?? [];
|
||||
$stdoutPath = $outputPaths['1'] ?? null;
|
||||
|
||||
if (!$stdoutPath) {
|
||||
throw new \Exception("No stdout path returned by LXD");
|
||||
}
|
||||
|
||||
// Step 4: Fetch raw stdout output
|
||||
$rawOutput = $this->requestRaw($stdoutPath);
|
||||
|
||||
return trim($rawOutput); // Expected: 'active', 'inactive', etc.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
11
index.php
11
index.php
@ -41,17 +41,10 @@ if ($state !== 'Running') {
|
||||
redirect($redirectBase);
|
||||
}
|
||||
|
||||
// === Check container status ===
|
||||
$state = $lxd->getContainerState($container)['metadata']['status'] ?? 'Stopped';
|
||||
|
||||
if ($state !== 'Running') {
|
||||
redirect($redirectBase);
|
||||
}
|
||||
|
||||
// === Get container IP ===
|
||||
$ip = $lxd->getContainerIP($container);
|
||||
$nginx = $lxd->isServiceRunning($container, 'nginx');
|
||||
$mysql = $lxd->isServiceRunning($container, 'mysql');
|
||||
$nginx = $lxd->getContainerServiceStatus($container, 'nginx');
|
||||
$mysql = $lxd->getContainerServiceStatus($container, 'mysql');
|
||||
|
||||
if (!$ip || $nginx !== 'active' || $mysql !== 'active') {
|
||||
redirect($waitingPage);
|
||||
|
||||
Reference in New Issue
Block a user