Files
ladill-hosting/app/Http/Controllers/Hosting/ServerPanelController.php
T
isaaccladandCursor e251a4cf60
Deploy Ladill Hosting / deploy (push) Failing after 17s
Initial Ladill Hosting app with Gitea deploy pipeline.
Shared web hosting extracted from the platform monolith, with CI deploy
to /var/www/ladill-hosting matching Bird/Domains/Email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 16:24:20 +00:00

300 lines
12 KiB
PHP

<?php
namespace App\Http\Controllers\Hosting;
use App\Http\Controllers\Controller;
use App\Models\CustomerHostingOrder;
use App\Services\Hosting\PanelRuntimes\ServerAgentPanelRuntime;
use App\Services\Hosting\ServerAgentBootstrapService;
use App\Services\Hosting\Providers\ContaboProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ServerPanelController extends Controller
{
public function show(Request $request, CustomerHostingOrder $order): View
{
$order = $this->authorizedOrder($request, $order);
$panelRuntime = (array) data_get($order->meta, 'server_panel_runtime', data_get($order->meta, 'server_order.panel_snapshot', []));
if ((bool) ($panelRuntime['managed_stack_candidate'] ?? false)) {
app(ServerAgentBootstrapService::class)->ensureBootstrap($order);
$order->refresh();
$panelRuntime = (array) data_get($order->meta, 'server_panel_runtime', data_get($order->meta, 'server_order.panel_snapshot', []));
}
return view('hosting.server-panel.show', [
'order' => $order,
'panelState' => (array) data_get($order->meta, 'server_panel', []),
'panelRuntime' => $panelRuntime,
'selectionSummary' => (array) data_get($order->meta, 'server_order.selection_summary', []),
'serverAgent' => $order->serverAgent,
'serverTasks' => $order->serverTasks()->latest('id')->limit(12)->get(),
'serverSites' => $order->serverSites()->latest('updated_at')->limit(6)->get(),
'serverDatabases' => $order->serverDatabases()->latest('updated_at')->limit(6)->get(),
'serverFiles' => $order->serverFiles()->latest('last_synced_at')->limit(8)->get(),
]);
}
public function sync(Request $request, CustomerHostingOrder $order, ContaboProvider $provider): RedirectResponse
{
$order = $this->authorizedOrder($request, $order);
$instance = $provider->getInstance($this->providerInstanceId($order));
abort_if($instance === null, 404, 'Server instance not found.');
$this->updateOrderState($order, $instance);
return back()->with('success', 'Server status refreshed.');
}
public function power(Request $request, CustomerHostingOrder $order, string $action, ContaboProvider $provider): RedirectResponse
{
$order = $this->authorizedOrder($request, $order);
abort_unless(in_array($action, ['start', 'stop', 'reboot'], true), 404);
$instanceId = $this->providerInstanceId($order);
$success = match ($action) {
'start' => $provider->startInstance($instanceId),
'stop' => $provider->stopInstance($instanceId),
'reboot' => $provider->rebootInstance($instanceId),
};
if (! $success) {
return back()->with('error', 'Server action failed. Please try again.');
}
$meta = (array) ($order->meta ?? []);
$panelState = (array) ($meta['server_panel'] ?? []);
$panelState['last_action'] = [
'name' => $action,
'at' => now()->toIso8601String(),
];
$panelState['power_status'] = match ($action) {
'stop' => 'off',
default => 'on',
};
$order->update([
'meta' => array_merge($meta, ['server_panel' => $panelState]),
]);
return back()->with('success', ucfirst($action).' request sent successfully.');
}
public function queueDomain(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'domain' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]+\.[a-zA-Z]{2,}$/'],
'document_root' => ['nullable', 'string', 'max:255'],
'php_version' => ['nullable', 'string', 'in:8.0,8.1,8.2,8.3,8.4'],
]);
$runtime->queueDomainTask(
$order,
$validated['domain'],
$validated['document_root'] ?? null,
$validated['php_version'] ?? null,
);
return back()->with('success', 'Domain task queued for the server agent.');
}
public function queueDatabase(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'name' => ['required', 'string', 'max:32', 'regex:/^[a-zA-Z0-9_]+$/'],
]);
$runtime->queueDatabaseTask($order, $validated['name']);
return back()->with('success', 'Database task queued for the server agent.');
}
public function queueSsl(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'domain' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]+\.[a-zA-Z]{2,}$/'],
]);
$runtime->queueSslTask($order, $validated['domain']);
return back()->with('success', 'SSL task queued for the server agent.');
}
public function queueFile(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'operation' => ['required', 'string', 'in:list,read,write'],
'path' => ['required', 'string', 'max:1000'],
'content' => ['nullable', 'string'],
]);
$runtime->queueFileTask(
$order,
$validated['operation'],
$validated['path'],
$validated['content'] ?? null,
);
return back()->with('success', 'File task queued for the server agent.');
}
public function queueSiteDelete(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'domain' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]+\.[a-zA-Z]{2,}$/'],
]);
$runtime->queueSiteDeleteTask($order, $validated['domain']);
return back()->with('success', 'Site removal task queued for the server agent.');
}
public function queuePhp(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'domain' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]+\.[a-zA-Z]{2,}$/'],
'php_version' => ['required', 'string', 'in:8.0,8.1,8.2,8.3,8.4'],
]);
$runtime->queuePhpTask($order, $validated['domain'], $validated['php_version']);
return back()->with('success', 'PHP configuration task queued for the server agent.');
}
public function queueCron(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'operation' => ['required', 'string', 'in:list,upsert,remove'],
'name' => ['nullable', 'string', 'max:80', 'regex:/^[a-zA-Z0-9._-]+$/'],
'expression' => ['nullable', 'string', 'max:120'],
'command' => ['nullable', 'string', 'max:1000'],
'user' => ['nullable', 'string', 'in:web,root,www-data,nginx,wwwrun,apache'],
]);
if (($validated['operation'] ?? '') !== 'list') {
$request->validate([
'name' => ['required', 'string', 'max:80', 'regex:/^[a-zA-Z0-9._-]+$/'],
]);
}
if (($validated['operation'] ?? '') === 'upsert') {
$request->validate([
'expression' => ['required', 'string', 'max:120'],
'command' => ['required', 'string', 'max:1000'],
]);
}
$runtime->queueCronTask(
$order,
$validated['operation'],
$validated['name'] ?? null,
$validated['expression'] ?? null,
$validated['command'] ?? null,
$validated['user'] ?? 'web',
);
return back()->with('success', 'Cron task queued for the server agent.');
}
public function queueLog(Request $request, CustomerHostingOrder $order, ServerAgentPanelRuntime $runtime): RedirectResponse
{
$order = $this->authorizedManagedOrder($request, $order);
$validated = $request->validate([
'target' => ['required', 'string', 'in:nginx_access,nginx_error,php_fpm,mariadb,agent_service'],
'domain' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/'],
'lines' => ['nullable', 'integer', 'min:20', 'max:500'],
]);
if (in_array($validated['target'], ['nginx_access', 'nginx_error'], true)) {
$request->validate([
'domain' => ['required', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/'],
]);
}
$runtime->queueLogTask(
$order,
$validated['target'],
$validated['domain'] ?? null,
(int) ($validated['lines'] ?? 120),
);
return back()->with('success', 'Log read task queued for the server agent.');
}
private function authorizedOrder(Request $request, CustomerHostingOrder $order): CustomerHostingOrder
{
abort_if($order->user_id !== $request->user()->id, 403);
$order->load('product');
abort_unless($order->product?->requiresContabo(), 404);
abort_unless($this->ladillPanelEnabled($order), 404);
return $order;
}
private function ladillPanelEnabled(CustomerHostingOrder $order): bool
{
$license = (string) data_get($order->meta, 'server_order.selection.license', '');
return (string) data_get(config('hosting.server_order.licenses'), $license.'.panel') === 'ladill';
}
private function providerInstanceId(CustomerHostingOrder $order): string
{
$instanceId = (string) data_get($order->meta, 'contabo_instance_id', '');
abort_if($instanceId === '', 404, 'Server instance is not provisioned yet.');
return $instanceId;
}
/**
* @param array<string, mixed> $instance
*/
private function updateOrderState(CustomerHostingOrder $order, array $instance): void
{
$meta = (array) ($order->meta ?? []);
$panelState = (array) ($meta['server_panel'] ?? []);
$panelState = array_merge($panelState, [
'instance_status' => $instance['status'] ?? null,
'power_status' => $instance['power_status'] ?? null,
'region' => $instance['region'] ?? null,
'datacenter' => $instance['datacenter'] ?? null,
'image' => $instance['image'] ?? null,
'last_synced_at' => now()->toIso8601String(),
]);
$order->update([
'server_ip' => $instance['ip_address'] ?? $order->server_ip,
'meta' => array_merge($meta, ['server_panel' => $panelState]),
]);
}
private function authorizedManagedOrder(Request $request, CustomerHostingOrder $order): CustomerHostingOrder
{
$order = $this->authorizedOrder($request, $order);
abort_unless((bool) data_get($order->meta, 'server_panel_runtime.managed_stack_candidate', false), 404);
return $order;
}
}