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:
Aditya Tripathi
2026-08-20 09:33:47 +00:00
873 changed files with 20312 additions and 11574 deletions
+100 -17
View File
@@ -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,
+61 -2
View File
@@ -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())
+23 -4
View File
@@ -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
View File
@@ -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',
]);
}
/**
+1 -1
View File
@@ -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());
+4 -6
View File
@@ -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) {
+38 -11
View File
@@ -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}}.
*
+19 -9
View File
@@ -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(
+1
View File
@@ -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('&&')))
);