Merge pull request #2789 from coollabsio/next

v4.0.0-beta.308
This commit is contained in:
Andras Bacsai 2024-07-11 13:01:13 +02:00 committed by GitHub
commit d37f63c63c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 1725 additions and 1857 deletions

View File

@ -38,6 +38,12 @@ class StopApplication
} }
} }
} }
if ($application->build_pack === 'dockercompose') {
// remove network
$uuid = $application->uuid;
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
instant_remote_process(["docker network rm {$uuid}"], $server, false);
}
} }
} }
} }

View File

@ -11,11 +11,11 @@ class StartProxy
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $async = true): string|Activity public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) { if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK'; return 'OK';
} }
$commands = collect([]); $commands = collect([]);

View File

@ -11,6 +11,8 @@ class CleanupDocker
public function handle(Server $server, bool $force = true) public function handle(Server $server, bool $force = true)
{ {
// cleanup docker images, containers, and builder caches
if ($force) { if ($force) {
instant_remote_process(['docker image prune -af'], $server, false); instant_remote_process(['docker image prune -af'], $server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
@ -20,5 +22,15 @@ class CleanupDocker
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
instant_remote_process(['docker builder prune -f'], $server, false); instant_remote_process(['docker builder prune -f'], $server, false);
} }
// cleanup networks
$networks = collectDockerNetworksByServer($server);
$proxyNetworks = collectProxyDockerNetworksByServer($server);
$diff = $proxyNetworks->diff($networks);
if ($diff->count() > 0) {
$diff->map(function ($network) use ($server) {
instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
instant_remote_process(["docker network rm $network"], $server);
});
}
} }
} }

View File

@ -19,18 +19,16 @@ class StopService
ray('Stopping service: '.$service->name); ray('Stopping service: '.$service->name);
$applications = $service->applications()->get(); $applications = $service->applications()->get();
foreach ($applications as $application) { foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server); instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
$application->update(['status' => 'exited']); $application->update(['status' => 'exited']);
} }
$dbs = $service->databases()->get(); $dbs = $service->databases()->get();
foreach ($dbs as $db) { foreach ($dbs as $db) {
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server); instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
$db->update(['status' => 'exited']); $db->update(['status' => 'exited']);
} }
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false); instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false); instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
} catch (\Exception $e) { } catch (\Exception $e) {
echo $e->getMessage(); echo $e->getMessage();
ray($e->getMessage()); ray($e->getMessage());

View File

@ -683,6 +683,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) { if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
} }
if ($request->build_pack === 'dockercompose') {
$request->offsetSet('ports_exposes', '80');
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -756,6 +759,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) { if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
} }
if ($request->build_pack === 'dockercompose') {
$request->offsetSet('ports_exposes', '80');
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -847,6 +853,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) { if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
} }
if ($request->build_pack === 'dockercompose') {
$request->offsetSet('ports_exposes', '80');
}
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
'git_repository' => 'string|required', 'git_repository' => 'string|required',
@ -1231,6 +1240,16 @@ class ApplicationsController extends Controller
format: 'uuid', format: 'uuid',
) )
), ),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
], ],
responses: [ responses: [
new OA\Response( new OA\Response(
@ -1264,15 +1283,12 @@ class ApplicationsController extends Controller
public function delete_by_uuid(Request $request) public function delete_by_uuid(Request $request)
{ {
$teamId = getTeamIdFromToken(); $teamId = getTeamIdFromToken();
$cleanup = $request->query->get('cleanup') ?? false; $cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
if (! $request->uuid) {
if ($request->collect()->count() == 0) { return response()->json(['message' => 'UUID is required.'], 404);
return response()->json([
'message' => 'Invalid request.',
], 400);
} }
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -1281,7 +1297,10 @@ class ApplicationsController extends Controller
'message' => 'Application not found', 'message' => 'Application not found',
], 404); ], 404);
} }
DeleteResourceJob::dispatch($application, $cleanup); DeleteResourceJob::dispatch(
resource: $application,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
return response()->json([ return response()->json([
'message' => 'Application deletion request queued.', 'message' => 'Application deletion request queued.',

View File

@ -9,6 +9,7 @@ use App\Actions\Database\StopDatabase;
use App\Actions\Database\StopDatabaseProxy; use App\Actions\Database\StopDatabaseProxy;
use App\Enums\NewDatabaseTypes; use App\Enums\NewDatabaseTypes;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\DeleteResourceJob;
use App\Models\Project; use App\Models\Project;
use App\Models\Server; use App\Models\Server;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -1528,6 +1529,16 @@ class DatabasesController extends Controller
format: 'uuid', format: 'uuid',
) )
), ),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
], ],
responses: [ responses: [
new OA\Response( new OA\Response(
@ -1561,6 +1572,7 @@ class DatabasesController extends Controller
public function delete_by_uuid(Request $request) public function delete_by_uuid(Request $request)
{ {
$teamId = getTeamIdFromToken(); $teamId = getTeamIdFromToken();
$cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
@ -1571,8 +1583,10 @@ class DatabasesController extends Controller
if (! $database) { if (! $database) {
return response()->json(['message' => 'Database not found.'], 404); return response()->json(['message' => 'Database not found.'], 404);
} }
StopDatabase::dispatch($database); DeleteResourceJob::dispatch(
$database->forceDelete(); resource: $database,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
return response()->json([ return response()->json([
'message' => 'Database deletion request queued.', 'message' => 'Database deletion request queued.',

View File

@ -462,7 +462,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->env_filename) { if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true], [executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
); );
@ -516,7 +516,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->env_filename) { if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true], [executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
); );
@ -2034,12 +2034,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->application->build_pack === 'dockerimage') { if ($this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.'); $this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} pull"), 'hidden' => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} build"), 'hidden' => true],
); );
} else { } else {
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true],
); );
} }
$this->application_deployment_queue->addLogEntry('New images built.'); $this->application_deployment_queue->addLogEntry('New images built.');
@ -2050,17 +2050,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->application->build_pack === 'dockerimage') { if ($this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.'); $this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} pull"), 'hidden' => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
); );
} else { } else {
if ($this->use_build_server) { if ($this->use_build_server) {
$this->execute_remote_command( $this->execute_remote_command(
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true], ["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
); );
} else { } else {
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true], [executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
); );
} }
} }

