2025-09-09 10:52:19 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Actions\Proxy;
|
|
|
|
|
|
2026-03-24 20:32:34 +00:00
|
|
|
use App\Enums\ProxyTypes;
|
2025-09-09 10:52:19 +00:00
|
|
|
use App\Models\Server;
|
|
|
|
|
use App\Services\ProxyDashboardCacheService;
|
2026-03-11 13:11:31 +00:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2025-09-09 10:52:19 +00:00
|
|
|
use Lorisleiva\Actions\Concerns\AsAction;
|
2026-03-24 20:32:34 +00:00
|
|
|
use Symfony\Component\Yaml\Yaml;
|
2025-09-09 10:52:19 +00:00
|
|
|
|
|
|
|
|
class GetProxyConfiguration
|
|
|
|
|
{
|
|
|
|
|
use AsAction;
|
|
|
|
|
|
|
|
|
|
public function handle(Server $server, bool $forceRegenerate = false): string
|
|
|
|
|
{
|
|
|
|
|
$proxyType = $server->proxyType();
|
|
|
|
|
if ($proxyType === 'NONE') {
|
|
|
|
|
return 'OK';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$proxy_configuration = null;
|
|
|
|
|
|
|
|
|
|
if (! $forceRegenerate) {
|
2026-03-11 13:11:31 +00:00
|
|
|
// Primary source: database
|
|
|
|
|
$proxy_configuration = $server->proxy->get('last_saved_proxy_configuration');
|
|
|
|
|
|
2026-03-24 20:32:34 +00:00
|
|
|
// Validate stored config matches current proxy type
|
|
|
|
|
if (! empty(trim($proxy_configuration ?? ''))) {
|
|
|
|
|
if (! $this->configMatchesProxyType($proxyType, $proxy_configuration)) {
|
|
|
|
|
Log::warning('Stored proxy config does not match current proxy type, will regenerate', [
|
|
|
|
|
'server_id' => $server->id,
|
|
|
|
|
'proxy_type' => $proxyType,
|
|
|
|
|
]);
|
|
|
|
|
$proxy_configuration = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:11:31 +00:00
|
|
|
// Backfill: existing servers may not have DB config yet — read from disk once
|
|
|
|
|
if (empty(trim($proxy_configuration ?? ''))) {
|
|
|
|
|
$proxy_configuration = $this->backfillFromDisk($server);
|
|
|
|
|
}
|
2025-09-09 10:52:19 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:11:31 +00:00
|
|
|
// Generate default configuration as last resort
|
2025-09-09 10:52:19 +00:00
|
|
|
if ($forceRegenerate || empty(trim($proxy_configuration ?? ''))) {
|
2025-10-07 09:11:13 +00:00
|
|
|
$custom_commands = [];
|
|
|
|
|
if (! empty(trim($proxy_configuration ?? ''))) {
|
|
|
|
|
$custom_commands = extractCustomProxyCommands($server, $proxy_configuration);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:11:31 +00:00
|
|
|
Log::warning('Proxy configuration regenerated to defaults', [
|
|
|
|
|
'server_id' => $server->id,
|
|
|
|
|
'server_name' => $server->name,
|
|
|
|
|
'reason' => $forceRegenerate ? 'force_regenerate' : 'config_not_found',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-10-07 09:11:13 +00:00
|
|
|
$proxy_configuration = str(generateDefaultProxyConfiguration($server, $custom_commands))->trim()->value();
|
2025-09-09 10:52:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (empty($proxy_configuration)) {
|
|
|
|
|
throw new \Exception('Could not get or generate proxy configuration');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ProxyDashboardCacheService::isTraefikDashboardAvailableFromConfiguration($server, $proxy_configuration);
|
|
|
|
|
|
|
|
|
|
return $proxy_configuration;
|
|
|
|
|
}
|
2026-03-11 13:11:31 +00:00
|
|
|
|
2026-03-24 20:32:34 +00:00
|
|
|
/**
|
|
|
|
|
* Check that the stored docker-compose YAML contains the expected service
|
|
|
|
|
* for the server's current proxy type. Returns false if the config belongs
|
|
|
|
|
* to a different proxy type (e.g. Traefik config on a CADDY server).
|
|
|
|
|
*/
|
|
|
|
|
private function configMatchesProxyType(string $proxyType, string $configuration): bool
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
$yaml = Yaml::parse($configuration);
|
|
|
|
|
$services = data_get($yaml, 'services', []);
|
|
|
|
|
|
|
|
|
|
return match ($proxyType) {
|
|
|
|
|
ProxyTypes::TRAEFIK->value => isset($services['traefik']),
|
|
|
|
|
ProxyTypes::CADDY->value => isset($services['caddy']),
|
|
|
|
|
ProxyTypes::NGINX->value => isset($services['nginx']),
|
|
|
|
|
default => true,
|
|
|
|
|
};
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
// If YAML is unparseable, don't block — let the existing flow handle it
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 13:11:31 +00:00
|
|
|
/**
|
|
|
|
|
* Backfill: read config from disk for servers that predate DB storage.
|
|
|
|
|
* Stores the result in the database so future reads skip SSH entirely.
|
|
|
|
|
*/
|
|
|
|
|
private function backfillFromDisk(Server $server): ?string
|
|
|
|
|
{
|
|
|
|
|
$proxy_path = $server->proxyPath();
|
|
|
|
|
$result = instant_remote_process([
|
|
|
|
|
"mkdir -p $proxy_path",
|
|
|
|
|
"cat $proxy_path/docker-compose.yml 2>/dev/null",
|
|
|
|
|
], $server, false);
|
|
|
|
|
|
|
|
|
|
if (! empty(trim($result ?? ''))) {
|
|
|
|
|
$server->proxy->last_saved_proxy_configuration = $result;
|
|
|
|
|
$server->save();
|
|
|
|
|
|
|
|
|
|
Log::info('Proxy config backfilled to database from disk', [
|
|
|
|
|
'server_id' => $server->id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-09-09 10:52:19 +00:00
|
|
|
}
|