feat(application): make ports_exposes optional for portless apps (#9182)
This commit is contained in:
commit
bb1a82df5d
19 changed files with 102 additions and 71 deletions
|
|
@ -253,7 +253,7 @@ private function restoreCoolifyDbBackup()
|
|||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
'database_id' => $database->id,
|
||||
'database_type' => \App\Models\StandalonePostgresql::class,
|
||||
'database_type' => StandalonePostgresql::class,
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ public function applications(Request $request)
|
|||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack'],
|
||||
properties: [
|
||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||
|
|
@ -312,7 +312,7 @@ public function create_public_application(Request $request)
|
|||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack'],
|
||||
properties: [
|
||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||
|
|
@ -478,7 +478,7 @@ public function create_private_gh_app_application(Request $request)
|
|||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack'],
|
||||
properties: [
|
||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||
|
|
@ -781,7 +781,7 @@ public function create_dockerfile_application(Request $request)
|
|||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name', 'ports_exposes'],
|
||||
required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name'],
|
||||
properties: [
|
||||
'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
|
||||
'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
|
||||
|
|
@ -1024,7 +1024,7 @@ private function create_application(Request $request, $type)
|
|||
'git_repository' => ['string', 'required', new ValidGitRepositoryUrl],
|
||||
'git_branch' => ['string', 'required', new ValidGitBranch],
|
||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
'docker_compose_domains.*' => 'array:name,domain',
|
||||
'docker_compose_domains.*.name' => 'string|required',
|
||||
|
|
@ -1230,7 +1230,7 @@ private function create_application(Request $request, $type)
|
|||
'git_repository' => 'string|required',
|
||||
'git_branch' => ['string', 'required', new ValidGitBranch],
|
||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable',
|
||||
'github_app_uuid' => 'string|required',
|
||||
'watch_paths' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
|
|
@ -1470,7 +1470,7 @@ private function create_application(Request $request, $type)
|
|||
'git_repository' => ['string', 'required', new ValidGitRepositoryUrl],
|
||||
'git_branch' => ['string', 'required', new ValidGitBranch],
|
||||
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable',
|
||||
'private_key_uuid' => 'string|required',
|
||||
'watch_paths' => 'string|nullable',
|
||||
'docker_compose_domains' => 'array|nullable',
|
||||
|
|
@ -1793,7 +1793,7 @@ private function create_application(Request $request, $type)
|
|||
$validationRules = [
|
||||
'docker_registry_image_name' => ['required', 'string', 'max:255', new DockerImageFormat],
|
||||
'docker_registry_image_tag' => ValidationPatterns::dockerImageTagRules(),
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
|
||||
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|nullable',
|
||||
];
|
||||
$validationRules = array_merge(sharedDataApplications(), $validationRules);
|
||||
$validator = customApiValidator($request->all(), $validationRules);
|
||||
|
|
|
|||
|
|
@ -1388,7 +1388,7 @@ private function generate_runtime_environment_variables()
|
|||
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($this->build_pack !== 'dockercompose') {
|
||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty() && ! empty($ports)) {
|
||||
$envs->push("PORT={$ports[0]}");
|
||||
}
|
||||
}
|
||||
|
|
@ -3138,7 +3138,7 @@ private function generate_compose_file()
|
|||
'image' => $this->production_image_name,
|
||||
'container_name' => $this->container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'expose' => $ports,
|
||||
...(! empty($ports) ? ['expose' => $ports] : []),
|
||||
'networks' => [
|
||||
$this->destination->network => [
|
||||
'aliases' => array_merge(
|
||||
|
|
@ -3170,16 +3170,19 @@ private function generate_compose_file()
|
|||
// If custom_healthcheck_found is true, the Dockerfile's HEALTHCHECK will be used
|
||||
// If healthcheck is disabled, no healthcheck will be added
|
||||
if (! $this->application->custom_healthcheck_found && ! $this->application->isHealthcheckDisabled()) {
|
||||
$docker_compose['services'][$this->container_name]['healthcheck'] = [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generate_healthcheck_commands(),
|
||||
],
|
||||
'interval' => $this->application->health_check_interval.'s',
|
||||
'timeout' => $this->application->health_check_timeout.'s',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period.'s',
|
||||
];
|
||||
$healthcheck_command = $this->generate_healthcheck_commands();
|
||||
if ($healthcheck_command !== null) {
|
||||
$docker_compose['services'][$this->container_name]['healthcheck'] = [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$healthcheck_command,
|
||||
],
|
||||
'interval' => $this->application->health_check_interval.'s',
|
||||
'timeout' => $this->application->health_check_timeout.'s',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period.'s',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (! is_null($this->application->limits_cpuset)) {
|
||||
|
|
@ -3389,7 +3392,11 @@ private function generate_healthcheck_commands()
|
|||
|
||||
// HTTP type healthcheck (default)
|
||||
if (! $this->application->health_check_port) {
|
||||
$health_check_port = (int) $this->application->ports_exposes_array[0];
|
||||
if (! empty($this->application->ports_exposes_array)) {
|
||||
$health_check_port = (int) $this->application->ports_exposes_array[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
$health_check_port = (int) $this->application->health_check_port;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Rules\SafeWebhookUrl;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
|
@ -44,7 +45,7 @@ public function handle(): void
|
|||
{
|
||||
$validator = Validator::make(
|
||||
['webhook_url' => $this->webhookUrl],
|
||||
['webhook_url' => ['required', 'url', new \App\Rules\SafeWebhookUrl]]
|
||||
['webhook_url' => ['required', 'url', new SafeWebhookUrl]]
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ protected function rules(): array
|
|||
'staticImage' => 'required',
|
||||
'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
|
||||
'publishDirectory' => ValidationPatterns::directoryPathRules(),
|
||||
'portsExposes' => ['required', 'string', 'regex:/^(\d+)(,\d+)*$/'],
|
||||
'portsExposes' => ['nullable', 'string', 'regex:/^(\d+)(,\d+)*$/'],
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'customNetworkAliases' => 'nullable',
|
||||
'dockerfile' => 'nullable',
|
||||
|
|
@ -212,7 +212,6 @@ protected function messages(): array
|
|||
'buildPack.required' => 'The Build Pack field is required.',
|
||||
'staticImage.required' => 'The Static Image field is required.',
|
||||
'baseDirectory.required' => 'The Base Directory field is required.',
|
||||
'portsExposes.required' => 'The Exposed Ports field is required.',
|
||||
'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
|
||||
...ValidationPatterns::portMappingMessages(),
|
||||
'isStatic.required' => 'The Static setting is required.',
|
||||
|
|
@ -760,7 +759,7 @@ public function submit($showToaster = true)
|
|||
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString();
|
||||
$this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString() ?: null;
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2346,7 +2346,7 @@ public function setConfig($config)
|
|||
'config.build_pack' => 'required|string',
|
||||
'config.base_directory' => 'required|string',
|
||||
'config.publish_directory' => 'required|string',
|
||||
'config.ports_exposes' => 'required|string',
|
||||
'config.ports_exposes' => 'nullable|string',
|
||||
'config.settings.is_static' => 'required|boolean',
|
||||
]);
|
||||
if ($deepValidator->fails()) {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function format_docker_command_output_to_json($rawOutput): Collection
|
|||
return $outputLines
|
||||
->reject(fn ($line) => empty($line))
|
||||
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
|
||||
} catch (\Throwable) {
|
||||
} catch (Throwable) {
|
||||
return collect([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ function format_docker_envs_to_json($rawOutput)
|
|||
|
||||
return [$env[0] => $env[1]];
|
||||
});
|
||||
} catch (\Throwable) {
|
||||
} catch (Throwable) {
|
||||
return collect([]);
|
||||
}
|
||||
}
|
||||
|
|
@ -255,12 +255,12 @@ function defaultLabels($id, $name, string $projectName, string $resourceName, st
|
|||
|
||||
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
{
|
||||
if ($resource->getMorphClass() === \App\Models\ServiceApplication::class) {
|
||||
if ($resource->getMorphClass() === ServiceApplication::class) {
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
$server = data_get($resource, 'service.server');
|
||||
$environment_variables = data_get($resource, 'service.environment_variables');
|
||||
$type = $resource->serviceType();
|
||||
} elseif ($resource->getMorphClass() === \App\Models\Application::class) {
|
||||
} elseif ($resource->getMorphClass() === Application::class) {
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
$server = data_get($resource, 'destination.server');
|
||||
$environment_variables = data_get($resource, 'environment_variables');
|
||||
|
|
@ -641,7 +641,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
} catch (Throwable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -1221,7 +1221,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
|
|||
$server = Server::ownedByCurrentTeam()->find($server_id);
|
||||
try {
|
||||
if (! $server) {
|
||||
throw new \Exception('Server not found');
|
||||
throw new Exception('Server not found');
|
||||
}
|
||||
$yaml_compose = Yaml::parse($compose);
|
||||
|
||||
|
|
@ -1237,7 +1237,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
|
|||
], $server);
|
||||
|
||||
return 'OK';
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
return $e->getMessage();
|
||||
} finally {
|
||||
if (filled($server)) {
|
||||
|
|
@ -1353,10 +1353,10 @@ function escapeBashDoubleQuoted(?string $value): string
|
|||
* Generate Docker build arguments from environment variables collection
|
||||
* Returns only keys (no values) since values are sourced from environment via export
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||
* @return \Illuminate\Support\Collection Collection of formatted --build-arg strings (keys only)
|
||||
* @param Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||
* @return Collection Collection of formatted --build-arg strings (keys only)
|
||||
*/
|
||||
function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
|
||||
function generateDockerBuildArgs($variables): Collection
|
||||
{
|
||||
$variables = collect($variables);
|
||||
|
||||
|
|
@ -1371,7 +1371,7 @@ function generateDockerBuildArgs($variables): \Illuminate\Support\Collection
|
|||
/**
|
||||
* Generate Docker environment flags from environment variables collection
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||
* @param Collection|array $variables Collection of variables with 'key', 'value', and optionally 'is_multiline'
|
||||
* @return string Space-separated environment flags
|
||||
*/
|
||||
function generateDockerEnvFlags($variables): string
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ function connectProxyToNetworks(Server $server)
|
|||
* This must be called BEFORE docker compose up since the compose file declares networks as external.
|
||||
*
|
||||
* @param Server $server The server to ensure networks on
|
||||
* @return \Illuminate\Support\Collection Commands to create networks if they don't exist
|
||||
* @return Collection Commands to create networks if they don't exist
|
||||
*/
|
||||
function ensureProxyNetworksExist(Server $server)
|
||||
{
|
||||
|
|
@ -215,7 +216,7 @@ function extractCustomProxyCommands(Server $server, string $existing_config): ar
|
|||
$custom_commands[] = $command;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
// If we can't parse the config, return empty array
|
||||
// Silently fail to avoid breaking the proxy regeneration
|
||||
}
|
||||
|
|
@ -436,7 +437,7 @@ function getExactTraefikVersionFromContainer(Server $server): ?string
|
|||
Log::debug("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Could not detect exact version");
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
Log::error("getExactTraefikVersionFromContainer: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
|
||||
|
||||
return null;
|
||||
|
|
@ -483,7 +484,7 @@ function getTraefikVersionFromDockerCompose(Server $server): ?string
|
|||
Log::debug("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Image format doesn't match expected pattern: {$image}");
|
||||
|
||||
return null;
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
Log::error("getTraefikVersionFromDockerCompose: Server '{$server->name}' (ID: {$server->id}) - Error: ".$e->getMessage());
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
|||
$timestamp = Carbon::parse(data_get($i, 'timestamp'));
|
||||
try {
|
||||
$timestamp->setTimezone($serverTimezone);
|
||||
} catch (\Exception) {
|
||||
} catch (Exception) {
|
||||
$timestamp->setTimezone('UTC');
|
||||
}
|
||||
data_set($i, 'timestamp', $timestamp->format('Y-M-d H:i:s.u'));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Stevebauman\Purify\Cache\CacheDefinitionCache;
|
||||
use Stevebauman\Purify\Definitions\Html5Definition;
|
||||
|
||||
return [
|
||||
|
|
@ -114,7 +115,7 @@
|
|||
|
||||
'serializer' => [
|
||||
'driver' => env('CACHE_STORE', env('CACHE_DRIVER', 'file')),
|
||||
'cache' => \Stevebauman\Purify\Cache\CacheDefinitionCache::class,
|
||||
'cache' => CacheDefinitionCache::class,
|
||||
],
|
||||
|
||||
// 'serializer' => [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->string('ports_exposes')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->string('ports_exposes')->nullable(false)->default('')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -35,7 +35,7 @@ public function run(): void
|
|||
]);
|
||||
|
||||
// Add predefined server variables to all existing servers
|
||||
$servers = \App\Models\Server::all();
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
SharedEnvironmentVariable::firstOrCreate([
|
||||
'key' => 'COOLIFY_SERVER_UUID',
|
||||
|
|
|
|||
12
openapi.json
12
openapi.json
|
|
@ -79,8 +79,7 @@
|
|||
"environment_uuid",
|
||||
"git_repository",
|
||||
"git_branch",
|
||||
"build_pack",
|
||||
"ports_exposes"
|
||||
"build_pack"
|
||||
],
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
|
|
@ -526,8 +525,7 @@
|
|||
"github_app_uuid",
|
||||
"git_repository",
|
||||
"git_branch",
|
||||
"build_pack",
|
||||
"ports_exposes"
|
||||
"build_pack"
|
||||
],
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
|
|
@ -977,8 +975,7 @@
|
|||
"private_key_uuid",
|
||||
"git_repository",
|
||||
"git_branch",
|
||||
"build_pack",
|
||||
"ports_exposes"
|
||||
"build_pack"
|
||||
],
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
|
|
@ -1775,8 +1772,7 @@
|
|||
"server_uuid",
|
||||
"environment_name",
|
||||
"environment_uuid",
|
||||
"docker_registry_image_name",
|
||||
"ports_exposes"
|
||||
"docker_registry_image_name"
|
||||
],
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ paths:
|
|||
- git_repository
|
||||
- git_branch
|
||||
- build_pack
|
||||
- ports_exposes
|
||||
properties:
|
||||
project_uuid:
|
||||
type: string
|
||||
|
|
@ -344,7 +343,6 @@ paths:
|
|||
- git_repository
|
||||
- git_branch
|
||||
- build_pack
|
||||
- ports_exposes
|
||||
properties:
|
||||
project_uuid:
|
||||
type: string
|
||||
|
|
@ -632,7 +630,6 @@ paths:
|
|||
- git_repository
|
||||
- git_branch
|
||||
- build_pack
|
||||
- ports_exposes
|
||||
properties:
|
||||
project_uuid:
|
||||
type: string
|
||||
|
|
@ -1141,7 +1138,6 @@ paths:
|
|||
- environment_name
|
||||
- environment_uuid
|
||||
- docker_registry_image_name
|
||||
- ports_exposes
|
||||
properties:
|
||||
project_uuid:
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -500,6 +500,13 @@ class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blu
|
|||
</div>
|
||||
@endif
|
||||
@endif
|
||||
@if ((empty($portsExposes) || $portsExposes === '0') && !empty($fqdn))
|
||||
<x-callout type="info" title="No ports exposed" class="mb-4">
|
||||
This application does not expose any ports and will not be reachable through the proxy or your domains.
|
||||
This behavior is normal for background workers, bots, or scheduled tasks.
|
||||
If your application needs to handle HTTP traffic, please specify the port(s) it listens on.
|
||||
</x-callout>
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
@if ($isStatic || $buildPack === 'static')
|
||||
<x-forms.input id="portsExposes" label="Ports Exposes" readonly
|
||||
|
|
@ -510,7 +517,7 @@ class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blu
|
|||
helper="Readonly labels are disabled. You can set the ports manually in the labels section."
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
<x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes" required
|
||||
<x-forms.input placeholder="3000,3001" id="portsExposes" label="Ports Exposes"
|
||||
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly."
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -3640,7 +3640,7 @@
|
|||
"owncloud": {
|
||||
"documentation": "https://owncloud.com/docs-guides/?utm_source=coolify.io",
|
||||
"slogan": "OwnCloud with Open Web UI integrates file management with a powerful, user-friendly interface.",
|
||||
"compose": "c2VydmljZXM6CiAgb3duY2xvdWQ6CiAgICBpbWFnZTogJ293bmNsb3VkL3NlcnZlcjpsYXRlc3QnCiAgICBkZXBlbmRzX29uOgogICAgICBtYXJpYWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9PV05DTE9VRF84MDgwCiAgICAgIC0gJ09XTkNMT1VEX0RPTUFJTj0ke1NFUlZJQ0VfVVJMX09XTkNMT1VEfScKICAgICAgLSAnT1dOQ0xPVURfVFJVU1RFRF9ET01BSU5TPSR7U0VSVklDRV9VUkxfT1dOQ0xPVUR9JwogICAgICAtIE9XTkNMT1VEX0RCX1RZUEU9bXlzcWwKICAgICAgLSBPV05DTE9VRF9EQl9IT1NUPW1hcmlhZGIKICAgICAgLSAnT1dOQ0xPVURfREJfTkFNRT0ke0RCX05BTUU6LW93bmNsb3VkfScKICAgICAgLSAnT1dOQ0xPVURfREJfVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfTUFSSUFEQn0nCiAgICAgIC0gJ09XTkNMT1VEX0RCX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NQVJJQURCfScKICAgICAgLSAnT1dOQ0xPVURfQURNSU5fVVNFUk5BTUU9JHtTRVJWSUNFX1VTRVJfT1dOQ0xPVUR9JwogICAgICAtICdPV05DTE9VRF9BRE1JTl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfT1dOQ0xPVUR9JwogICAgICAtICdPV05DTE9VRF9NWVNRTF9VVEY4TUI0PSR7TVlTUUxfVVRGOE1CNDotdHJ1ZX0nCiAgICAgIC0gJ09XTkNMT1VEX1JFRElTX0VOQUJMRUQ9JHtSRURJU19FTkFCTEVEOi10cnVlfScKICAgICAgLSBPV05DTE9VRF9SRURJU19IT1NUPXJlZGlzCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL3Vzci9iaW4vaGVhbHRoY2hlY2sKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogNQogICAgdm9sdW1lczoKICAgICAgLSAnb3duY2xvdWQtZGF0YTovbW50L2RhdGEnCiAgbWFyaWFkYjoKICAgIGltYWdlOiAnbWFyaWFkYjpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUFSSUFEQlJPT1R9JwogICAgICAtICdNWVNRTF9VU0VSPSR7U0VSVklDRV9VU0VSX01BUklBREJ9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTUFSSUFEQn0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7REJfTkFNRTotb3duY2xvdWR9JwogICAgICAtIFRaPWF1dG8KICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tY2hhcmFjdGVyLXNldC1zZXJ2ZXI9dXRmOG1iNCcKICAgICAgLSAnLS1jb2xsYXRpb24tc2VydmVyPXV0ZjhtYjRfYmluJwogICAgICAtICctLW1heC1hbGxvd2VkLXBhY2tldD0xMjhNJwogICAgICAtICctLWlubm9kYi1sb2ctZmlsZS1zaXplPTY0TScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBoZWFsdGhjaGVjay5zaAogICAgICAgIC0gJy0tY29ubmVjdCcKICAgICAgICAtICctLWlubm9kYl9pbml0aWFsaXplZCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAnb3duY2xvdWQtbXlzcWwtZGF0YTovdmFyL2xpYi9teXNxbCcKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NicKICAgIGNvbW1hbmQ6CiAgICAgIC0gJy0tZGF0YWJhc2VzJwogICAgICAtICcxJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMTBzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDUK",
|
||||
"compose": "c2VydmljZXM6CiAgb3duY2xvdWQ6CiAgICBpbWFnZTogJ293bmNsb3VkL3NlcnZlcjpsYXRlc3QnCiAgICBkZXBlbmRzX29uOgogICAgICBtYXJpYWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9PV05DTE9VRF84MDgwCiAgICAgIC0gJ09XTkNMT1VEX0RPTUFJTj0ke1NFUlZJQ0VfRlFETl9PV05DTE9VRH0nCiAgICAgIC0gJ09XTkNMT1VEX1RSVVNURURfRE9NQUlOUz0ke1NFUlZJQ0VfRlFETl9PV05DTE9VRH0nCiAgICAgIC0gT1dOQ0xPVURfREJfVFlQRT1teXNxbAogICAgICAtIE9XTkNMT1VEX0RCX0hPU1Q9bWFyaWFkYgogICAgICAtICdPV05DTE9VRF9EQl9OQU1FPSR7REJfTkFNRTotb3duY2xvdWR9JwogICAgICAtICdPV05DTE9VRF9EQl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9NQVJJQURCfScKICAgICAgLSAnT1dOQ0xPVURfREJfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX01BUklBREJ9JwogICAgICAtICdPV05DTE9VRF9BRE1JTl9VU0VSTkFNRT0ke1NFUlZJQ0VfVVNFUl9PV05DTE9VRH0nCiAgICAgIC0gJ09XTkNMT1VEX0FETUlOX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9PV05DTE9VRH0nCiAgICAgIC0gJ09XTkNMT1VEX01ZU1FMX1VURjhNQjQ9JHtNWVNRTF9VVEY4TUI0Oi10cnVlfScKICAgICAgLSAnT1dOQ0xPVURfUkVESVNfRU5BQkxFRD0ke1JFRElTX0VOQUJMRUQ6LXRydWV9JwogICAgICAtIE9XTkNMT1VEX1JFRElTX0hPU1Q9cmVkaXMKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSAvdXNyL2Jpbi9oZWFsdGhjaGVjawogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiA1CiAgICB2b2x1bWVzOgogICAgICAtICdvd25jbG91ZC1kYXRhOi9tbnQvZGF0YScKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NQVJJQURCUk9PVH0nCiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTUFSSUFEQn0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NQVJJQURCfScKICAgICAgLSAnTVlTUUxfREFUQUJBU0U9JHtEQl9OQU1FOi1vd25jbG91ZH0nCiAgICAgIC0gVFo9YXV0bwogICAgY29tbWFuZDoKICAgICAgLSAnLS1jaGFyYWN0ZXItc2V0LXNlcnZlcj11dGY4bWI0JwogICAgICAtICctLWNvbGxhdGlvbi1zZXJ2ZXI9dXRmOG1iNF9iaW4nCiAgICAgIC0gJy0tbWF4LWFsbG93ZWQtcGFja2V0PTEyOE0nCiAgICAgIC0gJy0taW5ub2RiLWxvZy1maWxlLXNpemU9NjRNJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGhlYWx0aGNoZWNrLnNoCiAgICAgICAgLSAnLS1jb25uZWN0JwogICAgICAgIC0gJy0taW5ub2RiX2luaXRpYWxpemVkJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICdvd25jbG91ZC1teXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo2JwogICAgY29tbWFuZDoKICAgICAgLSAnLS1kYXRhYmFzZXMnCiAgICAgIC0gJzEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQo=",
|
||||
"tags": [
|
||||
"owncloud",
|
||||
"file-management",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
'email' => 'username@example.edu',
|
||||
]);
|
||||
|
||||
$provider = \Mockery::mock();
|
||||
$provider = Mockery::mock();
|
||||
$provider->shouldReceive('setConfig')->once()->andReturnSelf();
|
||||
$provider->shouldReceive('with')->once()->with(['hd' => 'example.com'])->andReturnSelf();
|
||||
$provider->shouldReceive('user')->once()->andReturn((object) [
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
'is_registration_enabled' => true,
|
||||
]);
|
||||
|
||||
$provider = \Mockery::mock();
|
||||
$provider = Mockery::mock();
|
||||
$provider->shouldReceive('setConfig')->once()->andReturnSelf();
|
||||
$provider->shouldReceive('with')->once()->with(['hd' => 'example.com'])->andReturnSelf();
|
||||
$provider->shouldReceive('user')->once()->andReturn((object) [
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
Cache::shouldReceive('remember')
|
||||
->once()
|
||||
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
|
||||
->andReturn([
|
||||
'coolify' => [
|
||||
'v4' => [
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
Cache::shouldReceive('remember')
|
||||
->once()
|
||||
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
|
||||
->andReturn(null);
|
||||
|
||||
Livewire::test(Upgrade::class)
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
Cache::shouldReceive('remember')
|
||||
->once()
|
||||
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
|
||||
->andReturn([
|
||||
'coolify' => [
|
||||
'v4' => [
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
|
||||
Cache::shouldReceive('remember')
|
||||
->once()
|
||||
->with('coolify:versions:all', 3600, Mockery::type(\Closure::class))
|
||||
->with('coolify:versions:all', 3600, Mockery::type(Closure::class))
|
||||
->andReturn([
|
||||
'coolify' => [
|
||||
'v4' => [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Proxy\GetProxyConfiguration;
|
||||
use Illuminate\Log\LogManager;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributes;
|
||||
|
|
@ -83,7 +84,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
});
|
||||
|
||||
it('logs warning when regenerating defaults', function () {
|
||||
Log::swap(new \Illuminate\Log\LogManager(app()));
|
||||
Log::swap(new LogManager(app()));
|
||||
Log::spy();
|
||||
|
||||
// No DB config, no disk config — will try to regenerate
|
||||
|
|
@ -94,7 +95,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
// the force regenerate path instead
|
||||
try {
|
||||
GetProxyConfiguration::run($server, forceRegenerate: true);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
// generateDefaultProxyConfiguration may fail without full server setup
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
});
|
||||
|
||||
it('rejects stored Traefik config when proxy type is CADDY', function () {
|
||||
Log::swap(new \Illuminate\Log\LogManager(app()));
|
||||
Log::swap(new LogManager(app()));
|
||||
Log::spy();
|
||||
|
||||
$traefikConfig = "services:\n traefik:\n image: traefik:v3.6\n";
|
||||
|
|
@ -126,7 +127,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
// Both will fail in test env, but the warning log proves mismatch was detected.
|
||||
try {
|
||||
GetProxyConfiguration::run($server);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
// Expected — regeneration requires SSH/full server setup
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +137,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
});
|
||||
|
||||
it('rejects stored Caddy config when proxy type is TRAEFIK', function () {
|
||||
Log::swap(new \Illuminate\Log\LogManager(app()));
|
||||
Log::swap(new LogManager(app()));
|
||||
Log::spy();
|
||||
|
||||
$caddyConfig = "services:\n caddy:\n image: lucaslorentz/caddy-docker-proxy:2.8-alpine\n";
|
||||
|
|
@ -144,7 +145,7 @@ function mockServerWithDbConfig(?string $savedConfig, string $proxyType = 'TRAEF
|
|||
|
||||
try {
|
||||
GetProxyConfiguration::run($server);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (Throwable $e) {
|
||||
// Expected — regeneration requires SSH/full server setup
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue