Merge pull request #5489 from coollabsio/next

v4.0.0-beta.402
This commit is contained in:
Andras Bacsai
2025-04-01 11:07:21 +02:00
committed by GitHub
49 changed files with 1908 additions and 1142 deletions

View File

@@ -33,6 +33,26 @@ All notable changes to this project will be documented in this file.
## [4.0.0-beta.400] - 2025-03-27
### 🚀 Features
- *(database)* Disable MongoDB SSL by default in migration
### 🚜 Refactor
- *(proxy)* Improve port availability checks with multiple methods
- *(database)* Update MongoDB SSL configuration for improved security
- *(database)* Enhance SSL configuration handling for various databases
- *(notifications)* Update Telegram button URL for staging environment
- *(models)* Remove unnecessary cloud check in isEnabled method
- *(database)* Streamline event listeners in Redis General component
- *(database)* Remove redundant database status display in MongoDB view
- *(database)* Update import statements for Auth in database components
- *(database)* Require PEM key file for SSL certificate regeneration
- *(database)* Change MySQL daemon command to MariaDB daemon
## [4.0.0-beta.399] - 2025-03-25
### 🚀 Features
- *(database)* Disable MongoDB SSL by default in migration

View File

@@ -243,4 +243,4 @@ To add a new service to Coolify, please refer to our documentation:
### Contributing to Documentation
To contribute to the Coolify documentation, please refer to this guide:
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/readme.md)

View File

@@ -57,6 +57,17 @@ class StartDragonfly
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -58,6 +58,17 @@ class StartKeydb
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -59,6 +59,17 @@ class StartMariadb
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -63,6 +63,16 @@ class StartMongodb
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -59,6 +59,17 @@ class StartMysql
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -64,6 +64,17 @@ class StartPostgresql
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -58,6 +58,17 @@ class StartRedis
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
$this->ssl_certificate = $this->database->sslCertificates()->first();
if (! $this->ssl_certificate) {

View File

@@ -5,12 +5,10 @@ namespace App\Console\Commands;
use App\Models\InstanceSettings;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Process;
use Symfony\Component\Yaml\Yaml;
class Dev extends Command
{
protected $signature = 'dev {--init} {--generate-openapi}';
protected $signature = 'dev {--init}';
protected $description = 'Helper commands for development.';
@@ -21,36 +19,6 @@ class Dev extends Command
return;
}
if ($this->option('generate-openapi')) {
$this->generateOpenApi();
return;
}
}
public function generateOpenApi()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
// https://github.com/OAI/OpenAPI-Specification/releases
$process = Process::run([
'/var/www/html/vendor/bin/openapi',
'app',
'-o',
'openapi.yaml',
'--version',
'3.1.0',
]);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
// Convert YAML to JSON
$yaml = file_get_contents('openapi.yaml');
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
file_put_contents('openapi.json', $json);
echo "Converted OpenAPI YAML to JSON.\n";
}
public function init()

View File

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
use Symfony\Component\Yaml\Yaml;
class OpenApi extends Command
{
@@ -29,5 +30,10 @@ class OpenApi extends Command
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
$yaml = file_get_contents('openapi.yaml');
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
file_put_contents('openapi.json', $json);
echo "Converted OpenAPI YAML to JSON.\n";
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
use App\Actions\Database\StartDatabase;
use App\Actions\Service\StartService;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Server;
use App\Models\Tag;
@@ -142,6 +143,7 @@ class DeployController extends Controller
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.', schema: new OA\Schema(type: 'integer')),
],
responses: [
@@ -184,26 +186,32 @@ class DeployController extends Controller
public function deploy(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
$pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0;
if ($uuids && $tags) {
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
}
if (is_null($teamId)) {
return invalidTokenResponse();
if ($tags && $pr) {
return response()->json(['message' => 'You can only use tag or pr, not both.'], 400);
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
} elseif ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
return $this->by_uuids($uuids, $teamId, $force, $pr);
}
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0)
{
$uuids = explode(',', $uuid);
$uuids = collect(array_filter($uuids));
@@ -216,7 +224,7 @@ class DeployController extends Controller
foreach ($uuids as $uuid) {
$resource = getResourceByUuid($uuid, $teamId);
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force, $pr);
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
@@ -281,7 +289,7 @@ class DeployController extends Controller
return response()->json(['message' => 'No resources found with this tag.'], 404);
}
public function deploy_resource($resource, bool $force = false): array
public function deploy_resource($resource, bool $force = false, int $pr = 0): array
{
$message = null;
$deployment_uuid = null;
@@ -295,6 +303,7 @@ class DeployController extends Controller
application: $resource,
deployment_uuid: $deployment_uuid,
force_rebuild: $force,
pull_request_id: $pr,
);
$message = "Application {$resource->name} deployment queued.";
break;
@@ -314,4 +323,68 @@ class DeployController extends Controller
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
}
#[OA\Get(
summary: 'List application deployments',
description: 'List application deployments by using the app uuid',
path: '/deployments/applications/{uuid}',
operationId: 'list-deployments-by-app-uuid',
security: [
['bearerAuth' => []],
],
tags: ['Deployments'],
responses: [
new OA\Response(
response: 200,
description: 'List application deployments by using the app uuid.',
content: [
new OA\MediaType(
mediaType: 'application/json',
schema: new OA\Schema(
type: 'array',
items: new OA\Items(ref: '#/components/schemas/Application'),
)
),
]),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
),
new OA\Response(
response: 400,
ref: '#/components/responses/400',
),
]
)]
public function get_application_deployments(Request $request)
{
$request->validate([
'skip' => ['nullable', 'integer', 'min:0'],
'take' => ['nullable', 'integer', 'min:1'],
]);
$app_uuid = $request->route('uuid', null);
$skip = $request->get('skip', 0);
$take = $request->get('take', 10);
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$servers = Server::whereTeamId($teamId)->get();
if (is_null($app_uuid)) {
return response()->json(['message' => 'Application uuid is required'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $app_uuid)->first();
if (is_null($application)) {
return response()->json(['message' => 'Application not found'], 404);
}
$deployments = $application->deployments($skip, $take);
return response()->json($deployments);
}
}

View File

@@ -2027,7 +2027,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
} else {
$nginx_config = base64_encode(defaultNginxConfiguration());
if ($this->application->settings->is_spa) {
$nginx_config = base64_encode(defaultNginxConfiguration('spa'));
} else {
$nginx_config = base64_encode(defaultNginxConfiguration());
}
}
} else {
if ($this->application->build_pack === 'nixpacks') {
@@ -2094,7 +2098,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
} else {
$nginx_config = base64_encode(defaultNginxConfiguration());
if ($this->application->settings->is_spa) {
$nginx_config = base64_encode(defaultNginxConfiguration('spa'));
} else {
$nginx_config = base64_encode(defaultNginxConfiguration());
}
}
}
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";

View File

@@ -484,6 +484,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$fullImageName = $this->getFullImageName();
$containerExists = instant_remote_process(["docker ps -a -q -f name=backup-of-{$this->backup->uuid}"], $this->server, false);
if (filled($containerExists)) {
instant_remote_process(["docker rm -f backup-of-{$this->backup->uuid}"], $this->server, false);
}
if (isDev()) {
if ($this->database->name === 'coolify-db') {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;

View File

@@ -86,6 +86,7 @@ class General extends Component
'application.post_deployment_command_container' => 'nullable',
'application.custom_nginx_configuration' => 'nullable',
'application.settings.is_static' => 'boolean|required',
'application.settings.is_spa' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
@@ -124,6 +125,7 @@ class General extends Component
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
'application.settings.is_static' => 'Is static',
'application.settings.is_spa' => 'Is SPA',
'application.settings.is_build_server_enabled' => 'Is build server enabled',
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
@@ -171,6 +173,9 @@ class General extends Component
public function instantSave()
{
if ($this->application->settings->isDirty('is_spa')) {
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
@@ -190,6 +195,7 @@ class General extends Component
if ($this->application->settings->is_container_label_readonly_enabled) {
$this->resetDefaultLabels(false);
}
}
public function loadComposeFile($isInit = false)
@@ -287,9 +293,9 @@ class General extends Component
}
}
public function generateNginxConfiguration()
public function generateNginxConfiguration($type = 'static')
{
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
$this->application->custom_nginx_configuration = defaultNginxConfiguration($type);
$this->application->save();
$this->dispatch('success', 'Nginx configuration generated.');
}

View File

@@ -214,10 +214,23 @@ class General extends Component
return;
}
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
$server = $this->database->destination->server;
$caCert = SslCertificate::where('server_id', $server->id)
->where('is_ca_certificate', true)
->first();
if (! $caCert) {
$server->generateCaCertificate();
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
}
if (! $caCert) {
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
return;
}
SslHelper::generateSslCertificate(
commonName: $existingCert->commonName,
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],

View File

@@ -1530,7 +1530,6 @@ class Application extends BaseModel
$interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/');
$timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/');
$start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/');
$start_interval = str($healthcheckCommand)->match('/--start-interval=([0-9]+[a-zµ]*)/');
$retries = str($healthcheckCommand)->match('/--retries=(\d+)/');
if ($interval->isNotEmpty()) {
@@ -1542,13 +1541,10 @@ class Application extends BaseModel
if ($start_period->isNotEmpty()) {
$this->health_check_start_period = parseDockerfileInterval($start_period);
}
if ($start_interval->isNotEmpty()) {
$this->health_check_start_interval = parseDockerfileInterval($start_interval);
}
if ($retries->isNotEmpty()) {
$this->health_check_retries = $retries->toInteger();
}
if ($interval || $timeout || $start_period || $start_interval || $retries) {
if ($interval || $timeout || $start_period || $retries) {
$this->custom_healthcheck_found = true;
$this->save();
}

View File

@@ -9,8 +9,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
class LocalFileVolume extends BaseModel
{
protected $casts = [
'fs_path' => 'encrypted',
'mount_path' => 'encrypted',
// 'fs_path' => 'encrypted',
// 'mount_path' => 'encrypted',
'content' => 'encrypted',
'is_directory' => 'boolean',
];

View File

@@ -7,7 +7,9 @@ use App\Actions\Server\InstallDocker;
use App\Actions\Server\StartSentinel;
use App\Enums\ProxyTypes;
use App\Events\ServerReachabilityChanged;
use App\Helpers\SslHelper;
use App\Jobs\CheckAndStartSentinelJob;
use App\Jobs\RegenerateSslCertJob;
use App\Notifications\Server\Reachable;
use App\Notifications\Server\Unreachable;
use App\Services\ConfigurationRepository;
@@ -1337,4 +1339,41 @@ $schema://$host {
$configRepository = app(ConfigurationRepository::class);
$configRepository->disableSshMux();
}
public function generateCaCertificate()
{
try {
ray('Generating CA certificate for server', $this->id);
SslHelper::generateSslCertificate(
commonName: 'Coolify CA Certificate',
serverId: $this->id,
isCaCertificate: true,
validityDays: 10 * 365
);
$caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first();
ray('CA certificate generated', $caCertificate);
if ($caCertificate) {
$certificateContent = $caCertificate->ssl_certificate;
$caCertPath = config('constants.coolify.base_config_path').'/ssl/';
$commands = collect([
"mkdir -p $caCertPath",
"chown -R 9999:root $caCertPath",
"chmod -R 700 $caCertPath",
"rm -rf $caCertPath/coolify-ca.crt",
"echo '{$certificateContent}' > $caCertPath/coolify-ca.crt",
"chmod 644 $caCertPath/coolify-ca.crt",
]);
instant_remote_process($commands, $this, false);
dispatch(new RegenerateSslCertJob(
server_id: $this->id,
force_regeneration: true
));
}
} catch (\Throwable $e) {
return handleError($e);
}
}
}

View File

@@ -1363,15 +1363,21 @@ function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull
$source = $source."-pr-$pull_request_id";
}
if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) {
$volume = LocalFileVolume::wherePlainMountPath($target)->first() ?? new LocalFileVolume;
$volume->fill([
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
])->save();
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
]
);
}
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
@@ -1670,27 +1676,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $volume;
}
$existingVolume = LocalFileVolume::wherePlainMountPath($target)->first();
if ($existingVolume) {
$existingVolume->update([
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]);
} else {
LocalFileVolume::create([
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]);
}
]
);
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
@@ -3175,6 +3175,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
}
}
$serviceAppsLogDrainEnabledMap = collect([]);
if ($resource instanceof Service) {
$serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) {
return $app->isLogDrainEnabled();
});
}
// Parse the rest of the services
foreach ($services as $serviceName => $service) {
$image = data_get_str($service, 'image');
@@ -3185,6 +3192,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($resource instanceof Application && $resource->isLogDrainEnabled()) {
$logging = generate_fluentd_configuration();
}
if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) {
$logging = generate_fluentd_configuration();
}
}
$volumes = collect(data_get($service, 'volumes', []));
$networks = collect(data_get($service, 'networks', []));
@@ -3328,15 +3338,21 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
}
$volume = LocalFileVolume::wherePlainMountPath($target)->first() ?? new LocalFileVolume;
$volume->fill([
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $originalResource->id,
'resource_type' => get_class($originalResource),
])->save();
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $originalResource->id,
'resource_type' => get_class($originalResource),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $originalResource->id,
'resource_type' => get_class($originalResource),
]
);
if (isDev()) {
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
@@ -4055,9 +4071,35 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla
return $rateLimited;
}
function defaultNginxConfiguration(): string
function defaultNginxConfiguration(string $type = 'static'): string
{
return 'server {
if ($type === 'spa') {
return <<<'NGINX'
server {
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# Handle 404 errors
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
# Handle server errors (50x)
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
NGINX;
} else {
return <<<'NGINX'
server {
location / {
root /usr/share/nginx/html;
index index.html index.htm;
@@ -4077,7 +4119,9 @@ function defaultNginxConfiguration(): string
root /usr/share/nginx/html;
internal;
}
}';
}
NGINX;
}
}
function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array

View File

@@ -12,65 +12,63 @@
],
"require": {
"php": "^8.4",
"3sidedcube/laravel-redoc": "^1.0",
"danharrin/livewire-rate-limiting": "2.0.0",
"doctrine/dbal": "^4.2",
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^1.16.0",
"laravel/framework": "^11.0",
"laravel/horizon": "^5.29.1",
"laravel/pail": "^1.1",
"laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
"laravel/sanctum": "^4.0",
"laravel/socialite": "^5.14.0",
"laravel/tinker": "^2.8.1",
"laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-sftp-v3": "^3.0",
"livewire/livewire": "^3.5",
"log1x/laravel-webfonts": "^1.0",
"lorisleiva/laravel-actions": "^2.8",
"danharrin/livewire-rate-limiting": "2.1.0",
"doctrine/dbal": "^4.2.2",
"guzzlehttp/guzzle": "^7.9.2",
"laravel/fortify": "^1.25.4",
"laravel/framework": "12.4.1",
"laravel/horizon": "^5.30.3",
"laravel/pail": "^1.2.2",
"laravel/prompts": "^0.3.5|^0.3.5|^0.3.5",
"laravel/sanctum": "^4.0.8",
"laravel/socialite": "^5.18.0",
"laravel/tinker": "^2.10.1",
"laravel/ui": "^4.6.1",
"lcobucci/jwt": "^5.5.0",
"league/flysystem-aws-s3-v3": "^3.29",
"league/flysystem-sftp-v3": "^3.29",
"livewire/livewire": "^3.5.20",
"log1x/laravel-webfonts": "^2.0.1",
"lorisleiva/laravel-actions": "^2.8.6",
"nubs/random-name-generator": "^2.2",
"phpseclib/phpseclib": "^3.0",
"pion/laravel-chunk-upload": "^1.5",
"poliander/cron": "^3.0",
"purplepixie/phpdns": "^2.1",
"pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.15.0",
"sentry/sentry-laravel": "^4.6",
"phpseclib/phpseclib": "^3.0.43",
"pion/laravel-chunk-upload": "^1.5.4",
"poliander/cron": "^3.2.1",
"purplepixie/phpdns": "^2.2",
"pusher/pusher-php-server": "^7.2.7",
"resend/resend-laravel": "^0.17.0",
"sentry/sentry-laravel": "^4.13",
"socialiteproviders/authentik": "^5.2",
"socialiteproviders/google": "^4.1",
"socialiteproviders/infomaniak": "^4.0",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^4.11",
"spatie/laravel-ray": "^1.37",
"spatie/laravel-schemaless-attributes": "^2.4",
"spatie/url": "^2.2",
"stevebauman/purify": "^6.2",
"stripe/stripe-php": "^16.2.0",
"symfony/yaml": "^7.1.6",
"socialiteproviders/microsoft-azure": "^5.2",
"spatie/laravel-activitylog": "^4.10.1",
"spatie/laravel-data": "^4.13.1",
"spatie/laravel-ray": "^1.39.1",
"spatie/laravel-schemaless-attributes": "^2.5.1",
"spatie/url": "^2.4",
"stevebauman/purify": "^6.3",
"stripe/stripe-php": "^16.5.1",
"symfony/yaml": "^7.2.3",
"visus/cuid2": "^4.1.0",
"yosymfony/toml": "^1.0",
"zircote/swagger-php": "^5.0"
"yosymfony/toml": "^1.0.4",
"zircote/swagger-php": "^5.0.5"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.13",
"driftingly/rector-laravel": "^2.0",
"fakerphp/faker": "^1.21.0",
"laravel/dusk": "^8.0",
"laravel/pint": "^1.16",
"laravel/telescope": "^5.2",
"mockery/mockery": "^1.5.1",
"nunomaduro/collision": "^8.1",
"pestphp/pest": "^3.5",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5",
"rector/rector": "^2.0",
"serversideup/spin": "^3.0",
"spatie/laravel-ignition": "^2.1.0",
"symfony/http-client": "^7.1"
"barryvdh/laravel-debugbar": "^3.15.1",
"driftingly/rector-laravel": "^2.0.2",
"fakerphp/faker": "^1.24.1",
"laravel/dusk": "^8.3.1",
"laravel/pint": "^1.21",
"laravel/telescope": "^5.5",
"mockery/mockery": "^1.6.12",
"nunomaduro/collision": "^8.6.1",
"pestphp/pest": "^3.8.0",
"phpstan/phpstan": "^2.1.6",
"rector/rector": "^2.0.9",
"serversideup/spin": "^3.0.2",
"spatie/laravel-ignition": "^2.9.1",
"symfony/http-client": "^7.2.3"
},
"minimum-stability": "stable",
"prefer-stable": true,

919
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
return [
'coolify' => [
'version' => '4.0.0-beta.401',
'helper_version' => '1.0.7',
'version' => '4.0.0-beta.402',
'helper_version' => '1.0.8',
'realtime_version' => '1.0.6',
'self_hosted' => env('SELF_HOSTED', true),
'autoupdate' => env('AUTOUPDATE'),

View File

@@ -1,28 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Directory
|--------------------------------------------------------------------------
|
| The name of the directory where your OpenAPI definitions are stored.
|
*/
'directory' => '',
/*
|--------------------------------------------------------------------------
| Variables
|--------------------------------------------------------------------------
|
| You can automatically replace variables in your OpenAPI definitions by
| adding a key value pair to the array below. This will replace any
| instances of :key with the given value.
|
*/
'variables' => [],
];

View File

@@ -0,0 +1,160 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
echo "Starting local file volumes migration...\n";
if (DB::table('local_file_volumes')->exists()) {
echo "Found local_file_volumes table, proceeding with migration...\n";
// First, get all volumes and decrypt their values
$decryptedVolumes = collect();
$totalVolumes = DB::table('local_file_volumes')->count();
echo "Total volumes to process: {$totalVolumes}\n";
DB::table('local_file_volumes')
->orderBy('id')
->chunk(100, function ($volumes) use (&$decryptedVolumes) {
echo 'Processing chunk of '.count($volumes)." volumes...\n";
foreach ($volumes as $volume) {
try {
$fs_path = $volume->fs_path;
$mount_path = $volume->mount_path;
try {
if ($fs_path) {
$fs_path = Crypt::decryptString($fs_path);
}
} catch (\Exception $e) {
echo "Warning: Could not decrypt fs_path for volume {$volume->id}\n";
}
try {
if ($mount_path) {
$mount_path = Crypt::decryptString($mount_path);
}
} catch (\Exception $e) {
echo "Warning: Could not decrypt mount_path for volume {$volume->id}\n";
}
$decryptedVolumes->push([
'id' => $volume->id,
'fs_path' => $fs_path,
'mount_path' => $mount_path,
'resource_id' => $volume->resource_id,
'resource_type' => $volume->resource_type,
]);
} catch (\Exception $e) {
echo "Error decrypting volume {$volume->id}: {$e->getMessage()}\n";
Log::error("Error decrypting volume {$volume->id}: ".$e->getMessage());
}
}
});
echo 'Finished processing all volumes. Found '.$decryptedVolumes->count()." total volumes.\n";
// Group by the unique constraint fields and keep only the first occurrence
$uniqueVolumes = $decryptedVolumes->groupBy(function ($volume) {
return $volume['mount_path'].'|'.$volume['resource_id'].'|'.$volume['resource_type'];
})->map(function ($group) {
return $group->first();
});
echo 'After deduplication, found '.$uniqueVolumes->count()." unique volumes.\n";
// Get IDs to delete (all except the ones we're keeping)
$idsToKeep = $uniqueVolumes->pluck('id')->toArray();
$idsToDelete = $decryptedVolumes->pluck('id')->diff($idsToKeep)->toArray();
// Delete duplicate records
if (! empty($idsToDelete)) {
echo "\nFound ".count($idsToDelete)." duplicate volumes to delete.\n";
// Show details of volumes being deleted
$volumesToDelete = $decryptedVolumes->whereIn('id', $idsToDelete);
echo "\nVolumes to be deleted:\n";
foreach ($volumesToDelete as $volume) {
echo "ID: {$volume['id']}, Mount Path: {$volume['mount_path']}, Resource ID: {$volume['resource_id']}, Resource Type: {$volume['resource_type']}\n";
echo "FS Path: {$volume['fs_path']}\n";
echo "-------------------\n";
}
DB::table('local_file_volumes')->whereIn('id', $idsToDelete)->delete();
echo 'Deleted '.count($idsToDelete)." duplicate volume(s)\n";
}
echo "\nUpdating remaining volumes with decrypted values...\n";
$updateCount = 0;
// Update the remaining records with decrypted values
foreach ($uniqueVolumes as $volume) {
try {
DB::table('local_file_volumes')->where('id', $volume['id'])->update([
'fs_path' => $volume['fs_path'],
'mount_path' => $volume['mount_path'],
]);
$updateCount++;
} catch (\Exception $e) {
echo "Error updating volume {$volume['id']}: {$e->getMessage()}\n";
Log::error("Error updating volume {$volume['id']}: ".$e->getMessage());
}
}
echo "Successfully updated {$updateCount} volumes.\n";
} else {
echo "No local_file_volumes table found, skipping migration.\n";
}
echo "Migration completed successfully.\n";
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (DB::table('local_file_volumes')->exists()) {
DB::table('local_file_volumes')
->orderBy('id')
->chunk(100, function ($volumes) {
foreach ($volumes as $volume) {
DB::beginTransaction();
try {
$fs_path = $volume->fs_path;
$mount_path = $volume->mount_path;
try {
if ($fs_path) {
$fs_path = Crypt::encrypt($fs_path);
}
} catch (\Exception $e) {
}
try {
if ($mount_path) {
$mount_path = Crypt::encrypt($mount_path);
}
} catch (\Exception $e) {
}
DB::table('local_file_volumes')->where('id', $volume->id)->update([
'fs_path' => $fs_path,
'mount_path' => $mount_path,
]);
echo "Updated volume {$volume->id}\n";
} catch (\Exception $e) {
echo "Error decrypting local file volume fields: {$e->getMessage()}\n";
Log::error('Error decrypting local file volume fields: '.$e->getMessage());
}
DB::commit();
}
});
}
}
};

View File

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

View File

@@ -4,15 +4,15 @@ ARG BASE_IMAGE=alpine:3.21
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=28.0.0
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.33.1
ARG DOCKER_COMPOSE_VERSION=2.34.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.21.1
ARG DOCKER_BUILDX_VERSION=0.22.0
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.36.4
ARG PACK_VERSION=0.37.0
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.33.0
ARG NIXPACKS_VERSION=1.34.1
# https://github.com/minio/mc/releases
ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z
ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
FROM minio/mc:${MINIO_VERSION} AS minio-client

View File

@@ -2,7 +2,7 @@
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
# https://github.com/minio/mc/releases
ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z
ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2025.2.0
# https://www.postgresql.org/support/versioning/

View File

@@ -2,7 +2,7 @@
# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine
ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine
# https://github.com/minio/mc/releases
ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z
ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2025.2.0
# https://www.postgresql.org/support/versioning/

View File

@@ -2,9 +2,9 @@
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=28.0.0
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.33.1
ARG DOCKER_COMPOSE_VERSION=2.34.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.21.1
ARG DOCKER_BUILDX_VERSION=0.22.0
FROM debian:12-slim

40
lang/no.json Normal file
View File

@@ -0,0 +1,40 @@
{
"auth.login": "Logg inn",
"auth.login.authentik": "Logg inn med Authentik",
"auth.login.azure": "Logg inn med Microsoft",
"auth.login.bitbucket": "Logg inn med Bitbucket",
"auth.login.github": "Logg inn med GitHub",
"auth.login.gitlab": "Logg inn med Gitlab",
"auth.login.google": "Logg inn med Google",
"auth.login.infomaniak": "Logg inn med Infomaniak",
"auth.already_registered": "Allerede registrert?",
"auth.confirm_password": "Bekreft passord",
"auth.forgot_password": "Glemt passord",
"auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord",
"auth.register_now": "Registrer deg",
"auth.logout": "Logg ut",
"auth.register": "Registrer",
"auth.registration_disabled": "Registrering er deaktivert. Vennligst kontakt administrator.",
"auth.reset_password": "Tilbakestill passord",
"auth.failed": "Disse legitimasjonene samsvarer ikke med våre registre.",
"auth.failed.callback": "Klarte ikke å behandle tilbakekall fra innloggingsleverandør.",
"auth.failed.password": "Det oppgitte passordet er feil.",
"auth.failed.email": "Vi finner ingen bruker med den e-postadressen.",
"auth.throttle": "For mange innloggingsforsøk. Vennligst prøv igjen om :seconds sekunder.",
"input.name": "Navn",
"input.email": "E-post",
"input.password": "Passord",
"input.password.again": "Passord igjen",
"input.code": "Engangskode",
"input.recovery_code": "Gjenopprettingskode",
"button.save": "Lagre",
"repository.url": "<span class='text-helper'>Eksempler</span><br>For offentlige repositorier, bruk <span class='text-helper'>https://...</span>.<br>For private repositorier, bruk <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> gren vil bli valgt<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> gren vil bli valgt.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> gren vil bli valgt.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> gren vil bli valgt.",
"service.stop": "Denne tjenesten vil bli stoppet.",
"resource.docker_cleanup": "Kjør Docker-opprydding (fjern ubrukte bilder og byggebuffer).",
"resource.non_persistent": "Alle ikke-persistente data vil bli slettet.",
"resource.delete_volumes": "Slett alle volumer tilknyttet denne ressursen permanent.",
"resource.delete_connected_networks": "Slett alle ikke-forhåndsdefinerte nettverk tilknyttet denne ressursen permanent.",
"resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.",
"database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.",
"warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er <span class='dark:text-red-500 text-red-500 font-bold'>IKKE</span> anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes). <br><br>Bruk ditt eget domene i stedet."
}

