mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Merge remote-tracking branch 'origin/next' into coolify-analytics-traefik-caddy
# Conflicts: # app/Models/ServerSetting.php # package-lock.json # package.json # resources/views/components/forms/listbox.blade.php
This commit is contained in:
+100
-17
@@ -7,6 +7,7 @@ use App\Models\Server;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -198,6 +199,79 @@ function checkMinimumDockerEngineVersion($dockerVersion)
|
||||
|
||||
return $dockerVersion;
|
||||
}
|
||||
|
||||
function parseDockerEngineVersion(?string $rawVersion): ?string
|
||||
{
|
||||
if ($rawVersion === null || trim($rawVersion) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/\d+\.\d+(?:\.\d+)?/', $rawVersion, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parts = explode('.', $matches[0]);
|
||||
|
||||
return sprintf('%d.%d.%d', (int) $parts[0], (int) ($parts[1] ?? 0), (int) ($parts[2] ?? 0));
|
||||
}
|
||||
|
||||
function dockerEngineVersionFromJson(?string $raw): ?string
|
||||
{
|
||||
if ($raw === null || trim($raw) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
if (! is_array($decoded)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$version = $decoded['Server']['Version'] ?? null;
|
||||
|
||||
return is_string($version) ? parseDockerEngineVersion($version) : null;
|
||||
}
|
||||
|
||||
function dockerStopTimeoutOption(?string $dockerVersion): string
|
||||
{
|
||||
$normalized = parseDockerEngineVersion($dockerVersion);
|
||||
if ($normalized !== null && version_compare($normalized, '28.0.0', '>=')) {
|
||||
return '--timeout';
|
||||
}
|
||||
|
||||
return '--time';
|
||||
}
|
||||
|
||||
function dockerStopCommand(int $timeout, string $containers, Server|string|null $dockerVersion = null): string
|
||||
{
|
||||
$version = $dockerVersion instanceof Server
|
||||
? $dockerVersion->dockerVersion()
|
||||
: $dockerVersion;
|
||||
|
||||
$option = dockerStopTimeoutOption($version);
|
||||
$flag = $option === '--timeout'
|
||||
? "--timeout={$timeout}"
|
||||
: "--time={$timeout}";
|
||||
|
||||
$command = "docker stop {$flag} {$containers}";
|
||||
|
||||
if (app()->bound('config') && isDev()) {
|
||||
Log::info('docker stop command', [
|
||||
'command' => $command,
|
||||
'docker_version' => $version,
|
||||
]);
|
||||
}
|
||||
|
||||
return $command;
|
||||
}
|
||||
|
||||
function dockerRemoveCommandWithTimeout(string $container, int $timeout = 60, int $killAfter = 10): string
|
||||
{
|
||||
$container = escapeShellValue($container);
|
||||
$script = "if command -v timeout >/dev/null 2>&1; then timeout -k {$killAfter}s {$timeout}s docker rm -f {$container}; exit_code=\$?; else exit_code=124; fi; if [ \"\$exit_code\" -eq 124 ]; then echo '__COOLIFY_CONTAINER_REMOVE_TIMEOUT__'; fi; exit \$exit_code";
|
||||
|
||||
return 'bash -c '.escapeShellValue($script);
|
||||
}
|
||||
|
||||
function escapeShellValue(string $value): string
|
||||
{
|
||||
return "'".str_replace("'", "'\\''", $value)."'";
|
||||
@@ -453,6 +527,10 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
$path = $url->getPath();
|
||||
$host_without_www = str($host)->replace('www.', '');
|
||||
$schema = $url->getScheme();
|
||||
$siteAddress = "{$schema}://{$host}";
|
||||
if ($schema === 'https' && ! $is_force_https_enabled) {
|
||||
$siteAddress = "http://{$host}, https://{$host}";
|
||||
}
|
||||
$port = $url->getPort();
|
||||
$handle = 'handle_path';
|
||||
if (! $is_stripprefix_enabled) {
|
||||
@@ -464,7 +542,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
if (is_null($port) && $predefinedPort) {
|
||||
$port = $predefinedPort;
|
||||
}
|
||||
$labels->push("caddy_{$loop}={$schema}://{$host}");
|
||||
$labels->push("caddy_{$loop}={$siteAddress}");
|
||||
if (isNoindexDomain($domain, $noindex_domains)) {
|
||||
// Caddy's header directive takes either inline arguments or a block,
|
||||
// never both, so -Server has to move into the block alongside it.
|
||||
@@ -484,11 +562,12 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
if ($is_gzip_enabled) {
|
||||
$labels->push("caddy_{$loop}.encode=zstd gzip");
|
||||
}
|
||||
$redirect_schema = $is_force_https_enabled ? $schema : '{scheme}';
|
||||
if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
|
||||
$labels->push("caddy_{$loop}.redir={$schema}://www.{$host}{uri}");
|
||||
$labels->push("caddy_{$loop}.redir={$redirect_schema}://www.{$host}{uri}");
|
||||
}
|
||||
if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) {
|
||||
$labels->push("caddy_{$loop}.redir={$schema}://{$host_without_www}{uri}");
|
||||
$labels->push("caddy_{$loop}.redir={$redirect_schema}://{$host_without_www}{uri}");
|
||||
}
|
||||
if ($is_http_basic_auth_enabled) {
|
||||
$labels->push("caddy_{$loop}.basicauth.{$http_basic_auth_username}=\"{$hashedPassword}\"");
|
||||
@@ -510,7 +589,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
|
||||
return $labels->sort();
|
||||
}
|
||||
|
||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null)
|
||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null, string $redirect_direction = 'both', bool $is_http_basic_auth_enabled = false, ?string $http_basic_auth_username = null, ?string $http_basic_auth_password = null, ?Collection $noindex_domains = null, bool $escape_redirect_replacement_for_compose = true)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
@@ -593,14 +672,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
|
||||
$to_www_name = "{$loop}-{$uuid}-to-www";
|
||||
$to_non_www_name = "{$loop}-{$uuid}-to-non-www";
|
||||
$redirect_capture_prefix = $escape_redirect_replacement_for_compose ? '$$' : '$';
|
||||
$redirect_to_non_www = [
|
||||
"traefik.http.middlewares.{$to_non_www_name}.redirectregex.regex=^(http|https)://www\.(.+)",
|
||||
"traefik.http.middlewares.{$to_non_www_name}.redirectregex.replacement=\$\${1}://\$\${2}",
|
||||
"traefik.http.middlewares.{$to_non_www_name}.redirectregex.replacement={$redirect_capture_prefix}{1}://{$redirect_capture_prefix}{2}",
|
||||
"traefik.http.middlewares.{$to_non_www_name}.redirectregex.permanent=false",
|
||||
];
|
||||
$redirect_to_www = [
|
||||
"traefik.http.middlewares.{$to_www_name}.redirectregex.regex=^(http|https)://(?:www\.)?(.+)",
|
||||
"traefik.http.middlewares.{$to_www_name}.redirectregex.replacement=\$\${1}://www.\$\${2}",
|
||||
"traefik.http.middlewares.{$to_www_name}.redirectregex.replacement={$redirect_capture_prefix}{1}://www.{$redirect_capture_prefix}{2}",
|
||||
"traefik.http.middlewares.{$to_www_name}.redirectregex.permanent=false",
|
||||
];
|
||||
if ($schema === 'https') {
|
||||
@@ -642,8 +722,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares->push($middleware_name);
|
||||
});
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares->join(',')}");
|
||||
}
|
||||
} else {
|
||||
$middlewares = collect([]);
|
||||
@@ -671,8 +750,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$middlewares->push($middleware_name);
|
||||
});
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares->join(',')}");
|
||||
}
|
||||
}
|
||||
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
||||
@@ -685,15 +763,17 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
|
||||
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
|
||||
}
|
||||
$middlewares = collect([]);
|
||||
if ($is_noindex) {
|
||||
$middlewares->push($noindex_name);
|
||||
}
|
||||
if ($is_force_https_enabled) {
|
||||
$middlewares->push('redirect-to-https');
|
||||
$httpMiddlewares = collect([]);
|
||||
if ($is_noindex) {
|
||||
$httpMiddlewares->push($noindex_name);
|
||||
}
|
||||
$httpMiddlewares->push('redirect-to-https');
|
||||
} else {
|
||||
$httpMiddlewares = $middlewares;
|
||||
}
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares->join(',')}");
|
||||
if ($httpMiddlewares->isNotEmpty()) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$httpMiddlewares->join(',')}");
|
||||
}
|
||||
} else {
|
||||
// Set labels for http
|
||||
@@ -840,6 +920,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
http_basic_auth_username: $application->http_basic_auth_username,
|
||||
http_basic_auth_password: $application->http_basic_auth_password,
|
||||
noindex_domains: $noindexDomains,
|
||||
escape_redirect_replacement_for_compose: false,
|
||||
));
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
@@ -881,6 +962,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
http_basic_auth_username: $application->http_basic_auth_username,
|
||||
http_basic_auth_password: $application->http_basic_auth_password,
|
||||
noindex_domains: $noindexDomains,
|
||||
escape_redirect_replacement_for_compose: false,
|
||||
));
|
||||
break;
|
||||
case ProxyTypes::CADDY->value:
|
||||
@@ -912,6 +994,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
http_basic_auth_username: $application->http_basic_auth_username,
|
||||
http_basic_auth_password: $application->http_basic_auth_password,
|
||||
noindex_domains: $noindexDomains,
|
||||
escape_redirect_replacement_for_compose: false,
|
||||
));
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
|
||||
@@ -5,6 +5,8 @@ use App\Notifications\Internal\GeneralNotification;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
function is_transactional_emails_enabled(): bool
|
||||
{
|
||||
@@ -13,6 +15,63 @@ function is_transactional_emails_enabled(): bool
|
||||
return $settings->smtp_enabled || $settings->resend_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{address: string, name: string}
|
||||
*/
|
||||
function mail_from_identity(object $settings): array
|
||||
{
|
||||
if (blank($settings->smtp_from_address ?? null)) {
|
||||
throw new InvalidArgumentException('Transactional email sender address is not configured.');
|
||||
}
|
||||
|
||||
$address = (string) $settings->smtp_from_address;
|
||||
|
||||
$name = trim((string) ($settings->smtp_from_name ?? ''));
|
||||
|
||||
return [
|
||||
'address' => $address,
|
||||
'name' => $name !== '' ? $name : 'Coolify',
|
||||
];
|
||||
}
|
||||
|
||||
function mail_from_address(object $settings): Address
|
||||
{
|
||||
$identity = mail_from_identity($settings);
|
||||
|
||||
return new Address($identity['address'], $identity['name']);
|
||||
}
|
||||
|
||||
function mail_from_formatted(object $settings): string
|
||||
{
|
||||
return mail_from_address($settings)->toString();
|
||||
}
|
||||
|
||||
function mail_from_email(Email $email, object $settings): Email
|
||||
{
|
||||
$email->from(mail_from_address($settings));
|
||||
prevent_mail_from_header_folding($email, $settings);
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
function mail_from_message(Message $message, object $settings): Message
|
||||
{
|
||||
$identity = mail_from_identity($settings);
|
||||
$message->from($identity['address'], $identity['name']);
|
||||
prevent_mail_from_header_folding($message->getSymfonyMessage(), $settings);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
function prevent_mail_from_header_folding(Email $email, object $settings): void
|
||||
{
|
||||
if (strtolower(trim((string) ($settings->smtp_host ?? ''))) !== 'smtp.protonmail.ch') {
|
||||
return;
|
||||
}
|
||||
|
||||
$email->getHeaders()->get('From')?->setMaxLineLength(998);
|
||||
}
|
||||
|
||||
function send_internal_notification(string $message): void
|
||||
{
|
||||
try {
|
||||
@@ -33,7 +92,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
fn (Message $message) => mail_from_message($message, $settings)
|
||||
->to($email)
|
||||
->replyTo($email)
|
||||
->cc($cc)
|
||||
@@ -44,7 +103,7 @@ function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null
|
||||
Mail::send(
|
||||
[],
|
||||
[],
|
||||
fn (Message $message) => $message
|
||||
fn (Message $message) => mail_from_message($message, $settings)
|
||||
->to($email)
|
||||
->subject($mail->subject)
|
||||
->html((string) $mail->render())
|
||||
|
||||
@@ -358,6 +358,19 @@ function parseDockerVolumeString(string $volumeString): array
|
||||
];
|
||||
}
|
||||
|
||||
function addTraefikDockerNetworkLabel(Collection $labels, string $network): Collection
|
||||
{
|
||||
$hasUserDefinedNetwork = $labels->contains(
|
||||
fn ($label): bool => is_string($label) && str($label)->before('=')->is('traefik.docker.network')
|
||||
);
|
||||
|
||||
if (! $hasUserDefinedNetwork) {
|
||||
$labels->push("traefik.docker.network={$network}");
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null, ?string $commit = null): Collection
|
||||
{
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
@@ -1346,6 +1359,9 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
$redirectDirection = in_array($composeRedirect, ['www', 'non-www', 'both'], true)
|
||||
? $composeRedirect
|
||||
: 'both';
|
||||
if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) {
|
||||
$serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first());
|
||||
}
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
@@ -2620,13 +2636,16 @@ function serviceParser(Service $resource): Collection
|
||||
$redirectDirection = in_array(data_get($originalResource, 'redirect'), ['www', 'non-www', 'both'], true)
|
||||
? data_get($originalResource, 'redirect')
|
||||
: 'both';
|
||||
if (! $use_network_mode && (! $shouldGenerateLabelsExactly || $server->proxyType() === ProxyTypes::TRAEFIK->value)) {
|
||||
$serviceLabels = addTraefikDockerNetworkLabel($serviceLabels, $baseNetwork->first());
|
||||
}
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $originalResource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
|
||||
@@ -2641,7 +2660,7 @@ function serviceParser(Service $resource): Collection
|
||||
network: $network,
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $originalResource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
|
||||
@@ -2657,7 +2676,7 @@ function serviceParser(Service $resource): Collection
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $originalResource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
|
||||
@@ -2670,7 +2689,7 @@ function serviceParser(Service $resource): Collection
|
||||
network: $network,
|
||||
uuid: $uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $originalResource->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $originalResource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $originalResource->isStripprefixEnabled(),
|
||||
|
||||
+12
-11
@@ -130,8 +130,8 @@ function collectDockerNetworksByServer(Server $server)
|
||||
}
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
['networks' => $networks] = collectDockerNetworksByServer($server);
|
||||
if ($server->isSwarm()) {
|
||||
['networks' => $networks] = collectDockerNetworksByServer($server);
|
||||
$commands = $networks->map(function ($network) {
|
||||
$safe = escapeshellarg($network);
|
||||
|
||||
@@ -141,19 +141,20 @@ function connectProxyToNetworks(Server $server)
|
||||
"echo 'Successfully connected coolify-proxy to {$safe} network.'",
|
||||
];
|
||||
});
|
||||
} else {
|
||||
$commands = $networks->map(function ($network) {
|
||||
$safe = escapeshellarg($network);
|
||||
|
||||
return [
|
||||
"docker network ls --format '{{.Name}}' | grep '^{$network}$' >/dev/null || docker network create --attachable {$safe} >/dev/null",
|
||||
"docker network connect {$safe} coolify-proxy >/dev/null 2>&1 || true",
|
||||
"echo 'Successfully connected coolify-proxy to {$safe} network.'",
|
||||
];
|
||||
});
|
||||
return $commands->flatten();
|
||||
}
|
||||
|
||||
return $commands->flatten();
|
||||
return collect([
|
||||
'for network in $(docker inspect $(docker ps --filter label=coolify.managed=true --format "{{.ID}}") --format=\'{{range $network, $_ := .NetworkSettings.Networks}}{{println $network}}{{end}}\' 2>/dev/null | sort -u); do',
|
||||
' if [ -z "$network" ] || [ "$network" = "bridge" ] || [ "$network" = "host" ] || [ "$network" = "none" ] || [ "$network" = "default" ]; then',
|
||||
' continue',
|
||||
' fi',
|
||||
' if docker network inspect "$network" >/dev/null 2>&1; then',
|
||||
' docker network connect "$network" coolify-proxy >/dev/null 2>&1 || true',
|
||||
' fi',
|
||||
'done',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -177,7 +177,7 @@ function instant_remote_process(Collection|array $command, Server $server, bool
|
||||
|
||||
return SshRetryHandler::retry(
|
||||
function () use ($server, $command_string, $effectiveTimeout, $disableMultiplexing) {
|
||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string, $disableMultiplexing);
|
||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string, $disableMultiplexing, (int) $effectiveTimeout);
|
||||
$process = Process::timeout($effectiveTimeout)->run($sshCommand);
|
||||
|
||||
$output = trim($process->output());
|
||||
|
||||
@@ -167,13 +167,11 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
|
||||
|
||||
if ($isFile === 'OK') {
|
||||
// If its a file & exists
|
||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||
if ($fileVolume->is_based_on_git) {
|
||||
$fileVolume->content = $filesystemContent;
|
||||
}
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
if ($fileVolume->is_based_on_git) {
|
||||
$fileVolume->loadStorageOnServer();
|
||||
}
|
||||
} elseif ($isDir === 'OK') {
|
||||
// If its a directory & exists
|
||||
$fileVolume->content = null;
|
||||
@@ -235,7 +233,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
// IMPORTANT: Only extract variables that are DIRECTLY DECLARED for this service,
|
||||
// not variables that are merely referenced from other services
|
||||
$serviceConfig = data_get($dockerCompose, "services.{$name}");
|
||||
$environment = data_get($serviceConfig, 'environment', []);
|
||||
$environment = data_get($serviceConfig, 'environment') ?? [];
|
||||
$templateVariableNames = [];
|
||||
|
||||
foreach ($environment as $key => $value) {
|
||||
|
||||
@@ -2068,7 +2068,7 @@ function validateDNSEntry(string $fqdn, Server $server)
|
||||
$type = dnsRecordTypeForIp($ip) === 'AAAA' ? DNSTypes::NAME_AAAA : DNSTypes::NAME_A;
|
||||
foreach ($dns_servers as $dns_server) {
|
||||
try {
|
||||
$query = new DNSQuery($dns_server);
|
||||
$query = createDnsQuery($dns_server);
|
||||
$results = $query->query($host, $type);
|
||||
if ($results === false || $query->hasError()) {
|
||||
} else {
|
||||
@@ -2076,11 +2076,11 @@ function validateDNSEntry(string $fqdn, Server $server)
|
||||
if ($result->getType() == $type) {
|
||||
if (isCloudflareIp($result->getData())) {
|
||||
$found_matching_ip = true;
|
||||
break;
|
||||
break 2;
|
||||
}
|
||||
if ($ip && $result->getData() === $ip) {
|
||||
$found_matching_ip = true;
|
||||
break;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2092,6 +2092,15 @@ function validateDNSEntry(string $fqdn, Server $server)
|
||||
return $found_matching_ip;
|
||||
}
|
||||
|
||||
function createDnsQuery(string $dnsServer): DNSQuery
|
||||
{
|
||||
return app()->make(DNSQuery::class, [
|
||||
'server' => $dnsServer,
|
||||
'port' => 53,
|
||||
'timeout' => 5,
|
||||
]);
|
||||
}
|
||||
|
||||
function isCloudflareIp(string $ip): bool
|
||||
{
|
||||
// https://www.cloudflare.com/ips/
|
||||
@@ -3050,7 +3059,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $savedService->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
@@ -3065,7 +3074,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $savedService->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
@@ -3080,7 +3089,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $savedService->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
@@ -3093,7 +3102,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
is_force_https_enabled: $savedService->isForceHttpsEnabled(),
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
@@ -4566,7 +4575,7 @@ function formatContainerStatus(string $status): string
|
||||
* Check if password confirmation should be skipped.
|
||||
* Returns true if:
|
||||
* - Two-step confirmation is globally disabled
|
||||
* - User has no password (OAuth users)
|
||||
* - User has no usable local password confirmation (including SSO users)
|
||||
*
|
||||
* Used by modal-confirmation.blade.php to determine if password step should be shown.
|
||||
*
|
||||
@@ -4579,8 +4588,9 @@ function shouldSkipPasswordConfirmation(): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip if user has no password (OAuth users)
|
||||
if (! Auth::user()?->hasPassword()) {
|
||||
// OAuth users may have an unusable generated password, so the linked
|
||||
// identity is the source of truth for whether confirmation is possible.
|
||||
if (! Auth::user()?->requiresPasswordConfirmation()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4591,7 +4601,7 @@ function shouldSkipPasswordConfirmation(): bool
|
||||
* Verify password for two-step confirmation.
|
||||
* Skips verification if:
|
||||
* - Two-step confirmation is globally disabled
|
||||
* - User has no password (OAuth users)
|
||||
* - User has no usable local password confirmation (including SSO users)
|
||||
*
|
||||
* @param mixed $password The password to verify (may be array if skipped by frontend)
|
||||
* @param Component|null $component Optional Livewire component to add errors to
|
||||
@@ -4746,6 +4756,23 @@ function downsampleLTTB(array $data, int $threshold): array
|
||||
return $sampled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Sentinel container memory samples from bytes to megabytes.
|
||||
*
|
||||
* Sentinel stores container `used` memory in bytes. Application and database
|
||||
* metric charts label the series as megabytes, so the values must be converted
|
||||
* before they are sent to the frontend.
|
||||
*
|
||||
* @param array<int, array{0: int|float, 1: int|float}> $metrics
|
||||
* @return array<int, array{0: int, 1: float}>
|
||||
*/
|
||||
function convertContainerMemoryBytesToMegabytes(array $metrics): array
|
||||
{
|
||||
return array_map(static function (array $point): array {
|
||||
return [(int) $point[0], round(((float) $point[1]) / 1024 / 1024, 2)];
|
||||
}, $metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve shared environment variable patterns like {{environment.VAR}}, {{project.VAR}}, {{team.VAR}}.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<?php
|
||||
|
||||
use App\Auth\Oidc\OidcConfig;
|
||||
use App\Models\OauthSetting;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\BitbucketProvider;
|
||||
use Laravel\Socialite\Two\GithubProvider;
|
||||
use Laravel\Socialite\Two\GitlabProvider;
|
||||
use SocialiteProviders\Discord\Provider;
|
||||
use SocialiteProviders\Manager\Config;
|
||||
|
||||
function get_socialite_provider(string $provider)
|
||||
{
|
||||
@@ -12,7 +18,7 @@ function get_socialite_provider(string $provider)
|
||||
}
|
||||
|
||||
if ($provider === 'azure') {
|
||||
$azure_config = new \SocialiteProviders\Manager\Config(
|
||||
$azure_config = new Config(
|
||||
$oauth_setting->client_id,
|
||||
$oauth_setting->client_secret,
|
||||
$oauth_setting->redirect_uri,
|
||||
@@ -23,7 +29,7 @@ function get_socialite_provider(string $provider)
|
||||
}
|
||||
|
||||
if ($provider == 'authentik' || $provider == 'clerk') {
|
||||
$authentik_clerk_config = new \SocialiteProviders\Manager\Config(
|
||||
$authentik_clerk_config = new Config(
|
||||
$oauth_setting->client_id,
|
||||
$oauth_setting->client_secret,
|
||||
$oauth_setting->redirect_uri,
|
||||
@@ -34,7 +40,7 @@ function get_socialite_provider(string $provider)
|
||||
}
|
||||
|
||||
if ($provider == 'zitadel') {
|
||||
$zitadel_config = new \SocialiteProviders\Manager\Config(
|
||||
$zitadel_config = new Config(
|
||||
$oauth_setting->client_id,
|
||||
$oauth_setting->client_secret,
|
||||
$oauth_setting->redirect_uri,
|
||||
@@ -44,8 +50,12 @@ function get_socialite_provider(string $provider)
|
||||
return Socialite::driver('zitadel')->setConfig($zitadel_config);
|
||||
}
|
||||
|
||||
if ($provider === 'oidc') {
|
||||
return Socialite::driver('oidc')->setConfig(OidcConfig::fromOauthSetting($oauth_setting));
|
||||
}
|
||||
|
||||
if ($provider == 'google') {
|
||||
$google_config = new \SocialiteProviders\Manager\Config(
|
||||
$google_config = new Config(
|
||||
$oauth_setting->client_id,
|
||||
$oauth_setting->client_secret,
|
||||
$oauth_setting->redirect_uri
|
||||
@@ -63,11 +73,11 @@ function get_socialite_provider(string $provider)
|
||||
];
|
||||
|
||||
$provider_class_map = [
|
||||
'bitbucket' => \Laravel\Socialite\Two\BitbucketProvider::class,
|
||||
'discord' => \SocialiteProviders\Discord\Provider::class,
|
||||
'github' => \Laravel\Socialite\Two\GithubProvider::class,
|
||||
'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class,
|
||||
'infomaniak' => \SocialiteProviders\Infomaniak\Provider::class,
|
||||
'bitbucket' => BitbucketProvider::class,
|
||||
'discord' => Provider::class,
|
||||
'github' => GithubProvider::class,
|
||||
'gitlab' => GitlabProvider::class,
|
||||
'infomaniak' => SocialiteProviders\Infomaniak\Provider::class,
|
||||
];
|
||||
|
||||
$socialite = Socialite::buildProvider(
|
||||
|
||||
@@ -95,6 +95,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
|
||||
$isComplexPipeCommand = (
|
||||
$line->contains(' | sh') ||
|
||||
$line->contains(' | bash') ||
|
||||
$line->contains(' sh -c ') ||
|
||||
($line->contains(' | ') && ($line->contains('||') || $line->contains('&&')))
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user