View File

@ -28,14 +28,18 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {} public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
public bool $deleteConfigurations = false,
public bool $deleteVolumes = false) {}
public function handle() public function handle()
{ {
try { try {
$this->resource->forceDelete(); $persistentStorages = collect();
switch ($this->resource->type()) { switch ($this->resource->type()) {
case 'application': case 'application':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopApplication::run($this->resource); StopApplication::run($this->resource);
break; break;
case 'standalone-postgresql': case 'standalone-postgresql':
@ -46,6 +50,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
case 'standalone-keydb': case 'standalone-keydb':
case 'standalone-dragonfly': case 'standalone-dragonfly':
case 'standalone-clickhouse': case 'standalone-clickhouse':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopDatabase::run($this->resource); StopDatabase::run($this->resource);
break; break;
case 'service': case 'service':
@ -53,6 +58,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
DeleteService::run($this->resource); DeleteService::run($this->resource);
break; break;
} }
if ($this->deleteVolumes && $this->resource->type() !== 'service') {
$this->resource?->delete_volumes($persistentStorages);
}
if ($this->deleteConfigurations) { if ($this->deleteConfigurations) {
$this->resource?->delete_configurations(); $this->resource?->delete_configurations();
} }
@ -61,6 +70,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage()); send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
throw $e; throw $e;
} finally { } finally {
$this->resource->forceDelete();
Artisan::queue('cleanup:stucked-resources'); Artisan::queue('cleanup:stucked-resources');
} }
} }

View File

@ -16,6 +16,8 @@ class Danger extends Component
public bool $delete_configurations = true; public bool $delete_configurations = true;
public bool $delete_volumes = true;
public ?string $modalId = null; public ?string $modalId = null;
public function mount() public function mount()
@ -31,7 +33,7 @@ class Danger extends Component
try { try {
// $this->authorize('delete', $this->resource); // $this->authorize('delete', $this->resource);
$this->resource->delete(); $this->resource->delete();
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations); DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
return redirect()->route('project.resource.index', [ return redirect()->route('project.resource.index', [
'project_uuid' => $this->projectUuid, 'project_uuid' => $this->projectUuid,

View File

@ -84,7 +84,7 @@ class Deploy extends Component
try { try {
$this->server->proxy->force_stop = false; $this->server->proxy->force_stop = false;
$this->server->save(); $this->server->save();
$activity = StartProxy::run($this->server); $activity = StartProxy::run($this->server, force: true);
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class); $this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -94,6 +94,7 @@ use Visus\Cuid2\Cuid2;
'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'], 'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'],
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'], 'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
] ]
)] )]
@ -122,17 +123,12 @@ class Application extends BaseModel
ApplicationSetting::create([ ApplicationSetting::create([
'application_id' => $application->id, 'application_id' => $application->id,
]); ]);
$application->compose_parsing_version = '2';
$application->save();
}); });
static::deleting(function ($application) { static::forceDeleting(function ($application) {
$application->update(['fqdn' => null]); $application->update(['fqdn' => null]);
$application->settings()->delete(); $application->settings()->delete();
$storages = $application->persistentStorages()->get();
$server = data_get($application, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$application->persistentStorages()->delete(); $application->persistentStorages()->delete();
$application->environment_variables()->delete(); $application->environment_variables()->delete();
$application->environment_variables_preview()->delete(); $application->environment_variables_preview()->delete();
@ -158,6 +154,23 @@ class Application extends BaseModel
} }
} }
public function delete_volumes(?Collection $persistentStorages)
{
if ($this->build_pack === 'dockercompose') {
$server = data_get($this, 'destination.server');
ray('Deleting volumes');
instant_remote_process(["cd {$this->dirOnServer()} && docker compose down -v"], $server, false);
} else {
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
}
public function additional_servers() public function additional_servers()
{ {
return $this->belongsToMany(Server::class, 'additional_destinations') return $this->belongsToMany(Server::class, 'additional_destinations')
@ -775,6 +788,11 @@ class Application extends BaseModel
return "/artifacts/{$uuid}"; return "/artifacts/{$uuid}";
} }
public function dirOnServer()
{
return application_configuration_dir()."/{$this->uuid}";
}
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false) public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
{ {
$baseDir = $this->generateBaseDir($deployment_uuid); $baseDir = $this->generateBaseDir($deployment_uuid);
@ -897,7 +915,7 @@ class Application extends BaseModel
$commands->push("echo 'Checking out $branch'"); $commands->push("echo 'Checking out $branch'");
} }
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'github') { } elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name"; $branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) { if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
@ -941,7 +959,7 @@ class Application extends BaseModel
$commands->push("echo 'Checking out $branch'"); $commands->push("echo 'Checking out $branch'");
} }
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name); $git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
} elseif ($git_type === 'github') { } elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name"; $branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) { if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'")); $commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));

