Merge branch 'next' into feature/authentik-provider
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
|
||||
$version = include 'config/version.php';
|
||||
echo $version;
|
||||
// To prevent github actions from failing
|
||||
function env()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$version = include 'config/constants.php';
|
||||
echo $version['coolify']['version'] ?: 'unknown';
|
||||
|
@@ -21,14 +21,13 @@ function invalidTokenResponse()
|
||||
function serializeApiResponse($data)
|
||||
{
|
||||
if ($data instanceof Collection) {
|
||||
$data = $data->map(function ($d) {
|
||||
return $data->map(function ($d) {
|
||||
$d = collect($d)->sortKeys();
|
||||
$created_at = data_get($d, 'created_at');
|
||||
$updated_at = data_get($d, 'updated_at');
|
||||
if ($created_at) {
|
||||
unset($d['created_at']);
|
||||
$d['created_at'] = $created_at;
|
||||
|
||||
}
|
||||
if ($updated_at) {
|
||||
unset($d['updated_at']);
|
||||
@@ -50,8 +49,6 @@ function serializeApiResponse($data)
|
||||
|
||||
return $d;
|
||||
});
|
||||
|
||||
return $data;
|
||||
} else {
|
||||
$d = collect($data)->sortKeys();
|
||||
$created_at = data_get($d, 'created_at');
|
||||
@@ -59,7 +56,6 @@ function serializeApiResponse($data)
|
||||
if ($created_at) {
|
||||
unset($d['created_at']);
|
||||
$d['created_at'] = $created_at;
|
||||
|
||||
}
|
||||
if ($updated_at) {
|
||||
unset($d['updated_at']);
|
||||
|
@@ -44,13 +44,13 @@ function queue_application_deployment(Application $application, string $deployme
|
||||
]);
|
||||
|
||||
if ($no_questions_asked) {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onQueue('high');
|
||||
);
|
||||
} elseif (next_queuable($server_id, $application_id)) {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onQueue('high');
|
||||
);
|
||||
}
|
||||
}
|
||||
function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||
@@ -59,9 +59,9 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onQueue('high');
|
||||
);
|
||||
}
|
||||
function queue_next_deployment(Application $application)
|
||||
{
|
||||
@@ -72,9 +72,9 @@ function queue_next_deployment(Application $application)
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
))->onQueue('high');
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool
|
||||
$server = Server::find($server_id);
|
||||
$concurrent_builds = $server->settings->concurrent_builds;
|
||||
|
||||
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
|
||||
// ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
|
||||
|
||||
if ($deployments->count() > $concurrent_builds) {
|
||||
return false;
|
||||
@@ -113,9 +113,9 @@ function next_after_cancel(?Server $server = null)
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $next->id,
|
||||
))->onQueue('high');
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ const SPECIFIC_SERVICES = [
|
||||
|
||||
// Based on /etc/os-release
|
||||
const SUPPORTED_OS = [
|
||||
'ubuntu debian raspbian',
|
||||
'ubuntu debian raspbian pop',
|
||||
'centos fedora rhel ol rocky amzn almalinux',
|
||||
'sles opensuse-leap opensuse-tumbleweed',
|
||||
'arch',
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDocker;
|
||||
@@ -48,7 +49,7 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
|
||||
}
|
||||
$database = new StandaloneRedis;
|
||||
$database->name = generate_database_name('redis');
|
||||
$database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||
$redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||
$database->environment_id = $environment_id;
|
||||
$database->destination_id = $destination->id;
|
||||
$database->destination_type = $destination->getMorphClass();
|
||||
@@ -57,6 +58,20 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
|
||||
}
|
||||
$database->save();
|
||||
|
||||
EnvironmentVariable::create([
|
||||
'key' => 'REDIS_PASSWORD',
|
||||
'value' => $redis_password,
|
||||
'standalone_redis_id' => $database->id,
|
||||
'is_shared' => false,
|
||||
]);
|
||||
|
||||
EnvironmentVariable::create([
|
||||
'key' => 'REDIS_USERNAME',
|
||||
'value' => 'default',
|
||||
'standalone_redis_id' => $database->id,
|
||||
'is_shared' => false,
|
||||
]);
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
|
@@ -32,9 +32,8 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul
|
||||
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
|
||||
return $containers;
|
||||
return $containers->filter();
|
||||
}
|
||||
|
||||
return $containers;
|
||||
@@ -46,9 +45,8 @@ function getCurrentServiceContainerStatus(Server $server, int $id): Collection
|
||||
if (! $server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->filter();
|
||||
|
||||
return $containers;
|
||||
return $containers->filter();
|
||||
}
|
||||
|
||||
return $containers;
|
||||
@@ -67,7 +65,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 $e) {
|
||||
} catch (\Throwable) {
|
||||
return collect([]);
|
||||
}
|
||||
}
|
||||
@@ -104,14 +102,15 @@ function format_docker_envs_to_json($rawOutput)
|
||||
|
||||
return [$env[0] => $env[1]];
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
return collect([]);
|
||||
}
|
||||
}
|
||||
function checkMinimumDockerEngineVersion($dockerVersion)
|
||||
{
|
||||
$majorDockerVersion = str($dockerVersion)->before('.')->value();
|
||||
if ($majorDockerVersion <= 22) {
|
||||
$requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.')->value();
|
||||
if ($majorDockerVersion < $requiredDockerVersion) {
|
||||
$dockerVersion = null;
|
||||
}
|
||||
|
||||
@@ -193,7 +192,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('coolify.managed=true');
|
||||
$labels->push('coolify.version='.config('version'));
|
||||
$labels->push('coolify.version='.config('constants.coolify.version'));
|
||||
$labels->push('coolify.'.$type.'Id='.$id);
|
||||
$labels->push("coolify.type=$type");
|
||||
$labels->push('coolify.name='.$name);
|
||||
@@ -207,12 +206,12 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
||||
}
|
||||
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
{
|
||||
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
|
||||
if ($resource->getMorphClass() === \App\Models\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') {
|
||||
} elseif ($resource->getMorphClass() === \App\Models\Application::class) {
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
$server = data_get($resource, 'destination.server');
|
||||
$environment_variables = data_get($resource, 'environment_variables');
|
||||
@@ -227,16 +226,18 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
case $type?->contains('minio'):
|
||||
$MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
|
||||
$MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first();
|
||||
|
||||
if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) {
|
||||
return $payload;
|
||||
return collect([]);
|
||||
}
|
||||
if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) {
|
||||
$MINIO_BROWSER_REDIRECT_URL?->update([
|
||||
|
||||
if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) {
|
||||
$MINIO_BROWSER_REDIRECT_URL->update([
|
||||
'value' => generateFqdn($server, 'console-'.$uuid, true),
|
||||
]);
|
||||
}
|
||||
if (is_null($MINIO_SERVER_URL?->value)) {
|
||||
$MINIO_SERVER_URL?->update([
|
||||
if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) {
|
||||
$MINIO_SERVER_URL->update([
|
||||
'value' => generateFqdn($server, 'minio-'.$uuid, true),
|
||||
]);
|
||||
}
|
||||
@@ -248,16 +249,18 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
case $type?->contains('logto'):
|
||||
$LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first();
|
||||
$LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first();
|
||||
|
||||
if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) {
|
||||
return $payload;
|
||||
return collect([]);
|
||||
}
|
||||
if (is_null($LOGTO_ENDPOINT?->value)) {
|
||||
$LOGTO_ENDPOINT?->update([
|
||||
|
||||
if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) {
|
||||
$LOGTO_ENDPOINT->update([
|
||||
'value' => generateFqdn($server, 'logto-'.$uuid),
|
||||
]);
|
||||
}
|
||||
if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) {
|
||||
$LOGTO_ADMIN_ENDPOINT?->update([
|
||||
if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) {
|
||||
$LOGTO_ADMIN_ENDPOINT->update([
|
||||
'value' => generateFqdn($server, 'logto-admin-'.$uuid),
|
||||
]);
|
||||
}
|
||||
@@ -279,13 +282,16 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
$labels->push("caddy_ingress_network={$network}");
|
||||
}
|
||||
foreach ($domains as $loop => $domain) {
|
||||
$loop = $loop;
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
$host_without_www = str($host)->replace('www.', '');
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
$handle = 'handle_path';
|
||||
if (! $is_stripprefix_enabled) {
|
||||
$handle = 'handle';
|
||||
}
|
||||
if (is_null($port) && ! is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
}
|
||||
@@ -297,11 +303,11 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
||||
|
||||
if ($port) {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||
$labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||
} else {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}");
|
||||
$labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams}}");
|
||||
}
|
||||
$labels->push("caddy_{$loop}.handle_path={$path}*");
|
||||
$labels->push("caddy_{$loop}.{$handle}={$path}*");
|
||||
if ($is_gzip_enabled) {
|
||||
$labels->push("caddy_{$loop}.encode=zstd gzip");
|
||||
}
|
||||
@@ -335,10 +341,11 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) {
|
||||
return explode(',', $matches[1]);
|
||||
}
|
||||
|
||||
return null;
|
||||
})->flatten()
|
||||
->filter()
|
||||
->unique();
|
||||
->filter()
|
||||
->unique();
|
||||
}
|
||||
foreach ($domains as $loop => $domain) {
|
||||
try {
|
||||
@@ -361,8 +368,11 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$https_label = "https-{$loop}-{$uuid}-{$service_name}";
|
||||
}
|
||||
if (str($image)->contains('ghost')) {
|
||||
$labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)");
|
||||
$labels->push('traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1');
|
||||
$labels->push("traefik.http.middlewares.redir-ghost-{$uuid}.redirectregex.regex=^{$path}/(.*)");
|
||||
$labels->push("traefik.http.middlewares.redir-ghost-{$uuid}.redirectregex.replacement=/$1");
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.handler=rewrite");
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.rewrite.regexp=^{$path}/(.*)");
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.rewrite.replacement=/$1");
|
||||
}
|
||||
|
||||
$to_www_name = "{$loop}-{$uuid}-to-www";
|
||||
@@ -388,7 +398,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
if ($path !== '/') {
|
||||
// Middleware handling
|
||||
$middlewares = collect([]);
|
||||
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
|
||||
if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) {
|
||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
$middlewares->push("{$https_label}-stripprefix");
|
||||
}
|
||||
@@ -396,13 +406,13 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if (str($image)->contains('ghost')) {
|
||||
$middlewares->push('redir-ghost');
|
||||
$middlewares->push("redir-ghost-{$uuid}");
|
||||
}
|
||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||
$labels = $labels->merge($redirect_to_non_www);
|
||||
$middlewares->push($to_non_www_name);
|
||||
}
|
||||
if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) {
|
||||
if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
|
||||
$labels = $labels->merge($redirect_to_www);
|
||||
$middlewares->push($to_www_name);
|
||||
}
|
||||
@@ -417,9 +427,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares = collect([]);
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
}
|
||||
if (str($image)->contains('ghost')) {
|
||||
$middlewares->push('redir-ghost');
|
||||
$middlewares->push("redir-ghost-{$uuid}");
|
||||
}
|
||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||
$labels = $labels->merge($redirect_to_non_www);
|
||||
@@ -468,7 +478,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if (str($image)->contains('ghost')) {
|
||||
$middlewares->push('redir-ghost');
|
||||
$middlewares->push("redir-ghost-{$uuid}");
|
||||
}
|
||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||
$labels = $labels->merge($redirect_to_non_www);
|
||||
@@ -491,7 +501,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if (str($image)->contains('ghost')) {
|
||||
$middlewares->push('redir-ghost');
|
||||
$middlewares->push("redir-ghost-{$uuid}");
|
||||
}
|
||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||
$labels = $labels->merge($redirect_to_non_www);
|
||||
@@ -510,7 +520,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -581,7 +591,6 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if (data_get($preview, 'fqdn')) {
|
||||
@@ -633,7 +642,6 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $labels->all();
|
||||
@@ -658,7 +666,7 @@ function isDatabaseImage(?string $image = null)
|
||||
return false;
|
||||
}
|
||||
|
||||
function convert_docker_run_to_compose(?string $custom_docker_run_options = null)
|
||||
function convertDockerRunToCompose(?string $custom_docker_run_options = null)
|
||||
{
|
||||
$options = [];
|
||||
$compose_options = collect([]);
|
||||
@@ -683,9 +691,17 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
'--privileged' => 'privileged',
|
||||
'--ip' => 'ip',
|
||||
'--shm-size' => 'shm_size',
|
||||
'--gpus' => 'gpus',
|
||||
]);
|
||||
foreach ($matches as $match) {
|
||||
$option = $match[1];
|
||||
if ($option === '--gpus') {
|
||||
$regexForParsingDeviceIds = '/device=([0-9A-Za-z-,]+)/';
|
||||
preg_match($regexForParsingDeviceIds, $custom_docker_run_options, $device_matches);
|
||||
$value = $device_matches[1] ?? 'all';
|
||||
$options[$option][] = $value;
|
||||
$options[$option] = array_unique($options[$option]);
|
||||
}
|
||||
if (isset($match[2]) && $match[2] !== '') {
|
||||
$value = $match[2];
|
||||
$options[$option][] = $value;
|
||||
@@ -698,7 +714,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
$options = collect($options);
|
||||
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||
foreach ($options as $option => $value) {
|
||||
// ray($option,$value);
|
||||
if (! data_get($mapping, $option)) {
|
||||
continue;
|
||||
}
|
||||
@@ -727,6 +742,28 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
if (! is_null($value) && is_array($value) && count($value) > 0) {
|
||||
$compose_options->put($mapping[$option], $value[0]);
|
||||
}
|
||||
} elseif ($option === '--gpus') {
|
||||
$payload = [
|
||||
'driver' => 'nvidia',
|
||||
'capabilities' => ['gpu'],
|
||||
];
|
||||
if (! is_null($value) && is_array($value) && count($value) > 0) {
|
||||
if (str($value[0]) != 'all') {
|
||||
if (str($value[0])->contains(',')) {
|
||||
$payload['device_ids'] = str($value[0])->explode(',')->toArray();
|
||||
} else {
|
||||
$payload['device_ids'] = [$value[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
ray($payload);
|
||||
$compose_options->put('deploy', [
|
||||
'resources' => [
|
||||
'reservations' => [
|
||||
'devices' => [$payload],
|
||||
],
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
if ($list_options->contains($option)) {
|
||||
if ($compose_options->has($mapping[$option])) {
|
||||
@@ -748,7 +785,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
return $compose_options->toArray();
|
||||
}
|
||||
|
||||
function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network)
|
||||
function generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $network)
|
||||
{
|
||||
$ipv4 = data_get($docker_run_options, 'ip.0');
|
||||
$ipv6 = data_get($docker_run_options, 'ip6.0');
|
||||
|
@@ -3,6 +3,7 @@
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\GitlabApp;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use Lcobucci\JWT\Encoding\ChainedFormatter;
|
||||
@@ -16,7 +17,7 @@ function generate_github_installation_token(GithubApp $source)
|
||||
$signingKey = InMemory::plainText($source->privateKey->private_key);
|
||||
$algorithm = new Sha256;
|
||||
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
|
||||
$now = new DateTimeImmutable;
|
||||
$now = CarbonImmutable::now();
|
||||
$now = $now->setTime($now->format('H'), $now->format('i'));
|
||||
$issuedToken = $tokenBuilder
|
||||
->issuedBy($source->app_id)
|
||||
@@ -40,16 +41,15 @@ function generate_github_jwt_token(GithubApp $source)
|
||||
$signingKey = InMemory::plainText($source->privateKey->private_key);
|
||||
$algorithm = new Sha256;
|
||||
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
|
||||
$now = new DateTimeImmutable;
|
||||
$now = CarbonImmutable::now();
|
||||
$now = $now->setTime($now->format('H'), $now->format('i'));
|
||||
$issuedToken = $tokenBuilder
|
||||
|
||||
return $tokenBuilder
|
||||
->issuedBy($source->app_id)
|
||||
->issuedAt($now->modify('-1 minute'))
|
||||
->expiresAt($now->modify('+10 minutes'))
|
||||
->getToken($algorithm, $signingKey)
|
||||
->toString();
|
||||
|
||||
return $issuedToken;
|
||||
}
|
||||
|
||||
function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', ?array $data = null, bool $throwError = true)
|
||||
@@ -57,7 +57,7 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
|
||||
if (is_null($source)) {
|
||||
throw new \Exception('Not implemented yet.');
|
||||
}
|
||||
if ($source->getMorphClass() == 'App\Models\GithubApp') {
|
||||
if ($source->getMorphClass() === \App\Models\GithubApp::class) {
|
||||
if ($source->is_public) {
|
||||
$response = Http::github($source->api_url)->$method($endpoint);
|
||||
} else {
|
||||
|
87
bootstrap/helpers/notifications.php
Normal file
87
bootstrap/helpers/notifications.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Internal\GeneralNotification;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
function is_transactional_emails_enabled(): bool
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
|
||||
return $settings->smtp_enabled || $settings->resend_enabled;
|
||||
}
|
||||
|
||||
function send_internal_notification(string $message): void
|
||||
{
|
||||
try {
|
||||
$team = Team::find(0);
|
||||
$team?->notify(new GeneralNotification($message));
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$type = set_transanctional_email_settings($settings);
|
||||
if (! $type) {
|
||||
throw new Exception('No email settings found.');
|
||||
}
|
||||
if ($cc) {
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($email)
|
||||
->replyTo($email)
|
||||
->cc($cc)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
);
|
||||
} else {
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($email)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string //
|
||||
{
|
||||
if (! $settings) {
|
||||
$settings = instanceSettings();
|
||||
}
|
||||
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
||||
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
||||
if (data_get($settings, 'resend_enabled')) {
|
||||
config()->set('mail.default', 'resend');
|
||||
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
||||
|
||||
return 'resend';
|
||||
}
|
||||
if (data_get($settings, 'smtp_enabled')) {
|
||||
config()->set('mail.default', 'smtp');
|
||||
config()->set('mail.mailers.smtp', [
|
||||
'transport' => 'smtp',
|
||||
'host' => data_get($settings, 'smtp_host'),
|
||||
'port' => data_get($settings, 'smtp_port'),
|
||||
'encryption' => data_get($settings, 'smtp_encryption'),
|
||||
'username' => data_get($settings, 'smtp_username'),
|
||||
'password' => data_get($settings, 'smtp_password'),
|
||||
'timeout' => data_get($settings, 'smtp_timeout'),
|
||||
'local_domain' => null,
|
||||
]);
|
||||
|
||||
return 'smtp';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
@@ -16,12 +16,10 @@ function collectProxyDockerNetworksByServer(Server $server)
|
||||
return collect();
|
||||
}
|
||||
$networks = instant_remote_process(['docker inspect --format="{{json .NetworkSettings.Networks }}" coolify-proxy'], $server, false);
|
||||
$networks = collect($networks)->map(function ($network) {
|
||||
|
||||
return collect($networks)->map(function ($network) {
|
||||
return collect(json_decode($network))->keys();
|
||||
})->flatten()->unique();
|
||||
|
||||
return $networks;
|
||||
|
||||
}
|
||||
function collectDockerNetworksByServer(Server $server)
|
||||
{
|
||||
@@ -175,13 +173,12 @@ function generate_default_proxy_configuration(Server $server)
|
||||
],
|
||||
'volumes' => [
|
||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||
"{$proxy_path}:/traefik",
|
||||
|
||||
],
|
||||
'command' => [
|
||||
'--ping=true',
|
||||
'--ping.entrypoint=http',
|
||||
'--api.dashboard=true',
|
||||
'--api.insecure=false',
|
||||
'--entrypoints.http.address=:80',
|
||||
'--entrypoints.https.address=:443',
|
||||
'--entrypoints.http.http.encodequerysemicolons=true',
|
||||
@@ -189,21 +186,26 @@ function generate_default_proxy_configuration(Server $server)
|
||||
'--entrypoints.https.http.encodequerysemicolons=true',
|
||||
'--entryPoints.https.http2.maxConcurrentStreams=50',
|
||||
'--entrypoints.https.http3',
|
||||
'--providers.docker.exposedbydefault=false',
|
||||
'--providers.file.directory=/traefik/dynamic/',
|
||||
'--providers.docker.exposedbydefault=false',
|
||||
'--providers.file.watch=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge=true',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
||||
'--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http',
|
||||
'--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json',
|
||||
],
|
||||
'labels' => $labels,
|
||||
],
|
||||
],
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = '--api.insecure=true';
|
||||
$config['services']['traefik']['command'][] = '--log.level=debug';
|
||||
$config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log';
|
||||
$config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100';
|
||||
$config['services']['traefik']['volumes'][] = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/:/traefik';
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = '--api.insecure=false';
|
||||
$config['services']['traefik']['volumes'][] = "{$proxy_path}:/traefik";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
@@ -241,9 +243,11 @@ function generate_default_proxy_configuration(Server $server)
|
||||
'ports' => [
|
||||
'80:80',
|
||||
'443:443',
|
||||
'443:443/udp',
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed=true',
|
||||
'coolify.proxy=true',
|
||||
],
|
||||
'volumes' => [
|
||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||
|
@@ -124,7 +124,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
||||
associative: true,
|
||||
flags: JSON_THROW_ON_ERROR
|
||||
);
|
||||
} catch (\JsonException $exception) {
|
||||
} catch (\JsonException) {
|
||||
return collect([]);
|
||||
}
|
||||
$seenCommands = collect();
|
||||
@@ -204,7 +204,7 @@ function checkRequiredCommands(Server $server)
|
||||
}
|
||||
try {
|
||||
instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
break;
|
||||
}
|
||||
$commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false);
|
||||
|
@@ -24,7 +24,7 @@ function replaceVariables(string $variable): Stringable
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false)
|
||||
{
|
||||
try {
|
||||
if ($oneService->getMorphClass() === 'App\Models\Application') {
|
||||
if ($oneService->getMorphClass() === \App\Models\Application::class) {
|
||||
$workdir = $oneService->workdir();
|
||||
$server = $oneService->destination->server;
|
||||
} else {
|
||||
@@ -51,7 +51,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
// Exists and is a directory
|
||||
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
|
||||
|
||||
if ($isFile == 'OK') {
|
||||
if ($isFile === 'OK') {
|
||||
// If its a file & exists
|
||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||
if ($fileVolume->is_based_on_git) {
|
||||
@@ -59,12 +59,12 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
}
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
} elseif ($isDir == 'OK') {
|
||||
} elseif ($isDir === 'OK') {
|
||||
// If its a directory & exists
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && $content) {
|
||||
} elseif ($isFile === 'NOK' && $isDir === 'NOK' && ! $fileVolume->is_directory && $isInit && $content) {
|
||||
// Does not exists (no dir or file), not flagged as directory, is init, has content
|
||||
$fileVolume->content = $content;
|
||||
$fileVolume->is_directory = false;
|
||||
@@ -75,13 +75,13 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
"mkdir -p $dir",
|
||||
"echo '$content' | base64 -d | tee $fileLocation",
|
||||
], $server);
|
||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
|
||||
} elseif ($isFile === 'NOK' && $isDir === 'NOK' && $fileVolume->is_directory && $isInit) {
|
||||
// Does not exists (no dir or file), flagged as directory, is init
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
instant_remote_process(["mkdir -p $fileLocation"], $server);
|
||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && is_null($content)) {
|
||||
} elseif ($isFile === 'NOK' && $isDir === 'NOK' && ! $fileVolume->is_directory && $isInit && is_null($content)) {
|
||||
// Does not exists (no dir or file), not flagged as directory, is init, has no content => create directory
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
@@ -245,8 +245,5 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
}
|
||||
function serviceKeys()
|
||||
{
|
||||
$services = get_service_templates();
|
||||
$serviceKeys = $services->keys();
|
||||
|
||||
return $serviceKeys;
|
||||
return get_service_templates()->keys();
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\LocalPersistentVolume;
|
||||
@@ -24,21 +25,17 @@ use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\Internal\GeneralNotification;
|
||||
use Carbon\CarbonImmutable;
|
||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use Illuminate\Database\UniqueConstraintViolationException;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Process\Pool;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -86,8 +83,31 @@ function metrics_dir(): string
|
||||
return base_configuration_dir().'/metrics';
|
||||
}
|
||||
|
||||
function sanitize_string(?string $input = null): ?string
|
||||
{
|
||||
if (is_null($input)) {
|
||||
return null;
|
||||
}
|
||||
// Remove any HTML/PHP tags
|
||||
$sanitized = strip_tags($input);
|
||||
|
||||
// Convert special characters to HTML entities
|
||||
$sanitized = htmlspecialchars($sanitized, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
|
||||
// Remove any control characters
|
||||
$sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $sanitized);
|
||||
|
||||
// Trim whitespace
|
||||
$sanitized = trim($sanitized);
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
function generate_readme_file(string $name, string $updated_at): string
|
||||
{
|
||||
$name = sanitize_string($name);
|
||||
$updated_at = sanitize_string($updated_at);
|
||||
|
||||
return "Resource name: $name\nLatest Deployment Date: $updated_at";
|
||||
}
|
||||
|
||||
@@ -98,12 +118,12 @@ function isInstanceAdmin()
|
||||
|
||||
function currentTeam()
|
||||
{
|
||||
return auth()?->user()?->currentTeam() ?? null;
|
||||
return Auth::user()?->currentTeam() ?? null;
|
||||
}
|
||||
|
||||
function showBoarding(): bool
|
||||
{
|
||||
if (auth()->user()?->isMember()) {
|
||||
if (Auth::user()?->isMember()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -112,21 +132,20 @@ function showBoarding(): bool
|
||||
function refreshSession(?Team $team = null): void
|
||||
{
|
||||
if (! $team) {
|
||||
if (auth()->user()?->currentTeam()) {
|
||||
$team = Team::find(auth()->user()->currentTeam()->id);
|
||||
if (Auth::user()->currentTeam()) {
|
||||
$team = Team::find(Auth::user()->currentTeam()->id);
|
||||
} else {
|
||||
$team = User::find(auth()->user()->id)->teams->first();
|
||||
$team = User::find(Auth::id())->teams->first();
|
||||
}
|
||||
}
|
||||
Cache::forget('team:'.auth()->user()->id);
|
||||
Cache::remember('team:'.auth()->user()->id, 3600, function () use ($team) {
|
||||
Cache::forget('team:'.Auth::id());
|
||||
Cache::remember('team:'.Auth::id(), 3600, function () use ($team) {
|
||||
return $team;
|
||||
});
|
||||
session(['currentTeam' => $team]);
|
||||
}
|
||||
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
||||
{
|
||||
ray($error);
|
||||
if ($error instanceof TooManyRequestsException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||
@@ -142,6 +161,10 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
||||
return 'Duplicate entry found. Please use a different name.';
|
||||
}
|
||||
|
||||
if ($error instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if ($error instanceof Throwable) {
|
||||
$message = $error->getMessage();
|
||||
} else {
|
||||
@@ -164,14 +187,11 @@ function get_route_parameters(): array
|
||||
function get_latest_sentinel_version(): string
|
||||
{
|
||||
try {
|
||||
$response = Http::get('https://cdn.coollabs.io/sentinel/versions.json');
|
||||
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
$versions = $response->json();
|
||||
|
||||
return data_get($versions, 'sentinel.version');
|
||||
} catch (\Throwable $e) {
|
||||
//throw $e;
|
||||
ray($e->getMessage());
|
||||
|
||||
return data_get($versions, 'coolify.sentinel.version');
|
||||
} catch (\Throwable) {
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
@@ -239,43 +259,6 @@ function generate_application_name(string $git_repository, string $git_branch, ?
|
||||
return Str::kebab("$git_repository:$git_branch-$cuid");
|
||||
}
|
||||
|
||||
function is_transactional_emails_active(): bool
|
||||
{
|
||||
return isEmailEnabled(\App\Models\InstanceSettings::get());
|
||||
}
|
||||
|
||||
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
|
||||
{
|
||||
if (! $settings) {
|
||||
$settings = instanceSettings();
|
||||
}
|
||||
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
||||
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
||||
if (data_get($settings, 'resend_enabled')) {
|
||||
config()->set('mail.default', 'resend');
|
||||
config()->set('resend.api_key', data_get($settings, 'resend_api_key'));
|
||||
|
||||
return 'resend';
|
||||
}
|
||||
if (data_get($settings, 'smtp_enabled')) {
|
||||
config()->set('mail.default', 'smtp');
|
||||
config()->set('mail.mailers.smtp', [
|
||||
'transport' => 'smtp',
|
||||
'host' => data_get($settings, 'smtp_host'),
|
||||
'port' => data_get($settings, 'smtp_port'),
|
||||
'encryption' => data_get($settings, 'smtp_encryption'),
|
||||
'username' => data_get($settings, 'smtp_username'),
|
||||
'password' => data_get($settings, 'smtp_password'),
|
||||
'timeout' => data_get($settings, 'smtp_timeout'),
|
||||
'local_domain' => null,
|
||||
]);
|
||||
|
||||
return 'smtp';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function base_ip(): string
|
||||
{
|
||||
if (isDev()) {
|
||||
@@ -300,7 +283,7 @@ function getFqdnWithoutPort(string $fqdn)
|
||||
$path = $url->getPath();
|
||||
|
||||
return "$scheme://$host$path";
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
return $fqdn;
|
||||
}
|
||||
}
|
||||
@@ -355,7 +338,7 @@ function isDev(): bool
|
||||
|
||||
function isCloud(): bool
|
||||
{
|
||||
return ! config('coolify.self_hosted');
|
||||
return ! config('constants.coolify.self_hosted');
|
||||
}
|
||||
|
||||
function translate_cron_expression($expression_to_validate): string
|
||||
@@ -368,6 +351,9 @@ function translate_cron_expression($expression_to_validate): string
|
||||
}
|
||||
function validate_cron_expression($expression_to_validate): bool
|
||||
{
|
||||
if (empty($expression_to_validate)) {
|
||||
return false;
|
||||
}
|
||||
$isValid = false;
|
||||
$expression = new CronExpression($expression_to_validate);
|
||||
$isValid = $expression->isValid();
|
||||
@@ -378,80 +364,12 @@ function validate_cron_expression($expression_to_validate): bool
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
function send_internal_notification(string $message): void
|
||||
{
|
||||
try {
|
||||
$team = Team::find(0);
|
||||
$team?->notify(new GeneralNotification($message));
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
}
|
||||
}
|
||||
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$type = set_transanctional_email_settings($settings);
|
||||
if (! $type) {
|
||||
throw new Exception('No email settings found.');
|
||||
}
|
||||
if ($cc) {
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($email)
|
||||
->replyTo($email)
|
||||
->cc($cc)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
);
|
||||
} else {
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
->to($email)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
);
|
||||
}
|
||||
}
|
||||
function isTestEmailEnabled($notifiable)
|
||||
{
|
||||
if (data_get($notifiable, 'use_instance_email_settings') && isInstanceAdmin()) {
|
||||
return true;
|
||||
} elseif (data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') && auth()->user()->isAdminFromSession()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
function isEmailEnabled($notifiable)
|
||||
function validate_timezone(string $timezone): bool
|
||||
{
|
||||
return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings');
|
||||
return in_array($timezone, timezone_identifiers_list());
|
||||
}
|
||||
function setNotificationChannels($notifiable, $event)
|
||||
{
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
$isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event");
|
||||
$isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event");
|
||||
$isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event");
|
||||
|
||||
if ($isDiscordEnabled && $isSubscribedToDiscordEvent) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
if ($isEmailEnabled && $isSubscribedToEmailEvent) {
|
||||
$channels[] = EmailChannel::class;
|
||||
}
|
||||
if ($isTelegramEnabled && $isSubscribedToTelegramEvent) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
|
||||
return $channels;
|
||||
}
|
||||
function parseEnvFormatToArray($env_file_contents)
|
||||
{
|
||||
$env_array = [];
|
||||
@@ -496,9 +414,8 @@ function generateFqdn(Server $server, string $random, bool $forceHttps = false):
|
||||
if ($forceHttps) {
|
||||
$scheme = 'https';
|
||||
}
|
||||
$finalFqdn = "$scheme://{$random}.$host$path";
|
||||
|
||||
return $finalFqdn;
|
||||
return "$scheme://{$random}.$host$path";
|
||||
}
|
||||
function sslip(Server $server)
|
||||
{
|
||||
@@ -536,7 +453,7 @@ function get_service_templates(bool $force = false): Collection
|
||||
$services = $response->json();
|
||||
|
||||
return collect($services);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable) {
|
||||
$services = File::get(base_path('templates/service-templates.json'));
|
||||
|
||||
return collect(json_decode($services))->sortKeys();
|
||||
@@ -643,14 +560,13 @@ function queryResourcesByUuid(string $uuid)
|
||||
|
||||
return $resource;
|
||||
}
|
||||
function generatTagDeployWebhook($tag_name)
|
||||
function generateTagDeployWebhook($tag_name)
|
||||
{
|
||||
$baseUrl = base_url();
|
||||
$api = Url::fromString($baseUrl).'/api/v1';
|
||||
$endpoint = "/deploy?tag=$tag_name";
|
||||
$url = $api.$endpoint;
|
||||
|
||||
return $url;
|
||||
return $api.$endpoint;
|
||||
}
|
||||
function generateDeployWebhook($resource)
|
||||
{
|
||||
@@ -658,20 +574,18 @@ function generateDeployWebhook($resource)
|
||||
$api = Url::fromString($baseUrl).'/api/v1';
|
||||
$endpoint = '/deploy';
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
$url = $api.$endpoint."?uuid=$uuid&force=false";
|
||||
|
||||
return $url;
|
||||
return $api.$endpoint."?uuid=$uuid&force=false";
|
||||
}
|
||||
function generateGitManualWebhook($resource, $type)
|
||||
{
|
||||
if ($resource->source_id !== 0 && ! is_null($resource->source_id)) {
|
||||
return null;
|
||||
}
|
||||
if ($resource->getMorphClass() === 'App\Models\Application') {
|
||||
if ($resource->getMorphClass() === \App\Models\Application::class) {
|
||||
$baseUrl = base_url();
|
||||
$api = Url::fromString($baseUrl)."/webhooks/source/$type/events/manual";
|
||||
|
||||
return $api;
|
||||
return Url::fromString($baseUrl)."/webhooks/source/$type/events/manual";
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -683,7 +597,7 @@ function removeAnsiColors($text)
|
||||
|
||||
function getTopLevelNetworks(Service|Application $resource)
|
||||
{
|
||||
if ($resource->getMorphClass() === 'App\Models\Service') {
|
||||
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
||||
if ($resource->docker_compose_raw) {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
@@ -738,7 +652,7 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
|
||||
return $topLevelNetworks->keys();
|
||||
}
|
||||
} elseif ($resource->getMorphClass() === 'App\Models\Application') {
|
||||
} elseif ($resource->getMorphClass() === \App\Models\Application::class) {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
@@ -932,6 +846,15 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
||||
case 'REALBASE64_32':
|
||||
$generatedValue = base64_encode(Str::random(32));
|
||||
break;
|
||||
case 'HEX_32':
|
||||
$generatedValue = bin2hex(Str::random(32));
|
||||
break;
|
||||
case 'HEX_64':
|
||||
$generatedValue = bin2hex(Str::random(64));
|
||||
break;
|
||||
case 'HEX_128':
|
||||
$generatedValue = bin2hex(Str::random(128));
|
||||
break;
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
@@ -945,7 +868,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
||||
$key = InMemory::plainText($signingKey);
|
||||
$algorithm = new Sha256;
|
||||
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
|
||||
$now = new DateTimeImmutable;
|
||||
$now = CarbonImmutable::now();
|
||||
$now = $now->setTime($now->format('H'), $now->format('i'));
|
||||
$token = $tokenBuilder
|
||||
->issuedBy('supabase')
|
||||
@@ -965,7 +888,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
||||
$key = InMemory::plainText($signingKey);
|
||||
$algorithm = new Sha256;
|
||||
$tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default()));
|
||||
$now = new DateTimeImmutable;
|
||||
$now = CarbonImmutable::now();
|
||||
$now = $now->setTime($now->format('H'), $now->format('i'));
|
||||
$token = $tokenBuilder
|
||||
->issuedBy('supabase')
|
||||
@@ -986,7 +909,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n
|
||||
|
||||
function getRealtime()
|
||||
{
|
||||
$envDefined = env('PUSHER_PORT');
|
||||
$envDefined = config('constants.pusher.port');
|
||||
if (empty($envDefined)) {
|
||||
$url = Url::fromString(Request::getSchemeAndHttpHost());
|
||||
$port = $url->getPort();
|
||||
@@ -1048,7 +971,7 @@ function validate_dns_entry(string $fqdn, Server $server)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
}
|
||||
}
|
||||
ray("Found match: $found_matching_ip");
|
||||
@@ -1145,7 +1068,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
|
||||
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
||||
{
|
||||
if ($resource) {
|
||||
if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') {
|
||||
if ($resource->getMorphClass() === \App\Models\Application::class && $resource->build_pack === 'dockercompose') {
|
||||
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
|
||||
$domains = collect($domains);
|
||||
} else {
|
||||
@@ -1338,13 +1261,6 @@ function isAnyDeploymentInprogress()
|
||||
exit(0);
|
||||
}
|
||||
|
||||
function generateSentinelToken()
|
||||
{
|
||||
$token = Str::random(64);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
function isBase64Encoded($strValue)
|
||||
{
|
||||
return base64_encode(base64_decode($strValue, true)) === $strValue;
|
||||
@@ -1416,7 +1332,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
|
||||
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||
return $volume;
|
||||
}
|
||||
if (get_class($resource) === "App\Models\Application") {
|
||||
if (get_class($resource) === \App\Models\Application::class) {
|
||||
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
||||
} else {
|
||||
$dir = base_configuration_dir().'/services/'.$resource->service->uuid;
|
||||
@@ -1456,7 +1372,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
|
||||
}
|
||||
}
|
||||
$slugWithoutUuid = Str::slug($source, '-');
|
||||
if (get_class($resource) === "App\Models\Application") {
|
||||
if (get_class($resource) === \App\Models\Application::class) {
|
||||
$name = "{$resource->uuid}_{$slugWithoutUuid}";
|
||||
} else {
|
||||
$name = "{$resource->service->uuid}_{$slugWithoutUuid}";
|
||||
@@ -1499,7 +1415,7 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
|
||||
|
||||
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
|
||||
{
|
||||
if ($resource->getMorphClass() === 'App\Models\Service') {
|
||||
if ($resource->getMorphClass() === \App\Models\Service::class) {
|
||||
if ($resource->docker_compose_raw) {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
@@ -2213,10 +2129,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
} else {
|
||||
return collect([]);
|
||||
}
|
||||
} elseif ($resource->getMorphClass() === 'App\Models\Application') {
|
||||
} elseif ($resource->getMorphClass() === \App\Models\Application::class) {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
return;
|
||||
}
|
||||
$server = $resource->destination->server;
|
||||
@@ -2962,7 +2878,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
|
||||
try {
|
||||
$yaml = Yaml::parse($compose);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
@@ -3099,7 +3015,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
}
|
||||
}
|
||||
|
||||
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
|
||||
if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
|
||||
$path = $value->value();
|
||||
if ($path !== '/') {
|
||||
$fqdn = "$fqdn$path";
|
||||
@@ -3190,7 +3106,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
|
||||
} else {
|
||||
$value = generateEnvValue($command, $resource);
|
||||
$resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([
|
||||
@@ -3618,7 +3533,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
'is_required' => $isRequired,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ($isApplication) {
|
||||
@@ -3792,7 +3706,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
||||
service_name: $serviceName,
|
||||
image: $image,
|
||||
predefinedPort: $predefinedPort
|
||||
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -3983,20 +3896,19 @@ function convertComposeEnvironmentToArray($environment)
|
||||
}
|
||||
|
||||
return $convertedServiceVariables;
|
||||
|
||||
}
|
||||
function instanceSettings()
|
||||
{
|
||||
return InstanceSettings::get();
|
||||
}
|
||||
|
||||
function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) {
|
||||
|
||||
function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id)
|
||||
{
|
||||
$server = Server::find($server_id)->where('team_id', $team_id)->first();
|
||||
if (!$server) {
|
||||
if (! $server) {
|
||||
return;
|
||||
}
|
||||
$uuid = new Cuid2();
|
||||
$uuid = new Cuid2;
|
||||
$cloneCommand = "git clone --no-checkout -b $branch $repository .";
|
||||
$workdir = rtrim($base_directory, '/');
|
||||
$fileList = collect([".$workdir/coolify.json"]);
|
||||
@@ -4013,7 +3925,141 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire
|
||||
]);
|
||||
try {
|
||||
return instant_remote_process($commands, $server);
|
||||
} catch (\Exception $e) {
|
||||
// continue
|
||||
} catch (\Exception) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
function loggy($message = null, array $context = [])
|
||||
{
|
||||
if (! isDev()) {
|
||||
return;
|
||||
}
|
||||
if (function_exists('ray') && config('app.debug')) {
|
||||
ray($message, $context);
|
||||
}
|
||||
if (is_null($message)) {
|
||||
return app('log');
|
||||
}
|
||||
|
||||
return app('log')->debug($message, $context);
|
||||
}
|
||||
function sslipDomainWarning(string $domains)
|
||||
{
|
||||
$domains = str($domains)->trim()->explode(',');
|
||||
$showSslipHttpsWarning = false;
|
||||
$domains->each(function ($domain) use (&$showSslipHttpsWarning) {
|
||||
if (str($domain)->contains('https') && str($domain)->contains('sslip')) {
|
||||
$showSslipHttpsWarning = true;
|
||||
}
|
||||
});
|
||||
|
||||
return $showSslipHttpsWarning;
|
||||
}
|
||||
|
||||
function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?callable $callbackOnSuccess = null): bool
|
||||
{
|
||||
if (isDev()) {
|
||||
$decaySeconds = 120;
|
||||
}
|
||||
$rateLimited = false;
|
||||
$executed = RateLimiter::attempt(
|
||||
$limiterKey,
|
||||
$maxAttempts = 0,
|
||||
function () use (&$rateLimited, &$limiterKey, $callbackOnSuccess) {
|
||||
isDev() && loggy('Rate limit not reached for '.$limiterKey);
|
||||
$rateLimited = false;
|
||||
|
||||
if ($callbackOnSuccess) {
|
||||
$callbackOnSuccess();
|
||||
}
|
||||
},
|
||||
$decaySeconds,
|
||||
);
|
||||
if (! $executed) {
|
||||
isDev() && loggy('Rate limit reached for '.$limiterKey.'. Rate limiter will be disabled for '.$decaySeconds.' seconds.');
|
||||
$rateLimited = true;
|
||||
}
|
||||
|
||||
return $rateLimited;
|
||||
}
|
||||
|
||||
function defaultNginxConfiguration(): string
|
||||
{
|
||||
return 'server {
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri.html $uri/index.html $uri/index.htm $uri/ /index.html /index.htm =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri @redirect_to_index;
|
||||
internal;
|
||||
}
|
||||
|
||||
error_page 404 = @handle_404;
|
||||
|
||||
location @handle_404 {
|
||||
root /usr/share/nginx/html;
|
||||
try_files /404.html @redirect_to_index;
|
||||
internal;
|
||||
}
|
||||
|
||||
location @redirect_to_index {
|
||||
return 302 /;
|
||||
}
|
||||
}';
|
||||
}
|
||||
|
||||
function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array
|
||||
{
|
||||
$repository = $gitRepository;
|
||||
$providerInfo = [
|
||||
'host' => null,
|
||||
'user' => 'git',
|
||||
'port' => 22,
|
||||
'repository' => $gitRepository,
|
||||
];
|
||||
$sshMatches = [];
|
||||
$matches = [];
|
||||
|
||||
// Let's try and parse the string to detect if it's a valid SSH string or not
|
||||
preg_match('/((.*?)\:\/\/)?(.*@.*:.*)/', $gitRepository, $sshMatches);
|
||||
|
||||
if ($deploymentType === 'deploy_key' && empty($sshMatches) && $source) {
|
||||
// If this happens, the user may have provided an HTTP URL when they needed an SSH one
|
||||
// Let's try and fix that for known Git providers
|
||||
switch ($source->getMorphClass()) {
|
||||
case \App\Models\GithubApp::class:
|
||||
$providerInfo['host'] = Url::fromString($source->html_url)->getHost();
|
||||
$providerInfo['port'] = $source->custom_port;
|
||||
$providerInfo['user'] = $source->custom_user;
|
||||
break;
|
||||
}
|
||||
if (! empty($providerInfo['host'])) {
|
||||
// Until we do not support more providers with App (like GithubApp), this will be always true, port will be 22
|
||||
if ($providerInfo['port'] === 22) {
|
||||
$repository = "{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['repository']}";
|
||||
} else {
|
||||
$repository = "ssh://{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['port']}/{$providerInfo['repository']}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preg_match('/(?<=:)\d+(?=\/)/', $gitRepository, $matches);
|
||||
|
||||
if (count($matches) === 1) {
|
||||
$providerInfo['port'] = $matches[0];
|
||||
$gitHost = str($gitRepository)->before(':');
|
||||
$gitRepo = str($gitRepository)->after('/');
|
||||
$repository = "$gitHost:$gitRepo";
|
||||
}
|
||||
|
||||
return [
|
||||
'repository' => $repository,
|
||||
'port' => $providerInfo['port'],
|
||||
];
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ function get_socialite_provider(string $provider)
|
||||
{
|
||||
$oauth_setting = OauthSetting::firstWhere('provider', $provider);
|
||||
|
||||
if ($provider == 'azure') {
|
||||
if ($provider === 'azure') {
|
||||
$azure_config = new \SocialiteProviders\Manager\Config(
|
||||
$oauth_setting->client_id,
|
||||
$oauth_setting->client_secret,
|
||||
|
@@ -55,12 +55,11 @@ function getStripeCustomerPortalSession(Team $team)
|
||||
if (! $stripe_customer_id) {
|
||||
return null;
|
||||
}
|
||||
$session = \Stripe\BillingPortal\Session::create([
|
||||
|
||||
return \Stripe\BillingPortal\Session::create([
|
||||
'customer' => $stripe_customer_id,
|
||||
'return_url' => $return_url,
|
||||
]);
|
||||
|
||||
return $session;
|
||||
}
|
||||
function allowedPathsForUnsubscribedAccounts()
|
||||
{
|
||||
@@ -68,7 +67,6 @@ function allowedPathsForUnsubscribedAccounts()
|
||||
'subscription/new',
|
||||
'login',
|
||||
'logout',
|
||||
'waitlist',
|
||||
'force-password-reset',
|
||||
'livewire/update',
|
||||
];
|
||||
|
Reference in New Issue
Block a user