View File

@@ -2105,6 +2105,70 @@
]
}
},
"\/applications\/{uuid}\/logs": {
"get": {
"tags": [
"Applications"
],
"summary": "Get application logs.",
"description": "Get application logs by UUID.",
"operationId": "get-application-logs-by-uuid",
"parameters": [
{
"name": "uuid",
"in": "path",
"description": "UUID of the application.",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
},
{
"name": "lines",
"in": "query",
"description": "Number of lines to show from the end of the logs.",
"required": false,
"schema": {
"type": "integer",
"format": "int32",
"default": 100
}
}
],
"responses": {
"200": {
"description": "Get application logs by UUID.",
"content": {
"application\/json": {
"schema": {
"properties": {
"logs": {
"type": "string"
}
},
"type": "object"
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/applications\/{uuid}\/envs": {
"get": {
"tags": [
@@ -4477,6 +4541,14 @@
"schema": {
"type": "boolean"
}
},
{
"name": "pr",
"in": "query",
"description": "Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.",
"schema": {
"type": "integer"
}
}
],
"responses": {
@@ -4523,6 +4595,42 @@
]
}
},
"\/deployments\/applications\/{uuid}": {
"get": {
"tags": [
"Deployments"
],
"summary": "List application deployments",
"description": "List application deployments by using the app uuid",
"operationId": "list-deployments-by-app-uuid",
"responses": {
"200": {
"description": "List application deployments by using the app uuid.",
"content": {
"application\/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#\/components\/schemas\/Application"
}
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/version": {
"get": {
"summary": "Version",
@@ -5854,8 +5962,8 @@
"tags": [
"Services"
],
"summary": "Create",
"description": "Create a one-click service",
"summary": "Create service",
"description": "Create a one-click \/ custom service",
"operationId": "create-service",
"requestBody": {
"required": true,
@@ -6005,7 +6113,7 @@
},
"responses": {
"201": {
"description": "Create a service.",
"description": "Service created successfully.",
"content": {
"application\/json": {
"schema": {
@@ -6177,6 +6285,114 @@
"bearerAuth": []
}
]
},
"patch": {
"tags": [
"Services"
],
"summary": "Update",
"description": "Update service by UUID.",
"operationId": "update-service-by-uuid",
"requestBody": {
"description": "Service updated.",
"required": true,
"content": {
"application\/json": {
"schema": {
"required": [
"server_uuid",
"project_uuid",
"environment_name",
"environment_uuid",
"docker_compose_raw"
],
"properties": {
"name": {
"type": "string",
"description": "The service name."
},
"description": {
"type": "string",
"description": "The service description."
},
"project_uuid": {
"type": "string",
"description": "The project UUID."
},
"environment_name": {
"type": "string",
"description": "The environment name."
},
"environment_uuid": {
"type": "string",
"description": "The environment UUID."
},
"server_uuid": {
"type": "string",
"description": "The server UUID."
},
"destination_uuid": {
"type": "string",
"description": "The destination UUID."
},
"instant_deploy": {
"type": "boolean",
"description": "The flag to indicate if the service should be deployed instantly."
},
"connect_to_docker_network": {
"type": "boolean",
"default": false,
"description": "Connect the service to the predefined docker network."
},
"docker_compose_raw": {
"type": "string",
"description": "The Docker Compose raw content."
}
},
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Service updated.",
"content": {
"application\/json": {
"schema": {
"properties": {
"uuid": {
"type": "string",
"description": "Service UUID."
},
"domains": {
"type": "array",
"items": {
"type": "string"
},
"description": "Service domains."
}
},
"type": "object"
}
}
}
},
"401": {
"$ref": "#\/components\/responses\/401"
},
"400": {
"$ref": "#\/components\/responses\/400"
},
"404": {
"$ref": "#\/components\/responses\/404"
}
},
"security": [
{
"bearerAuth": []
}
]
}
},
"\/services\/{uuid}\/envs": {

View File

@@ -3152,6 +3152,12 @@ paths:
description: 'Force rebuild (without cache)'
schema:
type: boolean
-
name: pr
in: query
description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.'
schema:
type: integer
responses:
'200':
description: "Get deployment(s) UUID's"
@@ -3168,6 +3174,29 @@ paths:
security:
-
bearerAuth: []
'/deployments/applications/{uuid}':
get:
tags:
- Deployments
summary: 'List application deployments'
description: 'List application deployments by using the app uuid'
operationId: list-deployments-by-app-uuid
responses:
'200':
description: 'List application deployments by using the app uuid.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Application'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
security:
-
bearerAuth: []
/version:
get:
summary: Version
@@ -3995,80 +4024,9 @@ paths:
post:
tags:
- Services
summary: Create
description: 'Create a service'
summary: 'Create service'
description: 'Create a one-click / custom service'
operationId: create-service
requestBody:
required: true
content:
application/json:
schema:
required:
- server_uuid
- project_uuid
- environment_name
- environment_uuid
- docker_compose_raw
properties:
name:
type: string
maxLength: 255
description: 'Name of the service.'
description:
type: string
nullable: true
description: 'Description of the service.'
project_uuid:
type: string
description: 'Project UUID.'
environment_name:
type: string
description: 'Environment name. You need to provide at least one of environment_name or environment_uuid.'
environment_uuid:
type: string
description: 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.'
server_uuid:
type: string
description: 'Server UUID.'
destination_uuid:
type: string
description: 'Destination UUID. Required if server has multiple destinations.'
instant_deploy:
type: boolean
default: false
description: 'Start the service immediately after creation.'
connect_to_docker_network:
type: boolean
default: false
description: 'The flag to connect the service to the predefined Docker network.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'
type: object
responses:
'201':
description: 'Service created successfully.'
content:
application/json:
schema:
properties:
uuid: { type: string, description: 'Service UUID.' }
domains: { type: array, items: { type: string, nullable: true }, description: 'Service domains.' }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
security:
-
bearerAuth: []
/services/one-click:
post:
tags:
- Services
summary: 'Create one-click'
description: 'Create a one-click service'
operationId: create-one-click-service
requestBody:
required: true
content:
@@ -4271,7 +4229,7 @@ paths:
connect_to_docker_network:
type: boolean
default: false
description: 'The flag to connect the service to the predefined Docker network.'
description: 'Connect the service to the predefined docker network.'
docker_compose_raw:
type: string
description: 'The Docker Compose raw content.'

View File

@@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.401"
"version": "4.0.0-beta.402"
},
"nightly": {
"version": "4.0.0-beta.402"
"version": "4.0.0-beta.403"
},
"helper": {
"version": "1.0.7"

901
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,16 +7,16 @@
"build": "vite build"
},
"devDependencies": {
"@vitejs/plugin-vue": "5.2.1",
"autoprefixer": "10.4.20",
"axios": "1.8.2",
"@vitejs/plugin-vue": "5.2.3",
"autoprefixer": "10.4.21",
"axios": "1.8.4",
"laravel-echo": "2.0.2",
"laravel-vite-plugin": "^1.2.0",
"postcss": "8.5.3",
"pusher-js": "8.4.0",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "3.4.17",
"vite": "^6.2.3",
"vite": "^6.2.4",
"vue": "3.5.13"
},
"dependencies": {
@@ -24,6 +24,6 @@
"@tailwindcss/typography": "0.5.16",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"ioredis": "5.5.0"
"ioredis": "5.6.0"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{
"/app.js": "/app.js?id=99f84d421ae083196e0a45c3c310168b",
"/app.js": "/app.js?id=99e99836705c54c9dc04352a9907bc7f",
"/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9",
"/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf"
}

View File

@@ -69,6 +69,17 @@
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
Configuration</x-forms.button>
@endif
<div class="w-96 pb-8">
@if ($application->could_set_build_commands())
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
@endif
@if ($application->settings->is_static && $application->build_pack !== 'static')
<x-forms.checkbox label="Is it a SPA (Single Page Application)?"
helper="If your application is a SPA, enable this." id="application.settings.is_spa"
instantSave></x-forms.checkbox>
@endif
</div>
@if ($application->build_pack !== 'dockercompose')
<div class="flex items-end gap-2">
@if ($application->settings->is_container_label_readonly_enabled == false)
@@ -274,13 +285,6 @@
label="Use a Build Server?" />
</div>
@endif
@if ($application->could_set_build_commands())
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_static"
label="Is it a static site?"
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
</div>
@endif
@endif
</div>
@endif

View File

@@ -90,8 +90,6 @@
</div>
<h4 class="pt-6">API</h4>
<div class="pb-4">For API documentation, please visit <a class="dark:text-warning underline"
href="/docs/api">/docs/api</a></div>
<div class="md:w-96 pb-2">
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
</div>

View File

@@ -56,6 +56,7 @@ Route::group([
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:write,deploy']);
Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['api.ability:read']);
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['api.ability:read']);
Route::get('/deployments/applications/{uuid}', [DeployController::class, 'get_application_deployments'])->middleware(['api.ability:read']);
Route::get('/servers', [ServersController::class, 'servers'])->middleware(['api.ability:read']);
Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid'])->middleware(['api.ability:read']);

View File

@@ -3,7 +3,6 @@
use App\Http\Controllers\Controller;
use App\Http\Controllers\OauthController;
use App\Http\Controllers\UploadController;
use App\Http\Middleware\ApiAllowed;
use App\Livewire\Admin\Index as AdminIndex;
use App\Livewire\Boarding\Index as BoardingIndex;
use App\Livewire\Dashboard;
@@ -78,13 +77,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
use ThreeSidedCube\LaravelRedoc\Http\Controllers\DefinitionController;
use ThreeSidedCube\LaravelRedoc\Http\Controllers\DocumentationController;
Route::group(['middleware' => ['auth:sanctum', ApiAllowed::class]], function () {
Route::get('/docs/api', DocumentationController::class)->name('redoc.documentation');
Route::get('/docs/api/definition', DefinitionController::class)->name('redoc.definition');
});
Route::get('/admin', AdminIndex::class)->name('admin.index');

View File

@@ -6,7 +6,7 @@
services:
authentik-server:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.0}
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.3}
restart: unless-stopped
command: server
environment:
@@ -35,7 +35,7 @@ services:
redis:
condition: service_healthy
authentik-worker:
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.0}
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.2.3}
restart: unless-stopped
command: worker
environment:

View File

@@ -20,6 +20,11 @@ services:
- SMTP_PORT=${SMTP_PORT}
- SMTP_FROM_ADDRESS=${SMTP_FROM_ADDRESS}
- SMTP_FROM_NAME=${SMTP_FROM_NAME}
- INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=${INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID}
- INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=${INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET}
- INF_APP_CONNECTION_GITHUB_APP_SLUG=${INF_APP_CONNECTION_GITHUB_APP_SLUG}
- INF_APP_CONNECTION_GITHUB_APP_ID=${INF_APP_CONNECTION_GITHUB_APP_ID}
- INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=${INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY}
- DB_CONNECTION_URI=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/${POSTGRES_DB}
- REDIS_URL=redis://redis:6379
healthcheck:

View File

@@ -25,16 +25,19 @@ services:
- ANNOUNCE_PLAYER_ACHIEVEMENTS=${MINECRAFT_ANNOUNCE_PLAYER_ACHIEVEMENTS:-true}
- GENERATE_STRUCTURES=${MINECRAFT_GENERATE_STRUCTURES:-true}
- PVP=${MINECRAFT_PVP:-true}
- MODE=${MINECRAFT_GAME_MODE:-survival}
- FORCE_GAMEMODE=${MINECRAFT_FORCE_GAMEMODE:-false}
- HARDCORE=${MINECRAFT_HARDCORE:-false}
- ENABLE_COMMAND_BLOCK=${MINECRAFT_ENABLE_COMMAND_BLOCK:-false}
- SPAWN_ANIMALS=${MINECRAFT_SPAWN_ANIMALS:-true}
- SPAWN_MONSTERS=${MINECRAFT_SPAWN_MONSTERS:-true}
- SPAWN_NPCS=${MINECRAFT_SPAWN_NPCS:-true}
- SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-true}
- SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-false}
- ONLINE_MODE=${MINECRAFT_ONLINE_MODE:-true}
- PLAYER_IDLE_TIMEOUT=${MINECRAFT_PLAYER_IDLE_TIMEOUT:-0}
- MEMORY=${MINECRAFT_MEMORY:-1G}
- INIT_MEMORY=${MINECRAFT_INIT_MEMORY:-256M}
- MAX_MEMORY=${MINECRAFT_MAX_MEMORY:-1G}
- GUI=${MINECRAFT_GUI:-false}
- ENABLE_AUTOPAUSE=${MINECRAFT_ENABLE_AUTOPAUSE:-false}
- RCON_PASSWORD=${SERVICE_PASSWORD_RCON}
- PORT=${PORT:-25565}

View File

@@ -15,6 +15,8 @@ services:
- BASE_URL=${SERVICE_FQDN_PLAUSIBLE}
- SECRET_KEY_BASE=${SERVICE_BASE64_64_PLAUSIBLE}
- TOTP_VAULT_KEY=${SERVICE_REALBASE64_32_TOTP}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
depends_on:
plausible-db:
condition: service_healthy

View File

@@ -30,8 +30,15 @@ services:
- WAKAPI_MAIL_SENDER=${WAKAPI_MAIL_SENDER}
- WAKAPI_MAIL_SMTP_HOST=${WAKAPI_MAIL_SMTP_HOST}
- WAKAPI_MAIL_SMTP_PORT=${WAKAPI_MAIL_SMTP_PORT:-587}
- WAKAPI_MAIL_SMTP_USERNAME=${WAKAPI_MAIL_SMTP_USERNAME}
- WAKAPI_MAIL_SMTP_PASSWORD=${WAKAPI_MAIL_SMTP_PASSWORD}
- WAKAPI_MAIL_SMTP_USER=${WAKAPI_MAIL_SMTP_USERNAME}
- WAKAPI_MAIL_SMTP_PASS=${WAKAPI_MAIL_SMTP_PASSWORD}
- WAKAPI_MAIL_SMTP_TLS=${WAKAPI_MAIL_SMTP_TLS:-true}
# Instance configuration
- WAKAPI_ALLOW_SIGNUP=${WAKAPI_ALLOW_SIGNUP:-true}
- WAKAPI_LEADERBOARD_ENABLED=${WAKAPI_LEADERBOARD_ENABLED:-true}
- WAKAPI_DISABLE_FRONTPAGE=${WAKAPI_DISABLE_FRONTPAGE:-true}
- WAKAPI_PUBLIC_URL=${WAKAPI_PUBLIC_URL}
volumes:
- wakapi-data:/data

View File

@@ -144,7 +144,7 @@
"authentik": {
"documentation": "https://docs.goauthentik.io/docs/installation/docker-compose?utm_source=coolify.io",
"slogan": "An open-source Identity Provider, focused on flexibility and versatility.",
"compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjIuMH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjIuMH0nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==",
"compose": "c2VydmljZXM6CiAgYXV0aGVudGlrLXNlcnZlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjIuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogc2VydmVyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQVVUSEVOVElLU0VSVkVSXzkwMDAKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEOi10cnVlfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21lZGlhOi9tZWRpYScKICAgICAgLSAnLi9jdXN0b20tdGVtcGxhdGVzOi90ZW1wbGF0ZXMnCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgYXV0aGVudGlrLXdvcmtlcjoKICAgIGltYWdlOiAnZ2hjci5pby9nb2F1dGhlbnRpay9zZXJ2ZXI6JHtBVVRIRU5USUtfVEFHOi0yMDI1LjIuM30nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgY29tbWFuZDogd29ya2VyCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnQVVUSEVOVElLX1JFRElTX19IT1NUPSR7UkVESVNfSE9TVDotcmVkaXN9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fSE9TVD0ke1BPU1RHUkVTX0hPU1Q6LXBvc3RncmVzcWx9JwogICAgICAtICdBVVRIRU5USUtfUE9TVEdSRVNRTF9fVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX05BTUU9JHtQT1NUR1JFU19EQjotYXV0aGVudGlrfScKICAgICAgLSAnQVVUSEVOVElLX1BPU1RHUkVTUUxfX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnQVVUSEVOVElLX1NFQ1JFVF9LRVk9JHtTRVJWSUNFX1BBU1NXT1JEXzY0X0FVVEhFTlRJS1NFUlZFUn0nCiAgICAgIC0gJ0FVVEhFTlRJS19FUlJPUl9SRVBPUlRJTkdfX0VOQUJMRUQ9JHtBVVRIRU5USUtfRVJST1JfUkVQT1JUSU5HX19FTkFCTEVEfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19IT1NUPSR7QVVUSEVOVElLX0VNQUlMX19IT1NUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19QT1JUPSR7QVVUSEVOVElLX0VNQUlMX19QT1JUfScKICAgICAgLSAnQVVUSEVOVElLX0VNQUlMX19VU0VSTkFNRT0ke0FVVEhFTlRJS19FTUFJTF9fVVNFUk5BTUV9JwogICAgICAtICdBVVRIRU5USUtfRU1BSUxfX1BBU1NXT1JEPSR7QVVUSEVOVElLX0VNQUlMX19QQVNTV09SRH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMUz0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1RMU30nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTD0ke0FVVEhFTlRJS19FTUFJTF9fVVNFX1NTTH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVD0ke0FVVEhFTlRJS19FTUFJTF9fVElNRU9VVH0nCiAgICAgIC0gJ0FVVEhFTlRJS19FTUFJTF9fRlJPTT0ke0FVVEhFTlRJS19FTUFJTF9fRlJPTX0nCiAgICB1c2VyOiByb290CiAgICB2b2x1bWVzOgogICAgICAtICcvdmFyL3J1bi9kb2NrZXIuc29jazovdmFyL3J1bi9kb2NrZXIuc29jaycKICAgICAgLSAnLi9tZWRpYTovbWVkaWEnCiAgICAgIC0gJy4vY2VydHM6L2NlcnRzJwogICAgICAtICcuL2N1c3RvbS10ZW1wbGF0ZXM6L3RlbXBsYXRlcycKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNi1hbHBpbmUnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1kICQke1BPU1RHUkVTX0RCfSAtVSAkJHtQT1NUR1JFU19VU0VSfScKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAnYXV0aGVudGlrLWRiOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gUE9TVEdSRVNfREI9YXV0aGVudGlrCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOmFscGluZScKICAgIGNvbW1hbmQ6ICctLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcnCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgcGluZyB8IGdyZXAgUE9ORycKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXM6L2RhdGEnCg==",
"tags": [
"identity",
"login",
@@ -1439,7 +1439,7 @@
"infisical": {
"documentation": "https://infisical.com/docs/documentation/getting-started/introduction?utm_source=coolify.io",
"slogan": "Infisical is the open source secret management platform that developers use to centralize their application configuration and secrets like API keys and database credentials.",
"compose": "c2VydmljZXM6CiAgYmFja2VuZDoKICAgIGltYWdlOiAnaW5maXNpY2FsL2luZmlzaWNhbDpsYXRlc3QtcG9zdGdyZXMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkFDS0VORF84MDgwCiAgICAgIC0gJ1NJVEVfVVJMPSR7U0VSVklDRV9GUUROX0JBQ0tFTkRfODA4MH0nCiAgICAgIC0gJ05PREVfRU5WPSR7Tk9ERV9FTlY6LXByb2R1Y3Rpb259JwogICAgICAtICdFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfRU5DUllQVElPTktFWX0nCiAgICAgIC0gJ0FVVEhfU0VDUkVUPSR7U0VSVklDRV9SRUFMQkFTRTY0XzY0X0FVVEhTRUNSRVR9JwogICAgICAtICdTTVRQX0hPU1Q9JHtTTVRQX0hPU1R9JwogICAgICAtICdTTVRQX1VTRVJOQU1FPSR7U01UUF9VU0VSTkFNRX0nCiAgICAgIC0gJ1NNVFBfUEFTU1dPUkQ9JHtTTVRQX1BBU1NXT1JEfScKICAgICAgLSAnU01UUF9QT1JUPSR7U01UUF9QT1JUfScKICAgICAgLSAnU01UUF9GUk9NX0FERFJFU1M9JHtTTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ1NNVFBfRlJPTV9OQU1FPSR7U01UUF9GUk9NX05BTUV9JwogICAgICAtICdEQl9DT05ORUNUSU9OX1VSST1wb3N0Z3JlczovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRiOjU0MzIvJHtQT1NUR1JFU19EQn0nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL3JlZGlzOjYzNzknCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLS1uby12ZXJib3NlIC0tdHJpZXM9MSAtLXNwaWRlciBodHRwOi8vMTI3LjAuMC4xOjgwODAvYXBpL3N0YXR1cyB8fCBleGl0IDEnCiAgICBkZXBlbmRzX29uOgogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBkYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3JwogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXMtZGF0YTovZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19FTVBUWV9QQVNTV09SRD0ke0FMTE9XX0VNUFRZX1BBU1NXT1JEOi15ZXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgLWggbG9jYWxob3N0IC1wIDYzNzkgcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICBkYjoKICAgIGltYWdlOiAncG9zdGdyZXM6MTQtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncGdfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWluZmlzaWNhbH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLWggbG9jYWxob3N0IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxMAo=",
"compose": "c2VydmljZXM6CiAgYmFja2VuZDoKICAgIGltYWdlOiAnaW5maXNpY2FsL2luZmlzaWNhbDpsYXRlc3QtcG9zdGdyZXMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fQkFDS0VORF84MDgwCiAgICAgIC0gJ1NJVEVfVVJMPSR7U0VSVklDRV9GUUROX0JBQ0tFTkRfODA4MH0nCiAgICAgIC0gJ05PREVfRU5WPSR7Tk9ERV9FTlY6LXByb2R1Y3Rpb259JwogICAgICAtICdFTkNSWVBUSU9OX0tFWT0ke1NFUlZJQ0VfUEFTU1dPUkRfRU5DUllQVElPTktFWX0nCiAgICAgIC0gJ0FVVEhfU0VDUkVUPSR7U0VSVklDRV9SRUFMQkFTRTY0XzY0X0FVVEhTRUNSRVR9JwogICAgICAtICdTTVRQX0hPU1Q9JHtTTVRQX0hPU1R9JwogICAgICAtICdTTVRQX1VTRVJOQU1FPSR7U01UUF9VU0VSTkFNRX0nCiAgICAgIC0gJ1NNVFBfUEFTU1dPUkQ9JHtTTVRQX1BBU1NXT1JEfScKICAgICAgLSAnU01UUF9QT1JUPSR7U01UUF9QT1JUfScKICAgICAgLSAnU01UUF9GUk9NX0FERFJFU1M9JHtTTVRQX0ZST01fQUREUkVTU30nCiAgICAgIC0gJ1NNVFBfRlJPTV9OQU1FPSR7U01UUF9GUk9NX05BTUV9JwogICAgICAtICdJTkZfQVBQX0NPTk5FQ1RJT05fR0lUSFVCX0FQUF9DTElFTlRfSUQ9JHtJTkZfQVBQX0NPTk5FQ1RJT05fR0lUSFVCX0FQUF9DTElFTlRfSUR9JwogICAgICAtICdJTkZfQVBQX0NPTk5FQ1RJT05fR0lUSFVCX0FQUF9DTElFTlRfU0VDUkVUPSR7SU5GX0FQUF9DT05ORUNUSU9OX0dJVEhVQl9BUFBfQ0xJRU5UX1NFQ1JFVH0nCiAgICAgIC0gJ0lORl9BUFBfQ09OTkVDVElPTl9HSVRIVUJfQVBQX1NMVUc9JHtJTkZfQVBQX0NPTk5FQ1RJT05fR0lUSFVCX0FQUF9TTFVHfScKICAgICAgLSAnSU5GX0FQUF9DT05ORUNUSU9OX0dJVEhVQl9BUFBfSUQ9JHtJTkZfQVBQX0NPTk5FQ1RJT05fR0lUSFVCX0FQUF9JRH0nCiAgICAgIC0gJ0lORl9BUFBfQ09OTkVDVElPTl9HSVRIVUJfQVBQX1BSSVZBVEVfS0VZPSR7SU5GX0FQUF9DT05ORUNUSU9OX0dJVEhVQl9BUFBfUFJJVkFURV9LRVl9JwogICAgICAtICdEQl9DT05ORUNUSU9OX1VSST1wb3N0Z3JlczovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QGRiOjU0MzIvJHtQT1NUR1JFU19EQn0nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL3JlZGlzOjYzNzknCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLS1uby12ZXJib3NlIC0tdHJpZXM9MSAtLXNwaWRlciBodHRwOi8vMTI3LjAuMC4xOjgwODAvYXBpL3N0YXR1cyB8fCBleGl0IDEnCiAgICBkZXBlbmRzX29uOgogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICBkYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3JwogICAgdm9sdW1lczoKICAgICAgLSAncmVkaXMtZGF0YTovZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdBTExPV19FTVBUWV9QQVNTV09SRD0ke0FMTE9XX0VNUFRZX1BBU1NXT1JEOi15ZXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdyZWRpcy1jbGkgLWggbG9jYWxob3N0IC1wIDYzNzkgcGluZycKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDVzCiAgICAgIHJldHJpZXM6IDMKICBkYjoKICAgIGltYWdlOiAncG9zdGdyZXM6MTQtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncGdfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU30nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LWluZmlzaWNhbH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLWggbG9jYWxob3N0IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxMAo=",
"tags": [
"security",
"environment",
@@ -1967,7 +1967,7 @@
"minecraft": {
"documentation": "https://github.com/itzg/docker-minecraft-server?utm_source=coolify.io",
"slogan": "Minecraft Server that will automatically download selected version at startup.",
"compose": "c2VydmljZXM6CiAgbWM6CiAgICBpbWFnZTogaXR6Zy9taW5lY3JhZnQtc2VydmVyCiAgICBwb3J0czoKICAgICAgLSAnJHtQT1JUfToyNTU2NScKICAgIGVudmlyb25tZW50OgogICAgICAtIEVVTEE9dHJ1ZQogICAgICAtICdWRVJTSU9OPSR7TUlORUNSQUZUX1ZFUlNJT046LWxhdGVzdH0nCiAgICAgIC0gJ1RZUEU9JHtNSU5FQ1JBRlRfVFlQRTotVkFOSUxMQX0nCiAgICAgIC0gJ1NFUlZFUl9OQU1FPSR7TUlORUNSQUZUX1NFUlZFUl9OQU1FOi1NaW5lY3JhZnQgU2VydmVyfScKICAgICAgLSAnTU9URD0ke01JTkVDUkFGVF9NT1REOi1NaW5lY3JhZnQgU2VydmVyIHBvd2VyZWQgYnkgwqdhQ29vbGlmecKncn0nCiAgICAgIC0gJ0RJRkZJQ1VMVFk9JHtNSU5FQ1JBRlRfRElGRklDVUxUWTotbm9ybWFsfScKICAgICAgLSAnTUFYX1BMQVlFUlM9JHtNSU5FQ1JBRlRfTUFYX1BMQVlFUlM6LTEwfScKICAgICAgLSAnTUFYX1dPUkxEX1NJWkU9JHtNSU5FQ1JBRlRfTUFYX1dPUkxEX1NJWkU6LTEwMDAwfScKICAgICAgLSAnVklFV19ESVNUQU5DRT0ke01JTkVDUkFGVF9WSUVXX0RJU1RBTkNFOi0xMH0nCiAgICAgIC0gJ01BWF9CVUlMRF9IRUlHSFQ9JHtNSU5FQ1JBRlRfTUFYX0JVSUxEX0hFSUdIVDotMjU2fScKICAgICAgLSAnTUFYX1RJQ0tfVElNRT0ke01JTkVDUkFGVF9NQVhfVElDS19USU1FOi02MDAwMH0nCiAgICAgIC0gJ0FMTE9XX05FVEhFUj0ke01JTkVDUkFGVF9BTExPV19ORVRIRVI6LXRydWV9JwogICAgICAtICdBTk5PVU5DRV9QTEFZRVJfQUNISUVWRU1FTlRTPSR7TUlORUNSQUZUX0FOTk9VTkNFX1BMQVlFUl9BQ0hJRVZFTUVOVFM6LXRydWV9JwogICAgICAtICdHRU5FUkFURV9TVFJVQ1RVUkVTPSR7TUlORUNSQUZUX0dFTkVSQVRFX1NUUlVDVFVSRVM6LXRydWV9JwogICAgICAtICdQVlA9JHtNSU5FQ1JBRlRfUFZQOi10cnVlfScKICAgICAgLSAnRk9SQ0VfR0FNRU1PREU9JHtNSU5FQ1JBRlRfRk9SQ0VfR0FNRU1PREU6LWZhbHNlfScKICAgICAgLSAnSEFSRENPUkU9JHtNSU5FQ1JBRlRfSEFSRENPUkU6LWZhbHNlfScKICAgICAgLSAnRU5BQkxFX0NPTU1BTkRfQkxPQ0s9JHtNSU5FQ1JBRlRfRU5BQkxFX0NPTU1BTkRfQkxPQ0s6LWZhbHNlfScKICAgICAgLSAnU1BBV05fQU5JTUFMUz0ke01JTkVDUkFGVF9TUEFXTl9BTklNQUxTOi10cnVlfScKICAgICAgLSAnU1BBV05fTU9OU1RFUlM9JHtNSU5FQ1JBRlRfU1BBV05fTU9OU1RFUlM6LXRydWV9JwogICAgICAtICdTUEFXTl9OUENTPSR7TUlORUNSQUZUX1NQQVdOX05QQ1M6LXRydWV9JwogICAgICAtICdTTk9PUEVSX0VOQUJMRUQ9JHtNSU5FQ1JBRlRfU05PT1BFUl9FTkFCTEVEOi10cnVlfScKICAgICAgLSAnT05MSU5FX01PREU9JHtNSU5FQ1JBRlRfT05MSU5FX01PREU6LXRydWV9JwogICAgICAtICdQTEFZRVJfSURMRV9USU1FT1VUPSR7TUlORUNSQUZUX1BMQVlFUl9JRExFX1RJTUVPVVQ6LTB9JwogICAgICAtICdNRU1PUlk9JHtNSU5FQ1JBRlRfTUVNT1JZOi0xR30nCiAgICAgIC0gJ0VOQUJMRV9BVVRPUEFVU0U9JHtNSU5FQ1JBRlRfRU5BQkxFX0FVVE9QQVVTRTotZmFsc2V9JwogICAgICAtICdSQ09OX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9SQ09OfScKICAgICAgLSAnUE9SVD0ke1BPUlQ6LTI1NTY1fScKICAgIHZvbHVtZXM6CiAgICAgIC0gJy4vbWluZWNyYWZ0LWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gL3Vzci9sb2NhbC9iaW4vbWMtbW9uaXRvcgogICAgICAgIC0gc3RhdHVzCiAgICAgICAgLSAnLS1ob3N0JwogICAgICAgIC0gbG9jYWxob3N0CiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
"compose": "c2VydmljZXM6CiAgbWM6CiAgICBpbWFnZTogaXR6Zy9taW5lY3JhZnQtc2VydmVyCiAgICBwb3J0czoKICAgICAgLSAnJHtQT1JUfToyNTU2NScKICAgIGVudmlyb25tZW50OgogICAgICAtIEVVTEE9dHJ1ZQogICAgICAtICdWRVJTSU9OPSR7TUlORUNSQUZUX1ZFUlNJT046LWxhdGVzdH0nCiAgICAgIC0gJ1RZUEU9JHtNSU5FQ1JBRlRfVFlQRTotVkFOSUxMQX0nCiAgICAgIC0gJ1NFUlZFUl9OQU1FPSR7TUlORUNSQUZUX1NFUlZFUl9OQU1FOi1NaW5lY3JhZnQgU2VydmVyfScKICAgICAgLSAnTU9URD0ke01JTkVDUkFGVF9NT1REOi1NaW5lY3JhZnQgU2VydmVyIHBvd2VyZWQgYnkgwqdhQ29vbGlmecKncn0nCiAgICAgIC0gJ0RJRkZJQ1VMVFk9JHtNSU5FQ1JBRlRfRElGRklDVUxUWTotbm9ybWFsfScKICAgICAgLSAnTUFYX1BMQVlFUlM9JHtNSU5FQ1JBRlRfTUFYX1BMQVlFUlM6LTEwfScKICAgICAgLSAnTUFYX1dPUkxEX1NJWkU9JHtNSU5FQ1JBRlRfTUFYX1dPUkxEX1NJWkU6LTEwMDAwfScKICAgICAgLSAnVklFV19ESVNUQU5DRT0ke01JTkVDUkFGVF9WSUVXX0RJU1RBTkNFOi0xMH0nCiAgICAgIC0gJ01BWF9CVUlMRF9IRUlHSFQ9JHtNSU5FQ1JBRlRfTUFYX0JVSUxEX0hFSUdIVDotMjU2fScKICAgICAgLSAnTUFYX1RJQ0tfVElNRT0ke01JTkVDUkFGVF9NQVhfVElDS19USU1FOi02MDAwMH0nCiAgICAgIC0gJ0FMTE9XX05FVEhFUj0ke01JTkVDUkFGVF9BTExPV19ORVRIRVI6LXRydWV9JwogICAgICAtICdBTk5PVU5DRV9QTEFZRVJfQUNISUVWRU1FTlRTPSR7TUlORUNSQUZUX0FOTk9VTkNFX1BMQVlFUl9BQ0hJRVZFTUVOVFM6LXRydWV9JwogICAgICAtICdHRU5FUkFURV9TVFJVQ1RVUkVTPSR7TUlORUNSQUZUX0dFTkVSQVRFX1NUUlVDVFVSRVM6LXRydWV9JwogICAgICAtICdQVlA9JHtNSU5FQ1JBRlRfUFZQOi10cnVlfScKICAgICAgLSAnTU9ERT0ke01JTkVDUkFGVF9HQU1FX01PREU6LXN1cnZpdmFsfScKICAgICAgLSAnRk9SQ0VfR0FNRU1PREU9JHtNSU5FQ1JBRlRfRk9SQ0VfR0FNRU1PREU6LWZhbHNlfScKICAgICAgLSAnSEFSRENPUkU9JHtNSU5FQ1JBRlRfSEFSRENPUkU6LWZhbHNlfScKICAgICAgLSAnRU5BQkxFX0NPTU1BTkRfQkxPQ0s9JHtNSU5FQ1JBRlRfRU5BQkxFX0NPTU1BTkRfQkxPQ0s6LWZhbHNlfScKICAgICAgLSAnU1BBV05fQU5JTUFMUz0ke01JTkVDUkFGVF9TUEFXTl9BTklNQUxTOi10cnVlfScKICAgICAgLSAnU1BBV05fTU9OU1RFUlM9JHtNSU5FQ1JBRlRfU1BBV05fTU9OU1RFUlM6LXRydWV9JwogICAgICAtICdTUEFXTl9OUENTPSR7TUlORUNSQUZUX1NQQVdOX05QQ1M6LXRydWV9JwogICAgICAtICdTTk9PUEVSX0VOQUJMRUQ9JHtNSU5FQ1JBRlRfU05PT1BFUl9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ09OTElORV9NT0RFPSR7TUlORUNSQUZUX09OTElORV9NT0RFOi10cnVlfScKICAgICAgLSAnUExBWUVSX0lETEVfVElNRU9VVD0ke01JTkVDUkFGVF9QTEFZRVJfSURMRV9USU1FT1VUOi0wfScKICAgICAgLSAnSU5JVF9NRU1PUlk9JHtNSU5FQ1JBRlRfSU5JVF9NRU1PUlk6LTI1Nk19JwogICAgICAtICdNQVhfTUVNT1JZPSR7TUlORUNSQUZUX01BWF9NRU1PUlk6LTFHfScKICAgICAgLSAnR1VJPSR7TUlORUNSQUZUX0dVSTotZmFsc2V9JwogICAgICAtICdFTkFCTEVfQVVUT1BBVVNFPSR7TUlORUNSQUZUX0VOQUJMRV9BVVRPUEFVU0U6LWZhbHNlfScKICAgICAgLSAnUkNPTl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUkNPTn0nCiAgICAgIC0gJ1BPUlQ9JHtQT1JUOi0yNTU2NX0nCiAgICB2b2x1bWVzOgogICAgICAtICcuL21pbmVjcmFmdC1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC91c3IvbG9jYWwvYmluL21jLW1vbml0b3IKICAgICAgICAtIHN0YXR1cwogICAgICAgIC0gJy0taG9zdCcKICAgICAgICAtIGxvY2FsaG9zdAogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
"tags": [
"minecraft"
],
@@ -3233,7 +3233,7 @@
"wakapi": {
"documentation": "https://wakapi.dev/?utm_source=coolify.io",
"slogan": "A minimalist, self-hosted WakaTime-compatible backend for coding statistics",
"compose": "c2VydmljZXM6CiAgd2FrYXBpOgogICAgaW1hZ2U6ICdnaGNyLmlvL211ZXR5L3dha2FwaTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fV0FLQVBJXzMwMDAKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotRXVyb3BlL0Jlcmxpbn0nCiAgICAgIC0gJ1dBS0FQSV9TRVJWRVJfTElTVEVOX0lQVjY9Ii0iJwogICAgICAtICdXQUtBUElfRU5WPSR7V0FLQVBJX0VOVklST05NRU5UOi1wcm9kdWN0aW9ufScKICAgICAgLSAnV0FLQVBJX1NFQ1VSSVRZX1BBU1NXT1JEX1NBTFQ9JHtTRVJWSUNFX0JBU0U2NF82NF9QQVNTV09SRFNBTFR9JwogICAgICAtICdXQUtBUElfU0VDVVJJVFlfRVhQT1NFX01FVFJJQ1M9JHtXQUtBUElfU0VDVVJJVFlfRVhQT1NFX01FVFJJQ1M6LWZhbHNlfScKICAgICAgLSBXQUtBUElfREJfVFlQRT1wb3N0Z3JlcwogICAgICAtICdXQUtBUElfREJfTkFNRT0ke1dBS0FQSV9EQl9OQU1FOi13YWthcGl9JwogICAgICAtICdXQUtBUElfREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9EQVRBQkFTRX0nCiAgICAgIC0gJ1dBS0FQSV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfREFUQUJBU0V9JwogICAgICAtICdXQUtBUElfREJfSE9TVD0ke1dBS0FQSV9EQl9IT1NUOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1dBS0FQSV9EQl9QT1JUPSR7V0FLQVBJX0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdXQUtBUElfTUFJTF9FTkFCTEVEPSR7V0FLQVBJX01BSUxfRU5BQkxFRDotZmFsc2V9JwogICAgICAtIFdBS0FQSV9NQUlMX1BST1ZJREVSPXNtdHAKICAgICAgLSAnV0FLQVBJX01BSUxfU0VOREVSPSR7V0FLQVBJX01BSUxfU0VOREVSfScKICAgICAgLSAnV0FLQVBJX01BSUxfU01UUF9IT1NUPSR7V0FLQVBJX01BSUxfU01UUF9IT1NUfScKICAgICAgLSAnV0FLQVBJX01BSUxfU01UUF9QT1JUPSR7V0FLQVBJX01BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICAtICdXQUtBUElfTUFJTF9TTVRQX1VTRVJOQU1FPSR7V0FLQVBJX01BSUxfU01UUF9VU0VSTkFNRX0nCiAgICAgIC0gJ1dBS0FQSV9NQUlMX1NNVFBfUEFTU1dPUkQ9JHtXQUtBUElfTUFJTF9TTVRQX1BBU1NXT1JEfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3dha2FwaS1kYXRhOi9kYXRhJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6MzAwMC8nCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnd2FrYXBpLXBvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfREFUQUJBU0V9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfREFUQUJBU0V9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1dBS0FQSV9EQl9OQU1FOi13YWthcGl9JwogICAgICAtICdQT1NUR1JFU19QT1JUPSR7V0FLQVBJX0RCX1BPUlQ6LTU0MzJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=",
"compose": "c2VydmljZXM6CiAgd2FrYXBpOgogICAgaW1hZ2U6ICdnaGNyLmlvL211ZXR5L3dha2FwaTpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fV0FLQVBJXzMwMDAKICAgICAgLSAnVFo9JHtUSU1FWk9ORTotRXVyb3BlL0Jlcmxpbn0nCiAgICAgIC0gJ1dBS0FQSV9TRVJWRVJfTElTVEVOX0lQVjY9Ii0iJwogICAgICAtICdXQUtBUElfRU5WPSR7V0FLQVBJX0VOVklST05NRU5UOi1wcm9kdWN0aW9ufScKICAgICAgLSAnV0FLQVBJX1NFQ1VSSVRZX1BBU1NXT1JEX1NBTFQ9JHtTRVJWSUNFX0JBU0U2NF82NF9QQVNTV09SRFNBTFR9JwogICAgICAtICdXQUtBUElfU0VDVVJJVFlfRVhQT1NFX01FVFJJQ1M9JHtXQUtBUElfU0VDVVJJVFlfRVhQT1NFX01FVFJJQ1M6LWZhbHNlfScKICAgICAgLSBXQUtBUElfREJfVFlQRT1wb3N0Z3JlcwogICAgICAtICdXQUtBUElfREJfTkFNRT0ke1dBS0FQSV9EQl9OQU1FOi13YWthcGl9JwogICAgICAtICdXQUtBUElfREJfVVNFUj0ke1NFUlZJQ0VfVVNFUl9EQVRBQkFTRX0nCiAgICAgIC0gJ1dBS0FQSV9EQl9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfREFUQUJBU0V9JwogICAgICAtICdXQUtBUElfREJfSE9TVD0ke1dBS0FQSV9EQl9IT1NUOi1wb3N0Z3Jlc30nCiAgICAgIC0gJ1dBS0FQSV9EQl9QT1JUPSR7V0FLQVBJX0RCX1BPUlQ6LTU0MzJ9JwogICAgICAtICdXQUtBUElfTUFJTF9FTkFCTEVEPSR7V0FLQVBJX01BSUxfRU5BQkxFRDotZmFsc2V9JwogICAgICAtIFdBS0FQSV9NQUlMX1BST1ZJREVSPXNtdHAKICAgICAgLSAnV0FLQVBJX01BSUxfU0VOREVSPSR7V0FLQVBJX01BSUxfU0VOREVSfScKICAgICAgLSAnV0FLQVBJX01BSUxfU01UUF9IT1NUPSR7V0FLQVBJX01BSUxfU01UUF9IT1NUfScKICAgICAgLSAnV0FLQVBJX01BSUxfU01UUF9QT1JUPSR7V0FLQVBJX01BSUxfU01UUF9QT1JUOi01ODd9JwogICAgICAtICdXQUtBUElfTUFJTF9TTVRQX1VTRVI9JHtXQUtBUElfTUFJTF9TTVRQX1VTRVJOQU1FfScKICAgICAgLSAnV0FLQVBJX01BSUxfU01UUF9QQVNTPSR7V0FLQVBJX01BSUxfU01UUF9QQVNTV09SRH0nCiAgICAgIC0gJ1dBS0FQSV9NQUlMX1NNVFBfVExTPSR7V0FLQVBJX01BSUxfU01UUF9UTFM6LXRydWV9JwogICAgICAtICdXQUtBUElfQUxMT1dfU0lHTlVQPSR7V0FLQVBJX0FMTE9XX1NJR05VUDotdHJ1ZX0nCiAgICAgIC0gJ1dBS0FQSV9MRUFERVJCT0FSRF9FTkFCTEVEPSR7V0FLQVBJX0xFQURFUkJPQVJEX0VOQUJMRUQ6LXRydWV9JwogICAgICAtICdXQUtBUElfRElTQUJMRV9GUk9OVFBBR0U9JHtXQUtBUElfRElTQUJMRV9GUk9OVFBBR0U6LXRydWV9JwogICAgICAtICdXQUtBUElfUFVCTElDX1VSTD0ke1dBS0FQSV9QVUJMSUNfVVJMfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3dha2FwaS1kYXRhOi9kYXRhJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly8xMjcuMC4wLjE6MzAwMC8nCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3JlczoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnd2FrYXBpLXBvc3RncmVzLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfREFUQUJBU0V9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfREFUQUJBU0V9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1dBS0FQSV9EQl9OQU1FOi13YWthcGl9JwogICAgICAtICdQT1NUR1JFU19QT1JUPSR7V0FLQVBJX0RCX1BPUlQ6LTU0MzJ9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=",
"tags": [
"productivity",
"self-hosted",

View File

@@ -1,13 +1,13 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.401"
},
"nightly": {
"version": "4.0.0-beta.402"
},
"nightly": {
"version": "4.0.0-beta.403"
},
"helper": {
"version": "1.0.7"
"version": "1.0.8"
},
"realtime": {
"version": "1.0.6"