View File

@ -322,7 +322,7 @@ respond 404
$dynamic_config_path = $this->proxyPath().'/dynamic'; $dynamic_config_path = $this->proxyPath().'/dynamic';
if ($this->proxyType() === 'TRAEFIK_V2') { if ($this->proxyType() === 'TRAEFIK_V2') {
$file = "$dynamic_config_path/coolify.yaml"; $file = "$dynamic_config_path/coolify.yaml";
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) { if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
instant_remote_process([ instant_remote_process([
"rm -f $file", "rm -f $file",
], $this); ], $this);
@ -428,7 +428,7 @@ respond 404
} }
} elseif ($this->proxyType() === 'CADDY') { } elseif ($this->proxyType() === 'CADDY') {
$file = "$dynamic_config_path/coolify.caddy"; $file = "$dynamic_config_path/coolify.caddy";
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) { if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
instant_remote_process([ instant_remote_process([
"rm -f $file", "rm -f $file",
], $this); ], $this);

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -31,16 +32,9 @@ class StandaloneClickhouse extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -91,6 +85,17 @@ class StandaloneClickhouse extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -31,16 +32,9 @@ class StandaloneDragonfly extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$database->scheduledBackups()->delete();
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -91,6 +85,17 @@ class StandaloneDragonfly extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -31,16 +32,9 @@ class StandaloneKeydb extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$database->scheduledBackups()->delete();
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -91,6 +85,17 @@ class StandaloneKeydb extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -31,16 +32,9 @@ class StandaloneMariadb extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -91,6 +85,17 @@ class StandaloneMariadb extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -35,16 +36,9 @@ class StandaloneMongodb extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -95,6 +89,17 @@ class StandaloneMongodb extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -32,16 +33,9 @@ class StandaloneMysql extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -92,6 +86,17 @@ class StandaloneMysql extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -32,16 +33,9 @@ class StandalonePostgresql extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -61,6 +55,18 @@ class StandalonePostgresql extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
ray('Deleting volume: '.$storage->name);
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function isConfigurationChanged(bool $save = false) public function isConfigurationChanged(bool $save = false)
{ {
$newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method; $newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method;

View File

@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@ -27,16 +28,9 @@ class StandaloneRedis extends BaseModel
'is_readonly' => true, 'is_readonly' => true,
]); ]);
}); });
static::deleting(function ($database) { static::forceDeleting(function ($database) {
$database->scheduledBackups()->delete();
$storages = $database->persistentStorages()->get();
$server = data_get($database, 'destination.server');
if ($server) {
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
$database->tags()->detach(); $database->tags()->detach();
}); });
@ -87,6 +81,17 @@ class StandaloneRedis extends BaseModel
} }
} }
public function delete_volumes(Collection $persistentStorages)
{
if ($persistentStorages->count() === 0) {
return;
}
$server = data_get($this, 'destination.server');
foreach ($persistentStorages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
}
}
public function realStatus() public function realStatus()
{ {
return $this->getRawOriginal('status'); return $this->getRawOriginal('status');

View File

@ -5,7 +5,24 @@ use App\Models\Application;
use App\Models\Server; use App\Models\Server;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
function connectProxyToNetworks(Server $server) function collectProxyDockerNetworksByServer(Server $server)
{
if (! $server->isFunctional()) {
return collect();
}
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE') {
return collect();
}
$networks = instant_remote_process(['docker inspect --format="{{json .NetworkSettings.Networks }}" coolify-proxy'], $server, false);
$networks = collect($networks)->map(function ($network) {
return collect(json_decode($network))->keys();
})->flatten()->unique();
return $networks;
}
function collectDockerNetworksByServer(Server $server)
{ {
if ($server->isSwarm()) { if ($server->isSwarm()) {
$networks = collect($server->swarmDockers)->map(function ($docker) { $networks = collect($server->swarmDockers)->map(function ($docker) {
@ -43,6 +60,18 @@ function connectProxyToNetworks(Server $server)
if ($networks->count() === 0) { if ($networks->count() === 0) {
$networks = collect(['coolify-overlay']); $networks = collect(['coolify-overlay']);
} }
} else {
if ($networks->count() === 0) {
$networks = collect(['coolify']);
}
}
return $networks;
}
function connectProxyToNetworks(Server $server)
{
$networks = collectDockerNetworksByServer($server);
if ($server->isSwarm()) {
$commands = $networks->map(function ($network) { $commands = $networks->map(function ($network) {
return [ return [
"echo 'Connecting coolify-proxy to $network network...'", "echo 'Connecting coolify-proxy to $network network...'",
@ -51,9 +80,6 @@ function connectProxyToNetworks(Server $server)
]; ];
}); });
} else { } else {
if ($networks->count() === 0) {
$networks = collect(['coolify']);
}
$commands = $networks->map(function ($network) { $commands = $networks->map(function ($network) {
return [ return [
"echo 'Connecting coolify-proxy to $network network...'", "echo 'Connecting coolify-proxy to $network network...'",

View File

@ -1384,9 +1384,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}"); $parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) { $parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
$found_env = $envs_from_coolify->where('key', $key)->first(); if (! str($value)->startsWith('$')) {
if ($found_env) { $found_env = $envs_from_coolify->where('key', $key)->first();
return $found_env->value; if ($found_env) {
return $found_env->value;
}
} }
return $value; return $value;
@ -1480,128 +1482,263 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$baseName = generateApplicationContainerName($resource, $pull_request_id); $baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName"; $containerName = "$serviceName-$baseName";
if (count($serviceVolumes) > 0) { if ($resource->compose_parsing_version === '1') {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { if (count($serviceVolumes) > 0) {
if (is_string($volume)) { $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
$volume = str($volume); if (is_string($volume)) {
if ($volume->contains(':') && ! $volume->startsWith('/')) { $volume = str($volume);
$name = $volume->before(':'); if ($volume->contains(':') && ! $volume->startsWith('/')) {
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $name);
data_set($topLevelVolumes, $name, $v);
}
}
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':'); $name = $volume->before(':');
$mount = $volume->after(':'); $mount = $volume->after(':');
if ($pull_request_id !== 0) { if ($name->startsWith('.') || $name->startsWith('~')) {
$name = $name."-pr-$pull_request_id"; $dir = base_configuration_dir().'/applications/'.$resource->uuid;
} if ($name->startsWith('.')) {
$volume = str("$name:$mount"); $name = $name->replaceFirst('.', $dir);
} }
} if ($name->startsWith('~')) {
} elseif (is_array($volume)) { $name = $name->replaceFirst('~', $dir);
$source = data_get($volume, 'source'); }
$target = data_get($volume, 'target'); if ($pull_request_id !== 0) {
$read_only = data_get($volume, 'read_only'); $name = $name."-pr-$pull_request_id";
if ($source && $target) { }
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) { $volume = str("$name:$mount");
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else { } else {
data_set($volume, 'source', $source.':'.$target); if ($pull_request_id !== 0) {
} $name = $name."-pr-$pull_request_id";
} else { $volume = str("$name:$mount");
if ($pull_request_id !== 0) { if ($topLevelVolumes->has($name)) {
$source = $source."-pr-$pull_request_id"; $v = $topLevelVolumes->get($name);
} if (data_get($v, 'driver_opts.type') === 'cifs') {
if ($read_only) { // Do nothing
data_set($volume, 'source', $source.':'.$target.':ro'); } else {
} else { if (is_null(data_get($v, 'name'))) {
data_set($volume, 'source', $source.':'.$target); data_set($v, 'name', $name);
} data_set($topLevelVolumes, $name, $v);
if (! str($source)->startsWith('/')) { }
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
} }
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
} }
} else { } else {
$topLevelVolumes->put($source, [ if ($topLevelVolumes->has($name->value())) {
'name' => $source, $v = $topLevelVolumes->get($name->value());
]); if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
}
} elseif (is_array($volume)) {
$source = data_get($volume, 'source');
$target = data_get($volume, 'target');
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
} else {
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
if (! str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
}
}
} else {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
} }
} }
} }
} }
} if (is_array($volume)) {
if (is_array($volume)) { return data_get($volume, 'source');
return data_get($volume, 'source'); }
}
return $volume->value(); return $volume->value();
}); });
data_set($service, 'volumes', $serviceVolumes->toArray()); data_set($service, 'volumes', $serviceVolumes->toArray());
}
} elseif ($resource->compose_parsing_version === '2') {
if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$uuid = $resource->uuid;
$name = $uuid."-$name-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $name);
data_set($topLevelVolumes, $name, $v);
}
}
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
$uuid = $resource->uuid;
$name = str($uuid."-$name");
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
}
} elseif (is_array($volume)) {
$source = data_get($volume, 'source');
$target = data_get($volume, 'target');
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
$uuid = $resource->uuid;
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($pull_request_id === 0) {
$source = $uuid."-$source";
} else {
$source = $uuid."-$source-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
} else {
if ($pull_request_id === 0) {
$source = $uuid."-$source";
} else {
$source = $uuid."-$source-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
if (! str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
}
}
} else {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
}
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
} }
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
@ -2166,7 +2303,7 @@ function ip_match($ip, $cidrs, &$match = null)
return false; return false;
} }
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId, string $uuid) function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null)
{ {
if (is_null($teamId)) { if (is_null($teamId)) {
return response()->json(['error' => 'Team ID is required.'], 400); return response()->json(['error' => 'Team ID is required.'], 400);
@ -2182,8 +2319,12 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId,
return str($domain); return str($domain);
}); });
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid); $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid); $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
if ($uuid) {
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
}
$domainFound = false; $domainFound = false;
foreach ($applications as $app) { foreach ($applications as $app) {
if (is_null($app->fqdn)) { if (is_null($app->fqdn)) {

View File

@ -13,10 +13,10 @@
"doctrine/dbal": "^3.6", "doctrine/dbal": "^3.6",
"guzzlehttp/guzzle": "^7.5.0", "guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0", "laravel/fortify": "^v1.16.0",
"laravel/framework": "^v10.7.1", "laravel/framework": "^v11",
"laravel/horizon": "^5.23.1", "laravel/horizon": "^5.23.1",
"laravel/prompts": "^0.1.6", "laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1", "laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0", "laravel/socialite": "^v5.14.0",
"laravel/tinker": "^v2.8.1", "laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
@ -32,8 +32,7 @@
"poliander/cron": "^3.0", "poliander/cron": "^3.0",
"purplepixie/phpdns": "^2.1", "purplepixie/phpdns": "^2.1",
"pusher/pusher-php-server": "^7.2", "pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0", "sentry/sentry-laravel": "^4.6",
"sentry/sentry-laravel": "^3.4",
"socialiteproviders/microsoft-azure": "^5.1", "socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3", "spatie/laravel-data": "^3.4.3",
@ -48,10 +47,10 @@
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^v1.21.0", "fakerphp/faker": "^v1.21.0",
"laravel/dusk": "^v7.7.0", "laravel/dusk": "^v8.0",
"laravel/pint": "^1.16", "laravel/pint": "^1.16",
"mockery/mockery": "^1.5.1", "mockery/mockery": "^1.5.1",
"nunomaduro/collision": "^v7.4.0", "nunomaduro/collision": "^v8.1",
"pestphp/pest": "^2.16", "pestphp/pest": "^2.16",
"phpstan/phpstan": "^1.10", "phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.0.19", "phpunit/phpunit": "^10.0.19",

2627
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -60,8 +60,9 @@ return [
*/ */
'middleware' => [ 'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
], ],
]; ];

View File

@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.307', 'release' => '4.0.0-beta.308',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.307'; return '4.0.0-beta.308';

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->string('compose_parsing_version')->default('1');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('compose_parsing_version');
});
}
};

30
lang/zh-tw.json Normal file
View File

@ -0,0 +1,30 @@
{
"auth.login": "登入",
"auth.login.azure": "使用 Microsoft 登入",
"auth.login.bitbucket": "使用 Bitbucket 登入",
"auth.login.github": "使用 GitHub 登入",
"auth.login.gitlab": "使用 Gitlab 登入",
"auth.login.google": "使用 Google 登入",
"auth.already_registered": "已經註冊?",
"auth.confirm_password": "確認密碼",
"auth.forgot_password": "忘記密碼",
"auth.forgot_password_send_email": "發送重設密碼電郵",
"auth.register_now": "註冊",
"auth.logout": "登出",
"auth.register": "註冊",
"auth.registration_disabled": "註冊已停用,請聯絡管理員。",
"auth.reset_password": "重設密碼",
"auth.failed": "這些憑證與我們的記錄不符。",
"auth.failed.callback": "無法處理來自登入提供者的回呼。",
"auth.failed.password": "密碼錯誤。",
"auth.failed.email": "找不到該電子郵件地址的使用者。",
"auth.throttle": "登入嘗試次數太多。請在 :seconds 秒後重試。",
"input.name": "名稱",
"input.email": "電子郵件",
"input.password": "密碼",
"input.password.again": "再次輸入密碼",
"input.code": "一次性代碼",
"input.recovery_code": "恢復碼",
"button.save": "儲存",
"repository.url": "<span class='text-helper'>例子</span><br>對於公共代碼倉庫,請使用 <span class='text-helper'>https://...</span>。<br>對於私有代碼倉庫,請使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支將被選擇<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支將被選擇。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支將被選擇。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支將被選擇。"
}

View File

@ -1343,6 +1343,14 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
-
name: cleanup
in: query
description: 'Delete configurations and volumes.'
required: false
schema:
type: boolean
default: true
responses: responses:
'200': '200':
description: 'Application deleted.' description: 'Application deleted.'
@ -1799,6 +1807,14 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
-
name: cleanup
in: query
description: 'Delete configurations and volumes.'
required: false
schema:
type: boolean
default: true
responses: responses:
'200': '200':
description: 'Database deleted.' description: 'Database deleted.'
@ -4026,6 +4042,9 @@ components:
format: date-time format: date-time
nullable: true nullable: true
description: 'The date and time when the application was deleted.' description: 'The date and time when the application was deleted.'
compose_parsing_version:
type: string
description: 'How Coolify parse the compose file.'
type: object type: object
ApplicationDeploymentQueue: ApplicationDeploymentQueue:
description: 'Project model' description: 'Project model'

View File

@ -3,7 +3,7 @@
Notifications | Coolify Notifications | Coolify
</x-slot> </x-slot>
<x-notification.navbar /> <x-notification.navbar />
<form wire:submit='submit' class="flex flex-col gap-4"> <form wire:submit='submit' class="flex flex-col gap-4 pb-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2>Email</h2> <h2>Email</h2>
<x-forms.button type="submit"> <x-forms.button type="submit">
@ -33,7 +33,7 @@
label="Use Hosted Email Service" /> label="Use Hosted Email Service" />
</div> </div>
@else @else
<div class="pb-4 w-96"> <div class="w-96 pb-4">
<x-forms.checkbox disabled id="team.use_instance_email_settings" <x-forms.checkbox disabled id="team.use_instance_email_settings"
label="Use Hosted Email Service (Pro+ subscription required)" /> label="Use Hosted Email Service (Pro+ subscription required)" />
</div> </div>
@ -45,7 +45,7 @@
</div> </div>
@endif @endif
@if (!$team->use_instance_email_settings) @if (!$team->use_instance_email_settings)
<form class="flex flex-col items-end gap-2 pb-4 xl:flex-row" wire:submit='submitFromFields'> <form class="flex flex-col items-end gap-2 pt-4 pb-4 xl:flex-row" wire:submit='submitFromFields'>
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" /> <x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails." <x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
label="From Address" /> label="From Address" />

View File

@ -18,7 +18,12 @@
@endif @endif
SHA: {{ data_get($image, 'tag') }} SHA: {{ data_get($image, 'tag') }}
</div> </div>
<div class="text-xs">{{ data_get($image, 'created_at') }}</div> @php
$date = data_get($image, 'created_at');
$interval = \Illuminate\Support\Carbon::parse($date);
@endphp
<div class="text-xs">{{ $interval->diffForHumans() }}</div>
<div class="text-xs">{{ $date }}</div>
</div> </div>
<div class="flex justify-end p-2"> <div class="flex justify-end p-2">
@if (data_get($image, 'is_current')) @if (data_get($image, 'is_current'))

View File

@ -40,9 +40,6 @@
<livewire:project.resource.environment-select :environments="$project->environments" /> <livewire:project.resource.environment-select :environments="$project->environments" />
</div> </div>
</li> </li>
<li>
</li>
</ol> </ol>
</nav> </nav>
</div> </div>

View File

@ -16,7 +16,7 @@
name, example: <span class='text-helper'>-pr-1</span>" /> name, example: <span class='text-helper'>-pr-1</span>" />
@if ($resource?->build_pack !== 'dockercompose') @if ($resource?->build_pack !== 'dockercompose')
<x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage"> <x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage">
<livewire:project.shared.storages.add :resource="$resource"/> <livewire:project.shared.storages.add :resource="$resource" />
</x-modal-input> </x-modal-input>
@endif @endif
</div> </div>
@ -24,10 +24,12 @@
@if ($resource?->build_pack === 'dockercompose') @if ($resource?->build_pack === 'dockercompose')
<span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose <span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose
file or reload the compose file to reread the storage layout.</span> file or reload the compose file to reread the storage layout.</span>
@else
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
<div class="pt-4">No storage found.</div>
@endif
@endif @endif
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
<div class="pt-4">No storage found.</div>
@endif
@if ($resource->persistentStorages()->get()->count() > 0) @if ($resource->persistentStorages()->get()->count() > 0)
<livewire:project.shared.storages.all :resource="$resource" /> <livewire:project.shared.storages.all :resource="$resource" />
@endif @endif

View File

@ -11,5 +11,6 @@
<h4>Actions</h4> <h4>Actions</h4>
<x-forms.checkbox id="delete_configurations" <x-forms.checkbox id="delete_configurations"
label="Permanently delete configuration files from the server?"></x-forms.checkbox> label="Permanently delete configuration files from the server?"></x-forms.checkbox>
<x-forms.checkbox id="delete_volumes" label="Permanently delete associated volumes?"></x-forms.checkbox>
</x-modal-confirmation> </x-modal-confirmation>
</div> </div>

View File

@ -135,7 +135,7 @@
</div> </div>
@if ($server->isFunctional()) @if ($server->isFunctional())
<h3 class="py-4">Settings</h3> <h3 class="pt-4">Settings</h3>
<div class="flex flex-wrap gap-2 sm:flex-nowrap"> <div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required <x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." /> helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
@ -144,8 +144,8 @@
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required <x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
helper="You can define the maximum duration for a deployment to run before timing it out." /> helper="You can define the maximum duration for a deployment to run before timing it out." />
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2 pt-4 pb-2">
<h3 class="py-4">Sentinel</h3> <h3>Sentinel</h3>
@if ($server->isSentinelEnabled()) @if ($server->isSentinelEnabled())
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
@endif @endif

View File

@ -21,9 +21,9 @@
href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>. href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>.
</div> </div>
@if ($server->proxyType() === 'TRAEFIK_V2') @if ($server->proxyType() === 'TRAEFIK_V2')
<h4 class="pb-4">Traefik</h4> <h4>Traefik</h4>
@elseif ($server->proxyType() === 'CADDY') @elseif ($server->proxyType() === 'CADDY')
<h4 class="pb-4 ">Caddy</h4> <h4>Caddy</h4>
@endif @endif
@if ( @if (
$server->proxy->last_applied_settings && $server->proxy->last_applied_settings &&

View File

@ -11,7 +11,7 @@
</x-slot:content> </x-slot:content>
</x-slide-over> </x-slide-over>
@if (data_get($server, 'proxy.status') === 'running') @if (data_get($server, 'proxy.status') === 'running')
<div class="flex gap-4"> <div class="flex gap-2">
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2') @if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
<button> <button>
<a target="_blank" href="http://{{ $serverIp }}:8080"> <a target="_blank" href="http://{{ $serverIp }}:8080">

View File

@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_MYSQL - GITEA__database__USER=$SERVICE_USER_MYSQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL - GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL
volumes: volumes:
- gitea-data:/var/lib/gitea - gitea-data:/data
- gitea-timezone:/etc/timezone:ro - gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro - gitea-localtime:/etc/localtime:ro
ports: ports:

View File

@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_MYSQL - GITEA__database__USER=$SERVICE_USER_MYSQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL - GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL
volumes: volumes:
- gitea-data:/var/lib/gitea - gitea-data:/data
- gitea-timezone:/etc/timezone:ro - gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro - gitea-localtime:/etc/localtime:ro
ports: ports:

View File

@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_POSTGRESQL - GITEA__database__USER=$SERVICE_USER_POSTGRESQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_POSTGRESQL - GITEA__database__PASSWD=$SERVICE_PASSWORD_POSTGRESQL
volumes: volumes:
- gitea-data:/var/lib/gitea - gitea-data:/data
- gitea-timezone:/etc/timezone:ro - gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro - gitea-localtime:/etc/localtime:ro
ports: ports:

View File

@ -6,31 +6,30 @@
services: services:
plausible: plausible:
image: plausible/analytics:v2.0 image: "ghcr.io/plausible/community-edition:v2.1.0"
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run" command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
environment: environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible - "DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible"
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE_8000 - BASE_URL=$SERVICE_FQDN_PLAUSIBLE
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE - SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
- TOTP_VAULT_KEY=$SERVICE_REALBASE64_TOTP - TOTP_VAULT_KEY=$SERVICE_BASE64_TOTP
depends_on: depends_on:
- plausible_db - plausible_db
- plausible_events_db - plausible_events_db
- mail - mail
mail: mail:
image: bytemark/smtp image: bytemark/smtp
plausible_db: plausible_db:
image: postgres:14-alpine image: "postgres:14-alpine"
volumes: volumes:
- db-data:/var/lib/postgresql/data - "db-data:/var/lib/postgresql/data"
environment: environment:
- POSTGRES_DB=plausible - POSTGRES_DB=plausible
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
plausible_events_db: plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine image: "clickhouse/clickhouse-server:24.3.3.102-alpine"
volumes: volumes:
- type: volume - type: volume
source: event-data source: event-data
@ -39,19 +38,12 @@ services:
source: ./clickhouse/clickhouse-config.xml source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml target: /etc/clickhouse-server/config.d/logging.xml
read_only: true read_only: true
content: >- content: "<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>"
<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>
- type: bind - type: bind
source: ./clickhouse/clickhouse-user-config.xml source: ./clickhouse/clickhouse-user-config.xml
target: /etc/clickhouse-server/users.d/logging.xml target: /etc/clickhouse-server/users.d/logging.xml
read_only: true read_only: true
content: >- content: '<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log remove="remove"/><query_log remove="remove"/><text_log remove="remove"/><trace_log remove="remove"/><metric_log remove="remove"/><asynchronous_metric_log remove="remove"/><session_log remove="remove"/><part_log remove="remove"/></clickhouse>'
<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log
remove="remove"/><query_log remove="remove"/><text_log
remove="remove"/><trace_log remove="remove"/><metric_log
remove="remove"/><asynchronous_metric_log
remove="remove"/><session_log remove="remove"/><part_log
remove="remove"/></clickhouse>
ulimits: ulimits:
nofile: nofile:
soft: 262144 soft: 262144

View File

@ -24,6 +24,8 @@ services:
- STORAGE_ACCESS_KEY=$SERVICE_USER_MINIO - STORAGE_ACCESS_KEY=$SERVICE_USER_MINIO
- STORAGE_SECRET_KEY=$SERVICE_PASSWORD_MINIO - STORAGE_SECRET_KEY=$SERVICE_PASSWORD_MINIO
- STORAGE_USE_SSL=false - STORAGE_USE_SSL=false
- DISABLE_SIGNUPS=$SERVICE_DISABLE_SIGNUPS
- DISABLE_EMAIL_AUTH=$SERVICE_DISABLE_EMAIL_AUTH
depends_on: depends_on:
- postgres - postgres
- minio - minio

View File

@ -617,7 +617,7 @@ services:
- LOGFLARE_NODE_HOST=127.0.0.1 - LOGFLARE_NODE_HOST=127.0.0.1
- DB_USERNAME=supabase_admin - DB_USERNAME=supabase_admin
- DB_DATABASE=${POSTGRES_DB:-postgres} - DB_DATABASE=${POSTGRES_DB:-postgres}
- DB_HOSTNAME=${POSTGRES_HOST:-supabase-db} - DB_HOSTNAME=${POSTGRES_HOSTNAME:-supabase-db}
- DB_PORT=${POSTGRES_PORT:-5432} - DB_PORT=${POSTGRES_PORT:-5432}
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DB_SCHEMA=_analytics - DB_SCHEMA=_analytics
@ -628,7 +628,7 @@ services:
- LOGFLARE_MIN_CLUSTER_SIZE=1 - LOGFLARE_MIN_CLUSTER_SIZE=1
# Comment variables to use Big Query backend for analytics # Comment variables to use Big Query backend for analytics
- POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- POSTGRES_BACKEND_SCHEMA=_analytics - POSTGRES_BACKEND_SCHEMA=_analytics
- LOGFLARE_FEATURE_FLAG_OVERRIDE=multibackend=true - LOGFLARE_FEATURE_FLAG_OVERRIDE=multibackend=true
@ -904,7 +904,7 @@ services:
condition: service_healthy condition: service_healthy
restart: unless-stopped restart: unless-stopped
environment: environment:
- PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public} - PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public}
- PGRST_DB_ANON_ROLE=anon - PGRST_DB_ANON_ROLE=anon
- PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT} - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
@ -940,7 +940,7 @@ services:
- API_EXTERNAL_URL=${API_EXTERNAL_URL:-http://supabase-kong:8000} - API_EXTERNAL_URL=${API_EXTERNAL_URL:-http://supabase-kong:8000}
- GOTRUE_DB_DRIVER=postgres - GOTRUE_DB_DRIVER=postgres
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- GOTRUE_SITE_URL=${SERVICE_FQDN_SUPABASEKONG} - GOTRUE_SITE_URL=${SERVICE_FQDN_SUPABASEKONG}
- GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS} - GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS}
@ -1020,7 +1020,7 @@ services:
retries: 3 retries: 3
environment: environment:
- PORT=4000 - PORT=4000
- DB_HOST=${POSTGRES_HOST:-supabase-db} - DB_HOST=${POSTGRES_HOSTNAME:-supabase-db}
- DB_PORT=${POSTGRES_PORT:-5432} - DB_PORT=${POSTGRES_PORT:-5432}
- DB_USER=supabase_admin - DB_USER=supabase_admin
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES} - DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
@ -1098,7 +1098,7 @@ services:
- SERVER_REGION=local - SERVER_REGION=local
- MULTI_TENANT=false - MULTI_TENANT=false
- AUTH_JWT_SECRET=${SERVICE_PASSWORD_JWT} - AUTH_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- DB_INSTALL_ROLES=false - DB_INSTALL_ROLES=false
- STORAGE_BACKEND=s3 - STORAGE_BACKEND=s3
- STORAGE_S3_BUCKET=stub - STORAGE_S3_BUCKET=stub
@ -1121,7 +1121,7 @@ services:
# - SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogInNlcnZpY2Vfcm9sZSIsCiAgImlzcyI6ICJzdXBhYmFzZSIsCiAgImlhdCI6IDE3MDg5ODg0MDAsCiAgImV4cCI6IDE4NjY4NDEyMDAKfQ.GA7yF2BmqTzqGkP_oqDdJAQVt0djjIxGYuhE0zFDJV4 # - SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogInNlcnZpY2Vfcm9sZSIsCiAgImlzcyI6ICJzdXBhYmFzZSIsCiAgImlhdCI6IDE3MDg5ODg0MDAsCiAgImV4cCI6IDE4NjY4NDEyMDAKfQ.GA7yF2BmqTzqGkP_oqDdJAQVt0djjIxGYuhE0zFDJV4
# - POSTGREST_URL=http://supabase-rest:3000 # - POSTGREST_URL=http://supabase-rest:3000
# - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT} # - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
# - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} # - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# - FILE_SIZE_LIMIT=52428800 # - FILE_SIZE_LIMIT=52428800
# - STORAGE_BACKEND=s3 # - STORAGE_BACKEND=s3
# - STORAGE_S3_BUCKET=stub # - STORAGE_S3_BUCKET=stub
@ -1164,7 +1164,7 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
- PG_META_PORT=8080 - PG_META_PORT=8080
- PG_META_DB_HOST=${POSTGRES_HOST:-supabase-db} - PG_META_DB_HOST=${POSTGRES_HOSTNAME:-supabase-db}
- PG_META_DB_PORT=${POSTGRES_PORT:-5432} - PG_META_DB_PORT=${POSTGRES_PORT:-5432}
- PG_META_DB_NAME=${POSTGRES_DB:-postgres} - PG_META_DB_NAME=${POSTGRES_DB:-postgres}
- PG_META_DB_USER=supabase_admin - PG_META_DB_USER=supabase_admin
@ -1185,7 +1185,7 @@ services:
- SUPABASE_URL=http://supabase-kong:8000 - SUPABASE_URL=http://supabase-kong:8000
- SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY} - SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_SUPABASESERVICE_KEY} - SUPABASE_SERVICE_ROLE_KEY=${SERVICE_SUPABASESERVICE_KEY}
- SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786 # TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
- VERIFY_JWT=${FUNCTIONS_VERIFY_JWT:-false} - VERIFY_JWT=${FUNCTIONS_VERIFY_JWT:-false}
volumes: volumes:

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.307" "version": "4.0.0-beta.308"
} }
} }
} }