Merge branch 'next' into next

This commit is contained in:
Leonardo Cabeza authored and GitHub committed 2024-10-06 14:13:10 -05:00
commit 846e45134e
181 files changed
+3632 -1219

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
name: Coolify Realtime Development (v4)
on:
push:
branches: [ "next" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
- docker/coolify-realtime/terminal-server.js
- docker/coolify-realtime/package.json
- docker/coolify-realtime/soketi-entrypoint.sh
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify-realtime"
jobs:
amd64:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
labels: |
coolify.managed=true
aarch64:
runs-on: [ self-hosted, arm64 ]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Build image and push to registry
uses: docker/build-push-action@v5
with:
no-cache: true
context: .
file: docker/coolify-realtime/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64
labels: |
coolify.managed=true
merge-manifest:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
needs: [ amd64, aarch64 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get Version
id: version
run: |
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT
- name: Create & publish manifest
run: |
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }}
+1 -1
View File
@@ -2,7 +2,7 @@ name: Coolify Realtime (v4)
on:
push:
branches: [ "main", "next" ]
branches: [ "main" ]
paths:
- .github/workflows/coolify-realtime.yml
- docker/coolify-realtime/Dockerfile
-3
View File
@@ -46,9 +46,6 @@ class StartDragonfly
'networks' => [
$this->database->destination->network,
],
'ulimits' => [
'memlock' => '-1',
],
'labels' => [
'coolify.managed' => 'true',
],
+2 -3
View File
@@ -2,7 +2,6 @@
namespace App\Actions\Fortify;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
@@ -20,7 +19,7 @@ class CreateNewUser implements CreatesNewUsers
*/
public function create(array $input): User
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403);
}
@@ -48,7 +47,7 @@ class CreateNewUser implements CreatesNewUsers
$team = $user->teams()->first();
// Disable registration after first user is created
$settings = InstanceSettings::get();
$settings = instanceSettings();
$settings->is_registration_enabled = false;
$settings->save();
} else {
+1 -2
View File
@@ -2,7 +2,6 @@
namespace App\Actions\License;
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -13,7 +12,7 @@ class CheckResaleLicense
public function handle()
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (isDev()) {
$settings->update([
'is_resale_license_active' => true,
+1 -1
View File
@@ -22,7 +22,7 @@ class CheckConfiguration
];
$proxy_configuration = instant_remote_process($payload, $server, false);
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value();
}
if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception('Could not generate proxy configuration');
+37 -14
View File
@@ -2,14 +2,17 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
// It should return if the proxy should be started (true) or not (false)
public function handle(Server $server, $fromUI = false): bool
{
if (! $server->isFunctional()) {
return false;
@@ -62,22 +65,42 @@ class CheckProxy
$ip = 'host.docker.internal';
}
$connection80 = @fsockopen($ip, '80');
$connection443 = @fsockopen($ip, '443');
$port80 = is_resource($connection80) && fclose($connection80);
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
$portsToCheck = ['80', '443'];
try {
if ($server->proxyType() !== ProxyTypes::NONE->value) {
$proxyCompose = CheckConfiguration::run($server);
if (isset($proxyCompose)) {
$yaml = Yaml::parse($proxyCompose);
$portsToCheck = [];
if ($server->proxyType() === ProxyTypes::TRAEFIK->value) {
$ports = data_get($yaml, 'services.traefik.ports');
} elseif ($server->proxyType() === ProxyTypes::CADDY->value) {
$ports = data_get($yaml, 'services.caddy.ports');
}
if (isset($ports)) {
foreach ($ports as $port) {
$portsToCheck[] = str($port)->before(':')->value();
}
}
}
} else {
return false;
$portsToCheck = [];
}
} catch (\Exception $e) {
ray($e->getMessage());
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
if (count($portsToCheck) === 0) {
return false;
}
foreach ($portsToCheck as $port) {
$connection = @fsockopen($ip, $port);
if (is_resource($connection) && fclose($connection)) {
if ($fromUI) {
throw new \Exception("Port $port is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
}
+1 -1
View File
@@ -26,7 +26,7 @@ class StartProxy
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value();
$server->save();
if ($server->isSwarm()) {
$commands = $commands->merge([
+1 -2
View File
@@ -2,7 +2,6 @@
namespace App\Actions\Server;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -12,7 +11,7 @@ class CleanupDocker
public function handle(Server $server)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImageVersion = data_get($settings, 'helper_version');
$helperImage = config('coolify.helper_image');
$helperImageWithVersion = "$helperImage:$helperImageVersion";
+1 -2
View File
@@ -3,7 +3,6 @@
namespace App\Actions\Server;
use App\Jobs\PullHelperImageJob;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
@@ -20,7 +19,7 @@ class UpdateCoolify
public function handle($manual_update = false)
{
try {
$settings = InstanceSettings::get();
$settings = instanceSettings();
$this->server = Server::find(0);
if (! $this->server) {
return;
@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;
class CheckApplicationDeploymentQueue extends Command
{
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';
protected $description = 'Check application deployment queue.';
public function handle()
{
$seconds = $this->option('seconds');
$deployments = ApplicationDeploymentQueue::whereIn('status', [
ApplicationDeploymentStatus::IN_PROGRESS,
ApplicationDeploymentStatus::QUEUED,
])->where('created_at', '<=', now()->subSeconds($seconds))->get();
if ($deployments->isEmpty()) {
$this->info('No deployments found in the last '.$seconds.' seconds.');
return;
}
$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');
foreach ($deployments as $deployment) {
if ($this->option('force')) {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
$this->cancelDeployment($deployment);
} else {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
if ($this->confirm('Do you want to cancel this deployment?', true)) {
$this->cancelDeployment($deployment);
}
}
}
}
private function cancelDeployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
if ($deployment->server?->isFunctional()) {
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
}
}
}
@@ -7,9 +7,9 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $signature = 'cleanup:deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
protected $description = 'Cleanup application deployment queue.';
public function handle()
{
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
@@ -47,6 +48,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
try {
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
if (is_null($applicationDeploymentQueue->application)) {
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
$applicationDeploymentQueue->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
}
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
+7
View File
@@ -48,6 +48,13 @@ class Dev extends Command
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
}
// Generate STORAGE link if not exists
if (! file_exists(public_path('storage'))) {
echo "Generating STORAGE link.\n";
Artisan::call('storage:link');
}
// Seed database if it's empty
$settings = InstanceSettings::find(0);
if (! $settings) {
+2 -3
View File
@@ -7,7 +7,6 @@ use App\Enums\ActivityTypes;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
@@ -69,7 +68,7 @@ class Init extends Command
} catch (\Throwable $e) {
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
}
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! is_null(env('AUTOUPDATE', null))) {
if (env('AUTOUPDATE') == true) {
$settings->update(['is_auto_update_enabled' => true]);
@@ -196,7 +195,7 @@ class Init extends Command
{
$id = config('app.id');
$version = config('version');
$settings = InstanceSettings::get();
$settings = instanceSettings();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
+5 -4
View File
@@ -12,8 +12,8 @@ use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerCheckJob;
use App\Jobs\ServerStorageCheckJob;
use App\Jobs\UpdateCoolifyJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
@@ -66,7 +66,7 @@ class Kernel extends ConsoleKernel
private function pull_images($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if ($server->isSentinelEnabled()) {
@@ -88,7 +88,7 @@ class Kernel extends ConsoleKernel
private function schedule_updates($schedule)
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$updateCheckFrequency = $settings->update_check_frequency;
$schedule->job(new CheckForUpdatesJob)
@@ -116,6 +116,7 @@ class Kernel extends ConsoleKernel
}
foreach ($servers as $server) {
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
$serverTimezone = $server->settings->server_timezone;
if ($server->settings->force_docker_cleanup) {
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
+1 -1
View File
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
if ($e instanceof RuntimeException) {
return;
}
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
if ($this->settings->do_not_track) {
return;
}
+4 -2
View File
@@ -94,7 +94,9 @@ class SshMultiplexingHelper
$muxPersistTime = config('constants.ssh.mux_persist_time');
$scp_command = "timeout $timeout scp ";
if ($server->isIpv6()) {
$scp_command .= '-6 ';
}
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
@@ -136,8 +138,8 @@ class SshMultiplexingHelper
$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
$delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);
$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
@@ -177,6 +177,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -279,6 +280,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -381,6 +383,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -468,6 +471,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -552,6 +556,7 @@ class ApplicationsController extends Controller
'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -602,6 +607,7 @@ class ApplicationsController extends Controller
'name' => ['type' => 'string', 'description' => 'The application name.'],
'description' => ['type' => 'string', 'description' => 'The application description.'],
'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -627,7 +633,7 @@ class ApplicationsController extends Controller
private function create_application(Request $request, $type)
{
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
@@ -665,6 +671,7 @@ class ApplicationsController extends Controller
$fqdn = $request->domains;
$instantDeploy = $request->instant_deploy;
$githubAppUuid = $request->github_app_uuid;
$useBuildServer = $request->use_build_server;
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
@@ -737,6 +744,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -833,6 +844,10 @@ class ApplicationsController extends Controller
$application->environment_id = $environment->id;
$application->source_type = $githubApp->getMorphClass();
$application->source_id = $githubApp->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -925,6 +940,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->save();
$application->refresh();
if (! $application->settings->is_container_label_readonly_enabled) {
@@ -1004,6 +1023,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1062,6 +1085,10 @@ class ApplicationsController extends Controller
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
if (isset($useBuildServer)) {
$application->settings->is_build_server_enabled = $useBuildServer;
$application->settings->save();
}
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
@@ -1259,16 +1286,10 @@ class ApplicationsController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1316,10 +1337,14 @@ class ApplicationsController extends Controller
'message' => 'Application not found',
], 404);
}
DeleteResourceJob::dispatch(
resource: $application,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Application deletion request queued.',
@@ -1404,6 +1429,7 @@ class ApplicationsController extends Controller
'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
'use_build_server' => ['type' => 'boolean', 'nullable' => true, 'description' => 'Use build server.'],
],
)),
]),
@@ -1460,7 +1486,7 @@ class ApplicationsController extends Controller
], 404);
}
$server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
@@ -1538,6 +1564,13 @@ class ApplicationsController extends Controller
}
$instantDeploy = $request->instant_deploy;
$use_build_server = $request->use_build_server;
if (isset($use_build_server)) {
$application->settings->is_build_server_enabled = $use_build_server;
$application->settings->save();
}
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all();
@@ -1541,16 +1541,10 @@ class DatabasesController extends Controller
format: 'uuid',
)
),
new OA\Parameter(
name: 'cleanup',
in: 'query',
description: 'Delete configurations and volumes.',
required: false,
schema: new OA\Schema(
type: 'boolean',
default: true,
)
),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -1595,10 +1589,14 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
DeleteResourceJob::dispatch(
resource: $database,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Database deletion request queued.',
+2 -2
View File
@@ -86,7 +86,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
@@ -138,7 +138,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);
@@ -308,7 +308,7 @@ class ServersController extends Controller
$projects = Project::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;
+34 -12
View File
@@ -432,6 +432,10 @@ class ServicesController extends Controller
tags: ['Services'],
parameters: [
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
new OA\Parameter(name: 'delete_configurations', in: 'query', required: false, description: 'Delete configurations.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_volumes', in: 'query', required: false, description: 'Delete volumes.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'docker_cleanup', in: 'query', required: false, description: 'Run docker cleanup.', schema: new OA\Schema(type: 'boolean', default: true)),
new OA\Parameter(name: 'delete_connected_networks', in: 'query', required: false, description: 'Delete connected networks.', schema: new OA\Schema(type: 'boolean', default: true)),
],
responses: [
new OA\Response(
@@ -476,7 +480,14 @@ class ServicesController extends Controller
if (! $service) {
return response()->json(['message' => 'Service not found.'], 404);
}
DeleteResourceJob::dispatch($service);
DeleteResourceJob::dispatch(
resource: $service,
deleteConfigurations: $request->query->get('delete_configurations', true),
deleteVolumes: $request->query->get('delete_volumes', true),
dockerCleanup: $request->query->get('docker_cleanup', true),
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
);
return response()->json([
'message' => 'Service deletion request queued.',
@@ -516,7 +527,8 @@ class ServicesController extends Controller
items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -619,7 +631,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -738,7 +751,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -853,7 +867,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -953,7 +968,8 @@ class ServicesController extends Controller
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1025,9 +1041,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1101,9 +1119,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
@@ -1177,9 +1197,11 @@ class ServicesController extends Controller
type: 'object',
properties: [
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
])
]
)
),
]),
]
),
new OA\Response(
response: 401,
ref: '#/components/responses/401',
+1 -2
View File
@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -22,7 +21,7 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (! $user) {
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
abort(403, 'Registration is disabled');
}
+1 -1
View File
@@ -14,7 +14,7 @@ class ApiAllowed
if (isCloud()) {
return $next($request);
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}
+3 -4
View File
@@ -12,7 +12,6 @@ use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
@@ -962,7 +961,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
@@ -970,7 +969,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
if ($this->application->compose_parsing_version === '3') {
if ((int) $this->application->compose_parsing_version >= 3) {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}");
@@ -1334,7 +1333,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function prepare_builder_image()
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$helperImage = "{$helperImage}:{$settings->helper_version}";
// Get user home directory
+1 -2
View File
@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -22,7 +21,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
$settings = instanceSettings();
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
+65 -58
View File
@@ -2,9 +2,7 @@
namespace App\Jobs;
use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\InstanceSettings;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
@@ -25,7 +23,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
@@ -64,32 +61,32 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
}
public function handle(): void
{
try {
// Check if team is exists
if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']);
StopDatabase::run($this->database);
$this->database->delete();
$this->team = Team::find($this->backup->team_id);
if (! $this->team) {
$this->backup->delete();
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
if (is_null($this->server)) {
throw new \Exception('Server not found?!');
}
if (is_null($this->database)) {
throw new \Exception('Database not found?!');
}
BackupCreated::dispatch($this->team->id);
@@ -239,7 +236,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
@@ -252,6 +248,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
try {
if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/pg-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -280,6 +279,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mongodb($database);
} elseif (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mysql-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -289,6 +291,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->backup_standalone_mysql($database);
} elseif (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
if ($this->backup->dump_all) {
$this->backup_file = '/mariadb-dump-all-'.Carbon::now()->timestamp.'.gz';
}
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -327,7 +332,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
if ($this->team) {
BackupCreated::dispatch($this->team->id);
}
}
}
@@ -386,7 +393,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
if ($this->backup->dump_all) {
$backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location";
} else {
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
}
$commands[] = $backupCommand;
ray($commands);
@@ -407,8 +418,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
ray($commands);
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress | gzip > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
}
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') {
@@ -426,7 +440,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
try {
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
if ($this->backup->dump_all) {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} --all-databases --single-transaction --quick --lock-tables=false --compress > $this->backup_location";
} else {
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
}
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
@@ -468,34 +486,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
}
}
// private function upload_to_s3(): void
// {
// try {
// if (is_null($this->s3)) {
// return;
// }
// $key = $this->s3->key;
// $secret = $this->s3->secret;
// // $region = $this->s3->region;
// $bucket = $this->s3->bucket;
// $endpoint = $this->s3->endpoint;
// $this->s3->testConnection(shouldSave: true);
// $configName = new Cuid2;
// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
// instant_remote_process($commands, $this->server);
// $this->add_to_backup_output('Uploaded to S3.');
// } catch (\Throwable $e) {
// $this->add_to_backup_output($e->getMessage());
// throw $e;
// } finally {
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
// instant_remote_process($removeConfigCommands, $this->server, false);
// }
// }
private function upload_to_s3(): void
{
try {
@@ -517,10 +507,27 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$this->ensureHelperImageAvailable();
$fullImageName = $this->getFullImageName();
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
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;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
}
} else {
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
}
if ($this->s3->isHetzner()) {
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
} else {
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
}
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);
$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
@@ -562,7 +569,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function getFullImageName(): string
{
$settings = InstanceSettings::get();
$settings = instanceSettings();
$helperImage = config('coolify.helper_image');
$latestVersion = $settings->helper_version;
+4 -4
View File
@@ -31,10 +31,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
public bool $deleteConfigurations,
public bool $deleteVolumes,
public bool $dockerCleanup,
public bool $deleteConnectedNetworks
public bool $deleteConfigurations = true,
public bool $deleteVolumes = true,
public bool $dockerCleanup = true,
public bool $deleteConnectedNetworks = true
) {}
public function handle()
+1 -2
View File
@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -26,7 +25,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
$settings = InstanceSettings::get();
$settings = instanceSettings();
$latest_version = data_get($versions, 'coolify.helper.version');
$current_version = $settings->helper_version;
if (version_compare($latest_version, $current_version, '>')) {
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 60;
public $containers;
public $applications;
public $databases;
public $services;
public $previews;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server) {}
public function handle()
{
try {
if (! $this->server->isFunctional()) {
ray('Server is not ready.');
return 'Server is not ready.';
}
$team = $this->server->team;
$percentage = $this->server->storageCheck();
if ($percentage > 1) {
ray('Server storage is at '.$percentage.'%');
}
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}
+1 -2
View File
@@ -3,7 +3,6 @@
namespace App\Jobs;
use App\Actions\Server\UpdateCoolify;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -23,7 +22,7 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
{
try {
CheckForUpdatesJob::dispatchSync();
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->new_version_available) {
Log::info('No new version available. Skipping update.');
+1 -1
View File
@@ -30,7 +30,7 @@ class Dashboard extends Component
public function cleanup_queue()
{
Artisan::queue('cleanup:application-deployment-queue', [
Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}
+2 -1
View File
@@ -47,7 +47,7 @@ class Help extends Component
]
);
$mail->subject("[HELP]: {$this->subject}");
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
$url = 'https://app.coolify.io/api/feedback';
@@ -61,6 +61,7 @@ class Help extends Component
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
}
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
$this->reset('description', 'subject');
} catch (\Throwable $e) {
return handleError($e, $this);
}
+1 -1
View File
@@ -172,7 +172,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->smtp_enabled) {
$team = currentTeam();
$team->update([
@@ -31,6 +31,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'required|boolean',
'backup.s3_storage_id' => 'nullable|integer',
'backup.databases_to_backup' => 'nullable',
'backup.dump_all' => 'required|boolean',
];
protected $validationAttributes = [
@@ -40,6 +41,7 @@ class BackupEdit extends Component
'backup.save_s3' => 'Save to S3',
'backup.s3_storage_id' => 'S3 Storage',
'backup.databases_to_backup' => 'Databases to Backup',
'backup.dump_all' => 'Backup All Databases',
];
protected $messages = [
@@ -26,7 +26,7 @@ class ScheduledBackups extends Component
public function mount(): void
{
if ($this->selectedBackupId) {
$this->setSelectedBackup($this->selectedBackupId);
$this->setSelectedBackup($this->selectedBackupId, true);
}
$this->parameters = get_route_parameters();
if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') {
@@ -37,10 +37,13 @@ class ScheduledBackups extends Component
$this->s3s = currentTeam()->s3s;
}
public function setSelectedBackup($backupId)
public function setSelectedBackup($backupId, $force = false)
{
if ($this->selectedBackupId === $backupId && ! $force) {
return;
}
$this->selectedBackupId = $backupId;
$this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId);
$this->selectedBackup = $this->database->scheduledBackups->find($backupId);
if (is_null($this->selectedBackup)) {
$this->selectedBackupId = null;
}
+115
View File
@@ -90,6 +90,121 @@ class Select extends Component
// }
// }
public function loadServices2()
{
$services = get_service_templates(true);
$services = collect($services)->map(function ($service, $key) {
return array_merge($service, [
'name' => str($key)->headline(),
'logo' => asset($service['logo']),
]);
})->all();
$gitBasedApplications = [
[
'id' => 'public',
'name' => 'Public Repository',
'description' => 'You can deploy any kind of public repositories from the supported git providers.',
'logo' => asset('svgs/git.svg'),
],
[
'id' => 'private-gh-app',
'name' => 'Private Repository (with GitHub App)',
'description' => 'You can deploy public & private repositories through your GitHub Apps.',
'logo' => asset('svgs/github.svg'),
],
[
'id' => 'private-deploy-key',
'name' => 'Private Repository (with Deploy Key)',
'description' => 'You can deploy private repositories with a deploy key.',
'logo' => asset('svgs/git.svg'),
],
];
$dockerBasedApplications = [
[
'id' => 'dockerfile',
'name' => 'Dockerfile',
'description' => 'You can deploy a simple Dockerfile, without Git.',
'logo' => asset('svgs/docker.svg'),
],
[
'id' => 'docker-compose-empty',
'name' => 'Docker Compose Empty',
'description' => 'You can deploy complex application easily with Docker Compose, without Git.',
'logo' => asset('svgs/docker.svg'),
],
[
'id' => 'docker-image',
'name' => 'Docker Image',
'description' => 'You can deploy an existing Docker Image from any Registry, without Git.',
'logo' => asset('svgs/docker.svg'),
],
];
$databases = [
[
'id' => 'postgresql',
'name' => 'PostgreSQL',
'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.',
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#currentColor" d="M85.988 76.075c.632-5.262.443-6.034 4.362-5.182l.995.088c3.014.137 6.957-.485 9.272-1.561 4.986-2.313 7.942-6.177 3.026-5.162-11.215 2.313-11.986-1.483-11.986-1.483C103.5 45.204 108.451 22.9 104.178 17.44 92.524 2.548 72.35 9.59 72.012 9.773l-.108.021c-2.216-.461-4.695-.735-7.481-.78-5.075-.083-8.926 1.331-11.847 3.546 0 0-35.989-14.827-34.315 18.646.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.444-9.531 8.444-9.531 2.061 1.369 4.528 2.067 7.116 1.816l.2-.17c-.062.641-.035 1.268.081 2.01-3.027 3.383-2.137 3.977-8.189 5.222-6.122 1.262-2.525 3.508-.178 4.095 2.848.713 9.433 1.722 13.884-4.509l-.177.711c1.188.95 1.107 6.827 1.275 11.026.168 4.199.45 8.117 1.306 10.429.856 2.31 1.866 8.261 9.819 6.557 6.646-1.426 11.727-3.476 12.19-22.545"/><path fill="#currentColor" d="M71.208 102.77c-3.518 0-5.808-1.36-7.2-2.674-2.1-1.981-2.933-4.534-3.43-6.059l-.215-.637c-1.002-2.705-1.341-6.599-1.542-11.613a199.25 199.25 0 01-.075-2.352c-.017-.601-.038-1.355-.068-2.146a15.157 15.157 0 01-3.997 1.264c-2.48.424-5.146.286-7.926-.409-1.961-.49-3.999-1.506-5.16-3.076-3.385 2.965-6.614 2.562-8.373 1.976-3.103-1.035-5.88-3.942-8.491-8.89-1.859-3.523-3.658-8.115-5.347-13.646-2.94-9.633-4.808-19.779-4.974-23.109-.522-10.427 2.284-17.883 8.34-22.16 9.555-6.749 24.03-2.781 29.307-.979 3.545-2.137 7.716-3.178 12.43-3.102 2.532.041 4.942.264 7.181.662 2.335-.734 6.949-1.788 12.23-1.723 9.73.116 17.793 3.908 23.316 10.966 3.941 5.036 1.993 15.61.48 21.466-2.127 8.235-5.856 16.996-10.436 24.622 1.244.009 3.045-.141 5.607-.669 5.054-1.044 6.531 1.666 6.932 2.879 1.607 4.867-5.378 8.544-7.557 9.555-2.792 1.297-7.343 2.086-11.071 1.915l-.163-.011-.979-.086-.097.816-.093.799c-.25 9.664-1.631 15.784-4.472 19.829-2.977 4.239-7.116 5.428-10.761 6.209a16.146 16.146 0 01-3.396.383zm-7.402-35.174c2.271 1.817 2.47 5.236 2.647 11.626.022.797.043 1.552.071 2.257.086 2.134.287 7.132 1.069 9.244.111.298.21.602.314.922.872 2.672 1.31 4.011 5.081 3.203 3.167-.678 4.794-1.287 6.068-3.101 1.852-2.638 2.888-7.941 3.078-15.767l3.852.094-3.826-.459.112-.955c.367-3.148.631-5.424 2.736-6.928 1.688-1.207 3.613-1.09 5.146-.814-1.684-1.271-2.15-2.765-2.274-3.377l-.321-1.582.902-1.34c5.2-7.716 9.489-17.199 11.767-26.018 2.34-9.062 1.626-13.875.913-14.785-9.446-12.071-25.829-7.088-27.539-6.521l-.29.156-1.45.271-.743-.154c-2.047-.425-4.321-.66-6.76-.7-3.831-.064-6.921.841-9.455 2.764l-1.758 1.333-2.041-.841c-4.358-1.782-17.162-5.365-23.918-.58-3.75 2.656-5.458 7.861-5.078 15.47.125 2.512 1.833 12.021 4.647 21.245 3.891 12.746 7.427 16.979 8.903 17.472.257.087.926-.433 1.591-1.231 4.326-5.203 8.44-9.54 8.613-9.723l2.231-2.347 2.697 1.792c1.087.723 2.286 1.132 3.518 1.209l6.433-5.486-.932 9.51c-.021.214-.031.504.053 1.044l.28 1.803-1.213 1.358-.14.157 3.534 1.632 1.482-1.853z"/><path fill="#336791" d="M103.646 64.258c-11.216 2.313-11.987-1.484-11.987-1.484 11.842-17.571 16.792-39.876 12.52-45.335C92.524 2.547 72.35 9.59 72.013 9.773l-.109.019c-2.216-.459-4.695-.733-7.482-.778-5.075-.083-8.925 1.33-11.846 3.545 0 0-35.99-14.826-34.316 18.647.356 7.121 10.207 53.882 21.956 39.758 4.294-5.164 8.443-9.531 8.443-9.531 2.061 1.369 4.528 2.067 7.115 1.816l.201-.17c-.062.641-.034 1.268.08 2.01-3.026 3.383-2.138 3.977-8.188 5.222-6.123 1.262-2.526 3.508-.177 4.095 2.847.713 9.433 1.722 13.883-4.509l-.178.711c1.186.95 2.019 6.179 1.879 10.919s-.233 7.994.702 10.536c.935 2.541 1.866 8.261 9.82 6.557 6.646-1.425 10.09-5.116 10.57-11.272.34-4.377 1.109-3.73 1.158-7.644l.618-1.853c.711-5.934.113-7.848 4.208-6.957l.995.087c3.014.138 6.958-.485 9.273-1.561 4.986-2.314 7.943-6.177 3.028-5.162z"/><path fill="#fff" d="M71.61 100.394c-6.631.001-8.731-5.25-9.591-7.397-1.257-3.146-1.529-15.358-1.249-25.373a1.286 1.286 0 012.57.072c-.323 11.551.136 22.018 1.066 24.346 1.453 3.632 3.656 6.809 9.887 5.475 5.915-1.269 8.13-3.512 9.116-9.23.758-4.389 2.254-16.874 2.438-19.338a1.285 1.285 0 012.563.191c-.192 2.564-1.682 15.026-2.469 19.584-1.165 6.755-4.176 9.819-11.11 11.306a15.462 15.462 0 01-3.221.364zM35.659 74.749a5.343 5.343 0 01-1.704-.281c-4.307-1.437-8.409-8.451-12.193-20.849-2.88-9.438-4.705-19.288-4.865-22.489-.475-9.49 1.97-16.205 7.265-19.957 10.476-7.423 28.1-.354 28.845-.05a1.285 1.285 0 01-.972 2.379v.001c-.17-.07-17.07-6.84-26.392-.229-4.528 3.211-6.607 9.175-6.18 17.729.135 2.696 1.84 12.311 4.757 21.867 3.378 11.067 7.223 18.052 10.548 19.16.521.175 2.109.704 4.381-2.026 4.272-5.14 8.197-9.242 8.236-9.283a1.286 1.286 0 011.856 1.778c-.039.04-3.904 4.081-8.116 9.148-1.995 2.398-3.908 3.102-5.466 3.102zm55.92-10.829a1.284 1.284 0 01-1.065-2.004c11.971-17.764 16.173-39.227 12.574-43.825-4.53-5.788-10.927-8.812-19.012-8.985-5.987-.13-10.746 1.399-11.523 1.666l-.195.079c-.782.246-1.382-.1 Line truncated
',
],
[
'id' => 'mysql',
'name' => 'MySQL',
'description' => 'MySQL is an open-source relational database known for its simplicity and performance.',
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path fill="currentColor" d="M0 91.313h4.242V74.566l6.566 14.598c.773 1.77 1.832 2.391 3.914 2.391s3.098-.621 3.871-2.391l6.566-14.598v16.746h4.242V74.594c0-1.633-.652-2.422-2-2.828-3.223-1.004-5.383-.137-6.363 2.039l-6.441 14.41-6.238-14.41c-.937-2.176-3.14-3.043-6.359-2.039-1.348.406-2 1.195-2 2.828zM32.93 77.68h4.238v9.227c-.039.5.16 1.676 2.484 1.715h9.223V77.633h4.25c.02 0-.008 14.984-.008 15.047.023 3.695-4.582 4.496-6.707 4.559H33.02v-2.852l13.414-.004c2.73-.285 2.406-1.645 2.406-2.098v-1.113h-9.012c-4.195-.039-6.863-1.871-6.898-3.977-.004-.191.09-9.422 0-9.516zm0 0" />
<path fill="currentColor" d="M56.391 91.313h12.195c1.426 0 2.813-.301 3.914-.816 1.836-.84 2.73-1.984 2.73-3.48v-3.098c0-1.223-1.016-2.367-3.016-3.125-1.059-.41-2.367-.625-3.629-.625h-5.141c-1.711 0-2.527-.516-2.73-1.656-.039-.137-.039-.246-.039-.383V76.2c0-.109 0-.219.039-.355.203-.867.652-1.113 2.16-1.25l.41-.027h12.109v-2.824H63.488c-1.711 0-2.609.109-3.426.352-2.527.789-3.629 2.039-3.629 4.215v2.473c0 1.902 2.16 3.535 5.789 3.914l1.223.055h4.406c.164 0 .324 0 .449.027 1.344.109 1.914.355 2.324.844.211.195.332.473.324.758v2.477c0 .297-.203.68-.609 1.004-.367.328-.98.543-1.793.598l-.449.027H56.391zm45.297-4.922c0 2.91 2.164 4.539 6.523 4.867l1.227.055h11.051v-2.828h-11.133c-2.488 0-3.426-.625-3.426-2.121V71.738h-4.238V86.39zm-23.75.148V76.457c0-2.559 1.801-4.113 5.355-4.602a7.976 7.976 0 0 1 1.145-.082h8.047c.41 0 .777.027 1.188.082 3.555.488 5.352 2.043 5.352 4.602v10.082c0 2.078-.762 3.188-2.523 3.914l4.18 3.77h-4.926l-3.379-3.051-3.402.215H84.44a9.23 9.23 0 0 1-2.492-.352c-2.699-.734-4.008-2.152-4.008-4.496zm4.578-.246c0 .137.039.273.082.438.246 1.172 1.348 1.824 3.023 1.824h3.852l-3.539-3.195h4.926l3.086 2.789c.57-.305.941-.766 1.074-1.363.039-.137.039-.273.039-.41v-9.668c0-.109 0-.246-.039-.383-.246-1.09-1.348-1.715-2.984-1.715h-6.414c-1.879 0-3.105.816-3.105 2.098zm0 0" />
<path fill="#00618A" d="M124.219 67.047c-2.605-.07-4.598.172-6.301.891-.484.203-1.258.207-1.336.813.266.281.309.699.52 1.039.406.66 1.094 1.539 1.707 2l2.074 1.484c1.273.777 2.699 1.223 3.93 2 .723.461 1.441 1.039 2.148 1.559.348.254.582.656 1.039.816v-.074c-.238-.305-.301-.723-.52-1.039l-.965-.965c-.941-1.25-2.137-2.348-3.41-3.262-1.016-.727-3.281-1.711-3.707-2.891l-.074-.074c.719-.078 1.563-.34 2.223-.516 1.117-.301 2.113-.223 3.262-.52l1.559-.449v-.293c-.582-.598-.996-1.387-1.633-1.93-1.656-1.41-3.469-2.824-5.336-4.004-1.035-.652-2.312-1.074-3.41-1.629-.367-.187-1.016-.281-1.262-.594-.574-.734-.887-1.664-1.332-2.52l-2.668-5.633c-.562-1.285-.93-2.555-1.633-3.707-3.363-5.535-6.988-8.875-12.602-12.156-1.191-.699-2.633-.973-4.148-1.332l-2.449-.148c-.496-.211-1.012-.82-1.48-1.113-1.859-1.176-6.629-3.73-8.008-.371-.867 2.121 1.301 4.191 2.078 5.266.543.754 1.242 1.598 1.629 2.445.258.555.301 1.113.52 1.703.539 1.453 1.008 3.031 1.707 4.375.352.68.738 1.395 1.184 2 .273.371.742.539.816 1.113-.457.641-.484 1.633-.742 2.445-1.16 3.652-.723 8.191.965 10.898.516.828 1.734 2.609 3.41 1.926 1.465-.598 1.137-2.445 1.555-4.078.098-.367.039-.641.223-.887v.074l1.336 2.668c.988 1.59 2.738 3.25 4.223 4.371.773.582 1.379 1.59 2.375 1.93V68.6h-.074c-.195-.297-.496-.422-.742-.664-.582-.57-1.227-1.277-1.703-1.93-1.352-1.832-2.547-3.84-3.633-5.93-.52-.996-.973-2.098-1.41-3.113-.168-.391-.164-.984-.516-1.184-.48.742-1.187 1.344-1.559 2.223-.594 1.402-.668 3.117-.891 4.891l-.148.074c-1.031-.25-1.395-1.312-1.777-2.223-.973-2.305-1.152-6.02-.297-8.672.219-.687 1.219-2.852.813-3.484-.191-.633-.828-1-1.184-1.484a11.7 11.7 0 0 1-1.187-2.074c-.793-1.801-1.164-3.816-2-5.633-.398-.871-1.074-1.75-1.629-2.523-.617-.855-1.305-1.484-1.781-2.52-.168-.367-.398-.957-.148-1.336.078-.254.195-.359.445-.441.43-.332 1.629.109 2.074.293 1.191.496 2.184.965 3.191 1.633.48.32.969.941 1.555 1.113h.668c1.043.238 2.211.07 3.188.367 1.723.523 3.27 1.34 4.668 2.227 4.273 2.695 7.766 6.535 10.156 11.117.387.738.551 1.441.891 2.223.684 1.578 1.543 3.203 2.223 4.746s1.34 3.094 2.297 4.375c.504.672 2.453 1.031 3.336 1.406.621.262 1.637.535 2.223.891 1.125.676 2.211 1.48 3.266 2.223.523.375 2.141 1.188 2.223 1.855zM91.082 38.805a5.26 5.26 0 0 0-1.332.148v.074h.074c.258.535.715.879 1.035 1.336l.742 1.555.074-.07c.461-.324.668-.844.668-1.633-.187-.195-.211-.437-.371-.668-.211-.309-.621-.48-.891-.742zm0 0" />
</svg>',
],
[
'id' => 'mariadb',
'name' => 'MariaDB',
'description' => 'MariaDB is an open-source relational database known for its simplicity and performance.',
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="currentColor" d="M127.434 12.182a1.727 1.727 0 0 0-1.174-.392c-1.168 0-2.68.793-3.495 1.218l-.322.165a11.095 11.095 0 0 1-4.365 1.1c-1.554.049-2.892.14-4.635.322-10.327 1.06-14.933 8.975-19.37 16.63-2.416 4.164-4.91 8.489-8.33 11.793a22.472 22.472 0 0 1-2.252 1.913c-3.54 2.631-7.985 4.51-11.443 5.84-3.329 1.273-6.964 2.417-10.474 3.524-3.219 1.012-6.255 1.97-9.048 3.008a96.902 96.902 0 0 1-3.275 1.14c-2.545.825-4.378 1.458-7.06 3.304a45.386 45.386 0 0 0-2.804 2.066 29.585 29.585 0 0 0-5.597 5.894 34.802 34.802 0 0 1-4.701 5.642c-.566.554-1.57.826-3.074.826-1.76 0-3.895-.363-6.154-.747-2.33-.413-4.738-.805-6.803-.805-1.677 0-2.962.273-3.92.826 0 0-1.617.942-2.298 2.16l.67.302a13.718 13.718 0 0 1 2.859 2.065 14.342 14.342 0 0 0 2.973 2.115 2.553 2.553 0 0 1 .918.582c-.281.413-.694.946-1.129 1.516-2.384 3.119-3.774 5.09-2.977 6.163a2.507 2.507 0 0 0 1.239.28c5.196 0 7.989-1.35 11.52-3.06 1.024-.495 2.066-1.004 3.305-1.528 2.065-.896 4.288-2.325 6.647-3.838 3.084-2.01 6.31-4.076 9.442-5.072a25.734 25.734 0 0 1 7.943-1.115c3.305 0 6.783.441 10.138.872 2.499.322 5.089.652 7.63.805.986.057 1.9.086 2.787.086a32.307 32.307 0 0 0 3.557-.185l.284-.1c1.781-1.094 2.617-3.444 3.425-5.717.52-1.462.96-2.775 1.652-3.61a1.054 1.054 0 0 1 .132-.11.166.166 0 0 1 .202.032v.066c-.412 8.885-3.99 14.527-7.608 19.543l-2.416 2.59s3.383 0 5.307-.744c7.024-2.099 12.324-6.725 16.181-14.103a60.185 60.185 0 0 0 2.549-5.82c.065-.165.673-.47.616.384-.021.252-.038.533-.059.827 0 .173 0 .35-.033.528-.1 1.24-.392 3.859-.392 3.859l2.169-1.162c5.229-3.304 9.26-9.97 12.318-20.343 1.272-4.321 2.205-8.613 3.027-12.392.983-4.545 1.83-8.44 2.801-9.952 1.524-2.37 3.85-3.973 6.101-5.53.306-.211.616-.414.917-.637 2.83-1.986 5.643-4.279 6.263-8.555v-.095c.45-3.189.07-4.002-.364-4.373zm-7.283 103.727h-10.327V97.92h9.315c3.56 0 6.952.67 6.902 4.66 0 2.813-1.747 3.59-3.589 3.886 2.615.224 4.188 1.892 4.188 4.586.017 4.035-3.523 4.858-6.489 4.858zm-.772-10.14c3.565 0 4.362-1.372 4.362-3.115 0-2.619-1.595-3.214-4.362-3.214h-7.402v6.328zm.099 1.52h-7.501v7.1h7.823c2.194 0 4.511-.723 4.511-3.486 0-3.19-2.665-3.615-4.833-3.615zm-31.497-9.37h8.125c6.828 0 10.24 3.764 10.19 8.994.05 5.436-3.716 8.997-9.591 8.997H87.98zm2.242 1.596v14.825h6.197c5.432 0 7.501-3.665 7.501-7.477 0-4.309-2.59-7.348-7.5-7.348zm-10.838 5.357v-2.095h3.404v13.132h-3.392v-2.114c-.896 1.52-2.739 2.391-4.982 2.391-4.684 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.454.029 4.498.855 5.469 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.841 0 4.386-2.02 4.386-4.411 0-2.392-1.599-4.436-4.544-4.436-2.828 0-4.3 2.04-4.3 4.362zm-10.013-9.947a1.722 1.722 0 0 1 1.818-1.768 1.788 1.788 0 0 1 1.847 1.821 1.714 1.714 0 0 1-1.847 1.744 1.743 1.743 0 0 1-1.818-1.797zm.15 3.465h3.39v9.596c0 .595.125 1.02.62 1.02a3.657 3.657 0 0 0 .648-.073l.525 2.478a5.931 5.931 0 0 1-2.242.414c-1.421 0-2.942-.414-2.942-3.64zM52.15 115.91h-3.386v-13.132h3.386v2.942a5.197 5.197 0 0 1 4.735-3.218 6.13 6.13 0 0 1 2.119.347l-.723 2.479a7.435 7.435 0 0 0-1.793-.249c-2.445 0-4.338 1.843-4.338 4.545zm-11.037-11.037v-2.095h3.392v13.132h-3.392v-2.114c-.896 1.52-2.738 2.391-4.982 2.391-4.688 0-7.303-3.305-7.303-7.105 0-3.664 2.479-6.609 6.804-6.609 2.466.029 4.51.855 5.481 2.4zm-8.675 4.387c0 2.416 1.52 4.485 4.462 4.485 2.838 0 4.383-2.02 4.383-4.411 0-2.391-1.595-4.436-4.544-4.436-2.826 0-4.296 2.04-4.296 4.362zm-9.24-11.34 4.651 17.99h-3.51L21.24 102.95 15.4 115.91h-2.965l-5.808-12.883-3.19 12.883H0l4.61-17.99h3.04l6.28 13.93 6.253-13.93z"/></svg>',
],
[
'id' => 'redis',
'name' => 'Redis',
'description' => 'Redis is an open-source in-memory data structure store known for its simplicity and performance.',
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path d="M21.4 97.6c0 1.8-1.5 3.5-3.5 3.5-1.5 0-2.8.4-4 1.3-1.3.8-2.2 1.9-3 3.2-1.6 2.1-2.4 4.6-2.7 5.5v12.5c0 1.9-1.6 3.5-3.6 3.5-1.9 0-3.5-1.6-3.5-3.5v-26c0-1.9 1.6-3.4 3.5-3.4 2 0 3.6 1.5 3.6 3.4v.5c.4-.5.9-1 1.4-1.3 2.2-1.4 5-2.6 8.3-2.6 2 0 3.5 1.5 3.5 3.4zm-1.9 13c.1-9 7-16.5 16.1-16.5 8.6 0 15.3 6.4 15.9 15.3v.3c0 .1 0 .5-.1.6-.2 1.6-1.6 2.6-3.4 2.6H27c.3 1.5 1.1 3.2 2.2 4.3 1.4 1.6 4 2.8 6.3 3 2.4.2 5.2-.4 6.8-1.6 1.4-1.4 4.1-1.3 4.9-.2.9.9 1.5 2.9 0 4.3-3.2 3-7.1 4.3-11.8 4.3-8.9 0-15.9-7.5-15.9-16.4zm7.1-3.2h18.6c-.7-2.6-4-6.5-9.7-7-5.6.2-8.3 4.3-8.9 7zm58.3 16.1c0 1.9-1.6 3.6-3.6 3.6-1.8 0-3.2-1.3-3.5-2.8-2.5 1.7-5.7 2.8-9 2.8-8.9 0-16-7.5-16-16.4 0-9 7.1-16.5 16-16.5 3.2 0 6.4 1.1 8.8 2.8V84.5c0-1.9 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v26.2l.1 12.8zm-16-22.2c-2.4 0-4.5 1-6.2 2.7-1.6 1.6-2.6 4-2.6 6.6 0 2.5 1 4.9 2.6 6.5 1.6 1.7 3.8 2.7 6.2 2.7 2.4 0 4.5-1 6.2-2.7 1.6-1.6 2.6-4 2.6-6.5 0-2.6-1-5-2.6-6.6-1.6-1.7-3.7-2.7-6.2-2.7zm28.6-15.4c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6v-1.4c0-2 1.6-3.6 3.6-3.6s3.6 1.6 3.6 3.6v1.4zm0 11.9v25.7c0 2-1.5 3.6-3.6 3.6-2 0-3.6-1.6-3.6-3.6V97.8c0-2.1 1.6-3.6 3.6-3.6 2.1 0 3.6 1.5 3.6 3.6zm4.5 19.8c1.2-1.6 3.5-1.8 4.9-.5 1.7 1.4 4.7 3 7.2 2.9 1.8 0 3.4-.6 4.5-1.3.9-.8 1.2-1.4 1.2-2 0-.3-.1-.5-.2-.7-.1-.2-.3-.5-.9-.8-.9-.7-2.9-1.4-5.3-1.8h-.1c-2-.4-4-.9-5.7-1.7-1.8-.9-3.4-2-4.5-3.8-.7-1.2-1.1-2.6-1.1-4.1 0-3 1.7-5.6 3.9-7.2 2.3-1.6 5.1-2.4 8.1-2.4 4.5 0 7.8 2.2 9.9 3.6 1.6 1.1 2 3.2 1.1 4.9-1.1 1.6-3.2 2-4.9.9-2.1-1.4-4-2.4-6.1-2.4-1.6 0-3.1.5-4 1.2-.9.6-1.1 1.2-1.1 1.5 0 .3 0 .3.1.5.1.1.3.4.7.7.9.6 2.6 1.2 4.8 1.6l.1.1h.1c2.2.4 4.2 1 6.1 1.9 1.8.8 3.6 2 4.7 3.9.8 1.3 1.3 2.8 1.3 4.3 0 3.2-1.8 5.9-4.1 7.6-2.4 1.6-5.3 2.6-8.6 2.6-5.1-.1-9.1-2.4-11.7-4.5-1.4-1.3-1.6-3.5-.4-5z" fill="#currentColor"/><path fill="#A41E11" d="M106.9 62.7c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.1-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 53.3c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8C53.5 68 26.2 57 21.2 54.6c-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 5 1.8 5.2 3.2.2 5.8z"/><path fill="#A41E11" d="M106.9 47.4c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.4-2.1-31.7-13.2-36.7-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c.1.9-1 1.9-3.5 3.2z"/><path fill="#D82C20" d="M106.9 38c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-4.9-2.4-5-4-.2-5.9 4.8-1.9 32.1-12.6 37.8-14.6 5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#A41E11" d="M106.9 31.5c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5-2.5-1.2-3.8-2.2-3.8-3.1v-9.4s35.6-7.8 41.4-9.8c5.8-2.1 7.8-2.1 12.6-.3 4.9 1.8 34.2 7.1 39 8.8v9.3c0 .8-1.1 1.9-3.6 3.2z"/><path fill="#D82C20" d="M106.9 22.1c-5 2.6-30.7 13.2-36.2 16-5.5 2.9-8.5 2.8-12.8.8-4.3-2.1-31.7-13.1-36.6-15.5s-5-4-.2-5.9c4.8-1.9 32.1-12.6 37.8-14.6C64.7.8 66.7.8 71.5 2.6c4.9 1.8 30.5 12 35.3 13.7 4.9 1.7 5.1 3.2.1 5.8z"/><path fill="#fff" d="M76.2 13l-8.1.8-1.8 4.4-2.9-4.9-9.3-.8L61 10l-2-3.8 6.5 2.5 6.1-2-1.7 4zM65.8 34.1l-15-6.3 21.6-3.3z"/><ellipse fill="#fff" cx="45" cy="19.9" rx="11.5" ry="4.5"/><path fill="#7A0C00" d="M85.7 14.2l12.8 5-12.8 5.1z"/><path fill="#AD2115" d="M71.6 19.8l14.1-5.6v10.1l-1.3.5z"/></svg>',
],
[
'id' => 'keydb',
'name' => 'KeyDB',
'description' => 'KeyDB is an open-source in-memory data structure store known for its simplicity and performance.',
'logo' => ' <div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg version="1.1" width="180" height="60" id="svg1326" viewBox="0 0 544 182" sodipodi:docname="keydb.svg" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs1330"/><sodipodi:namedview id="namedview1328" pagecolor="currentColor" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.00784314" inkscape:pagecheckerboard="true" showgrid="false" inkscape:zoom="1.5064209" inkscape:cx="374.06544" inkscape:cy="123.80338" inkscape:window-width="1536" inkscape:window-height="889" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg1326" width="791px"/><path d="M 78.589334,169.85617 12.63903,131.77977 c -1.0281,-0.591 -1.6035,-1.6659 -1.6046,-2.7722 h -0.0134 V 52.833267 c 0,-1.2966 0.7688,-2.4135 1.8749,-2.9206 l 65.739904,-37.9545 c 1.0386,-0.5966 2.2717,-0.5456 3.2321,0.0264 l 65.951096,38.0761 c 1.0282,0.591 1.6042,1.6659 1.6047,2.7726 h 0.0134 v 76.174303 c 0,1.2962 -0.7685,2.4137 -1.8743,2.9202 l -65.740496,37.9554 c -1.0389,0.5964 -2.2717,0.5453 -3.233,-0.027 z M 17.44353,127.16987 v 0 l 62.785704,36.2488 62.785996,-36.2488 V 54.671267 l -62.785996,-36.2489 -62.785704,36.2489 z" style="fill:currentColor;fill-rule:evenodd" id="path1290"/><path d="M 80.229234,14.730167 14.23273,129.00757 h 131.9933 z" style="opacity:.88;fill:#ff0;fill-opacity:1;fill-rule:evenodd" id="path1292"/><path d="M 80.229234,21.136467 19.78603,125.79677 h 120.8861 z M 11.45983,127.40197 v 0 L 77.433934,13.163767 c 0.2713,-0.4856 0.6733,-0.9068 1.1895,-1.2056 1.531,-0.8864 3.4908,-0.3645 4.3778,1.1671 L 148.88863,127.21177 c 0.3464,0.5125 0.5485,1.1308 0.5485,1.7958 0,1.7733 -1.4378,3.2111 -3.2108,3.2111 H 14.23213 v -0.007 c -0.5459,5e-4 -1.099,-0.1386 -1.6052,-0.4318 -1.531,-0.8863 -2.0537,-2.8465 -1.1671,-4.3778 z" style="fill:currentColor;fill-rule:evenodd" id="path1294"/><path d="m 12.63903,55.605567 c -1.5309,-0.8799 -2.059,-2.8347 -1.1792,-4.3657 0.8802,-1.531 2.8347,-2.0591 4.3657,-1.1792 L 80.229234,87.229065 144.63323,50.060667 c 1.531,-0.8799 3.4855,-0.3518 4.3651,1.1792 0.8796,1.531 0.3517,3.4858 -1.1793,4.3657 L 81.867334,93.666065 c -0.9603,0.5723 -2.1931,0.6227 -3.2315,0.026 z" style="opacity:1;fill:currentColor;fill-rule:evenodd" id="path1296"/><path d="m 83.440034,167.11087 c 0,1.773 -1.4377,3.2111 -3.2108,3.2111 -1.7736,0 -3.2114,-1.4381 -3.2114,-3.2111 V 90.920065 c 0,-1.773 1.4378,-3.2111 3.2114,-3.2111 1.7731,0 3.2108,1.4381 3.2108,3.2111 z" style="fill:currentColor;fill-rule:evenodd" id="path1298"/><path d="m 146.22633,137.25697 c 4.5555,0 8.2491,-3.693 8.2491,-8.2494 0,-4.5564 -3.6936,-8.2491 -8.2491,-8.2491 -4.5564,0 -8.2503,3.6927 -8.2503,8.2491 0,4.5564 3.6939,8.2494 8.2503,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1300"/><path d="m 146.22633,61.082867 c 4.5555,0 8.2491,-3.6938 8.2491,-8.2496 0,-4.5562 -3.6936,-8.2494 -8.2491,-8.2494 -4.5564,0 -8.2503,3.6932 -8.2503,8.2494 0,4.5558 3.6939,8.2496 8.2503,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1302"/><path d="m 14.23213,61.082867 c 4.5561,0 8.25,-3.6938 8.25,-8.2496 0,-4.5562 -3.6939,-8.2494 -8.25,-8.2494 -4.5555003,0 -8.2494003,3.6932 -8.2494003,8.2494 0,4.5558 3.6939,8.2496 8.2494003,8.2496 z" style="fill:currentColor;fill-rule:evenodd" id="path1304"/><path d="m 14.23213,137.25697 c 4.5561,0 8.25,-3.693 8.25,-8.2494 0,-4.5564 -3.6939,-8.2491 -8.25,-8.2491 -4.5555003,0 -8.2494003,3.6927 -8.2494003,8.2491 0,4.5564 3.6939,8.2494 8.2494003,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1306"/><path d="m 80.229234,175.36027 c 4.5558,0 8.2497,-3.6933 8.2497,-8.2494 0,-4.5562 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6932 -8.2497,8.2494 0,4.5561 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1308"/><path d="m 80.229234,99.170065 c 4.5558,0 8.2497,-3.6938 8.2497,-8.25 0,-4.5561 -3.6939,-8.2494 -8.2497,-8.2494 -4.5564,0 -8.2497,3.6933 -8.2497,8.2494 0,4.5562 3.6933,8.25 8.2497,8.25 z" style="fill:currentColor;fill-rule:evenodd" id="path1310"/><path d="m 80.229234,22.979567 c 4.5558,0 8.2497,-3.6932 8.2497,-8.2494 0,-4.5561 -3.6939,-8.2492996 -8.2497,-8.2492996 -4.5564,0 -8.2497,3.6931996 -8.2497,8.2492996 0,4.5562 3.6933,8.2494 8.2497,8.2494 z" style="fill:currentColor;fill-rule:evenodd" id="path1312"/></svg></div>',
],
[
'id' => 'dragonfly',
'name' => 'Dragonfly',
'description' => 'Dragonfly is an open-source in-memory data structure store known for its simplicity and performance.',
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg class="block dark:hidden" width="245" height="60" viewBox="0 0 210 50" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 0C3.883 0 0 3.883 0 22C0 40.117 3.883 44 22 44C40.117 44 44 40.117 44 22C44 3.883 40.117 0 22 0ZM19.5585 16.265C19.8224 16.406 20 16.7008 20 17C20 18.1046 20.8954 19 22 19C23.1046 19 24 18.1046 24 17C24 16.7008 24.1776 16.406 24.4414 16.265C25.0714 15.9283 25.5 15.2642 25.5 14.5C25.5 13.3954 24.6046 12.5 23.5 12.5C23.3071 12.5 23.1205 12.5273 22.944 12.5783C22.3661 12.7452 21.6339 12.7452 21.056 12.5783C20.8794 12.5273 20.6929 12.5 20.5 12.5C19.3954 12.5 18.5 13.3954 18.5 14.5C18.5 15.2642 18.9286 15.9283 19.5585 16.265ZM20.3087 18.8411C20.2282 18.8977 20.1644 18.978 20.1283 19.0744L19.6243 20.4185C19.5438 20.633 19.5395 20.8686 19.6119 21.0859L20.4253 23.526C20.4744 23.6733 20.5566 23.8087 20.687 23.893C20.9154 24.0408 21.3531 24.25 22 24.25C22.6468 24.25 23.0845 24.0408 23.3129 23.893C23.4433 23.8087 23.5255 23.6733 23.5746 23.526L24.388 21.0859C24.4604 20.8686 24.4561 20.633 24.3756 20.4185L23.8716 19.0744C23.8355 18.978 23.7717 18.8977 23.6912 18.8411C23.2461 19.2502 22.6522 19.5 22 19.5C21.3477 19.5 20.7538 19.2502 20.3087 18.8411ZM21 30L20.5105 24.3712C20.8174 24.5497 21.3156 24.75 22 24.75C22.6842 24.75 23.1825 24.5498 23.4895 24.3711L23 30C23 30 22.75 30.25 22 30.25C21.25 30.25 21 30 21 30ZM21.25 34L21.0055 30.5779L21.0331 30.5892C21.2547 30.6779 21.5686 30.75 22 30.75C22.4314 30.75 22.7453 30.6779 22.967 30.5892L22.9944 30.578L22.75 34C22.75 34 22.625 34.5 22 34.5C21.375 34.5 21.25 34 21.25 34ZM9.24995 20H19.2C18.8224 20.6294 18.9914 21.0678 19.1582 21.5005L19.1582 21.5005L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006L19.1582 21.5006C19.1901 21.5835 19.222 21.6662 19.25 21.75L9.90851 24.4252C9.31202 24.5932 8.67362 24.3772 8.3018 23.8814L7.71391 23.0976C7.37302 22.643 7.31818 22.0349 7.57227 21.5267L7.92108 20.8291C8.17311 20.3251 8.68641 20.0048 9.24995 20ZM7.99998 19.2331L19.375 19.5C19.375 19.5 19.5546 19.0506 19.625 18.75C19.6491 18.647 19.6745 18.5441 19.6961 18.4586C19.7264 18.3386 19.6699 18.2154 19.5562 18.1662C18.2312 17.5935 10.5221 14.2625 9.23721 13.7331C8.73807 13.5197 8.16257 13.5926 7.73242 13.9238L6.58499 14.8072C6.20854 15.097 5.99872 15.5412 5.99998 16C6.00037 16.1456 6.02204 16.2928 6.06639 16.4369L6.62254 18.2331C6.80943 18.8405 7.36503 19.2067 7.99998 19.2331ZM34.75 20H24.8C25.1776 20.6294 25.0086 21.0679 24.8418 21.5005L24.8418 21.5005L24.8418 21.5006L24.8418 21.5006C24.8098 21.5835 24.7779 21.6662 24.75 21.75L34.0914 24.4252C34.6879 24.5932 35.3263 24.3772 35.6982 23.8814L36.286 23.0976C36.6269 22.643 36.6818 22.0349 36.4277 21.5267L36.0789 20.8291C35.8268 20.3251 35.3135 20.0048 34.75 20ZM36 19.2331L24.625 19.5C24.625 19.5 24.4453 19.0506 24.375 18.75C24.3509 18.647 24.3255 18.5441 24.3039 18.4586C24.2735 18.3386 24.3301 18.2154 24.4438 18.1662C25.7688 17.5935 33.4779 14.2625 34.7627 13.7331C35.2619 13.5197 35.8374 13.5926 36.2675 13.9238L37.415 14.8072C37.7914 15.097 38.0012 15.5412 38 16C37.9996 16.1456 37.9779 16.2928 37.9336 16.4369L37.3774 18.2331C37.1905 18.8405 36.6349 19.2067 36 19.2331Z" fill="black"/></svg><svg class="hidden dark:block" xmlns="http://www.w3.org/2000/svg" width="225" height="55" viewBox="0 0 378 88" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M44 0C7.766 0 0 7.766 0 44C0 80.234 7.766 88 44 88C80.234 88 88 80.234 88 44C88 7.766 80.234 0 44 0ZM39.1171 32.53C39.6448 32.8121 40 33.4016 40 34C40 36.2091 41.7909 38 44 38C46.2091 38 48 36.2091 48 34C48 33.4016 48.3552 32.8121 48.8829 32.53C50.1428 31.8566 51 30.5284 51 29C51 26.7909 49.2091 25 47 25C46.6142 25 46.2411 25.0546 45.8881 25.1566C44.7322 25.4904 43.2678 25.4904 42.1119 25.1566C41.7589 25.0546 41.3858 25 41 25C38.7909 25 37 26.7909 37 29C37 30.5284 37.8572 31.8566 39.1171 32.53ZM40.6174 37.6822C40.4565 37.7954 40.3289 37.9561 40.2566 38.1489L39.2486 40.837C39.0877 41.266 39.079 41.7371 39.2238 42.1717L40.8506 47.0521C40.9488 47.3466 41.1133 47.6174 41.374 47.786C41.8309 48.0815 42.7062 48.5 43.9999 48.5C45.2937 48.5 46.169 48.0815 46.6259 47.786C46.8866 47.6174 47.0511 47.3466 47.1492 47.0521L48.776 42.1717C48.9209 41.7371 48.9122 41.266 48.7513 40.837L47.7433 38.1489C47.671 37.9561 47.5434 37.7954 47.3825 37.6822C46.4922 38.5005 45.3044 39 43.9999 39C42.6955 39 41.5077 38.5005 40.6174 37.6822ZM42 60L41.0211 48.7423C41.6348 49.0994 42.6312 49.5 44 49.5C45.3684 49.5 46.365 49.0996 46.979 48.7423L46 60C46 60 45.5 60.5 44 60.5C42.5 60.5 42 60 42 60ZM42.5 68L42.0111 61.1559C42.0291 61.1635 42.0474 61.171 42.0662 61.1785C42.5095 61.3558 43.1373 61.5 44 61.5C44.8628 61.5 45.4906 61.3558 45.9339 61.1785C45.9526 61.171 45.9709 61.1635 45.9888 61.156L45.5 68C45.5 68 45.25 69 44 69C42.75 69 42.5 68 42.5 68ZM18.49 Line truncated
],
[
'id' => 'mongodb',
'name' => 'MongoDB',
'description' => 'MongoDB is an open-source document-oriented database known for its simplicity and performance.',
'logo' => '<svg class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M87.259 100.139c.169-.325.331-.612.469-.909.087-.19.221-.228.41-.223 1.133.032 2.266.067 3.4.078.963.01 1.928-.008 2.892-.019 1.086-.013 2.172-.07 3.257-.039 1.445.042 2.853.325 4.16.968 1.561.769 2.742 1.94 3.547 3.483a8.71 8.71 0 01.931 3.14c.172 1.608.059 3.179-.451 4.717-.632 1.906-1.832 3.365-3.499 4.458-1.283.841-2.69 1.338-4.198 1.622-1.596.301-3.197.204-4.798.209-1.756.007-3.511-.031-5.267-.051-.307-.003-.351-.061-.27-.354l.075-.27c.171-.538.263-.562.809-.652a2.83 2.83 0 001.087-.413c.184-.122.26-.44.332-.685.062-.214.065-.449.067-.675.025-3.425.051-6.849.065-10.272a44.077 44.077 0 00-.065-2.596c-.034-.605-.357-1.019-1.077-1.162-.56-.111-1.124-.197-1.687-.296l-.189-.059zm16.076 8.293c-.076-.682-.113-1.37-.235-2.042-.292-1.613-.998-3.018-2.238-4.119-2.005-1.779-4.419-2.053-6.949-1.841-.576.048-.7.245-.709.837-.014.84-.028 1.68-.029 2.52-.004 2.664-.004 5.328 0 7.992.001.758.009 1.516.031 2.272.024.774.305 1.429 1.063 1.729 1.195.473 2.452.529 3.706.336 2.003-.307 3.404-1.474 4.344-3.223.744-1.388.954-2.903 1.016-4.461zm4.869 9.071c-.024-.415.146-.758.356-1.073.057-.085.253-.081.388-.108l1.146-.227c.405-.086.618-.358.675-.755.038-.262.074-.527.077-.792a879.6 879.6 0 00.059-6.291c.01-2.1.002-4.2.002-6.3l-.009-.401c-.041-.675-.367-1.025-1.037-1.124l-1.453-.221c-.179-.024-.244-.11-.179-.269.112-.271.219-.552.377-.796.059-.09.258-.125.392-.122.694.01 1.388.062 2.082.061l6.041-.036c1.164-.001 2.288.202 3.332.759 1.149.612 1.792 1.559 1.976 2.849.192 1.355-.219 2.497-1.209 3.404-.407.374-.934.618-1.406.922l-.154.096c.438.161.855.3 1.261.466 1.188.487 2.133 1.248 2.633 2.463.395.959.395 1.959.161 2.953-.364 1.556-1.389 2.591-2.722 3.374-1.251.735-2.605 1.163-4.047 1.235-1.33.067-2.666.042-3.999.057l-.772.004a996.106 996.106 0 01-3.854-.096l-.117-.032zm5.537-6.089h.013c0 .658-.009 1.316.003 1.974.008.426-.007.864.085 1.274.138.613.418 1.166 1.106 1.342a6.671 6.671 0 002.818.124c1.177-.205 2.116-.795 2.631-1.916.382-.833.439-1.716.308-2.618-.174-1.188-.805-2.05-1.854-2.615-.688-.371-1.422-.598-2.204-.628-.876-.033-1.753-.035-2.629-.062-.246-.007-.28.118-.279.32.005.934.002 1.869.002 2.805zm1.865-4.475c.479-.024 1.021-.031 1.56-.085 1.032-.103 1.759-.622 2.138-1.609.193-.501.185-1.017.19-1.538.015-1.357-.777-2.469-2.066-2.929-.995-.355-2.021-.361-3.053-.333-.418.011-.605.194-.611.615l-.062 5.489c-.003.218.091.312.303.319l1.601.071z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M10.543 116.448l-.073.944c-.68 0-1.307-.005-1.934.002-1.181.012-2.362.031-3.544.048l-.114.007c-.169-.02-.476-.02-.484-.07-.05-.281-.034-.576-.021-.867.001-.033.116-.075.183-.091l.919-.205c.573-.149.775-.396.802-.988.031-.667.062-1.335.065-2.002.009-1.642-.001-3.282.006-4.924.001-.384-.132-.67-.49-.826a43.787 43.787 0 00-1.285-.546c-.204-.082-.469-.127-.445-.401.024-.279.281-.352.523-.407 1.002-.229 2.005-.452 3.004-.696.322-.079.63-.212 1.015-.346.02.208.057.406.053.604l-.059.941c-.001.106.054.248.133.307.048.037.209-.03.289-.092.854-.65 1.758-1.211 2.789-1.538 1.597-.507 2.968-.037 3.928 1.34.338.485.339.485.808.146.805-.585 1.647-1.101 2.589-1.441 2.068-.747 4.055.201 4.774 2.284.262.756.362 1.537.36 2.335l-.019 5.298c-.001.437.144.686.56.822.467.153.951.258 1.477.396l-.122.911c-.598 0-1.148-.004-1.698.001-1.344.012-2.688.019-4.031.05-.234.006-.295-.052-.307-.271-.039-.701-.045-.7.615-.858l.222-.057c.645-.176.86-.374.865-1.028.015-1.878.054-3.761-.041-5.635-.099-1.944-1.642-2.979-3.526-2.481a5.194 5.194 0 00-1.69.814c-.175.125-.208.269-.194.488.053.828.086 1.657.093 2.486.012 1.451-.006 2.902 0 4.354.002.588.203.813.784.949l.863.199.16.036c.012.276.023.552.01.828-.008.173-.142.188-.292.185-.839-.021-1.679-.049-2.518-.047-1.021.002-2.041.031-3.061.049h-.24c0-.293-.014-.573.01-.852.005-.067.123-.161.204-.182l1.006-.213c.427-.105.631-.324.655-.758.114-2.01.196-4.021.007-6.03a3.695 3.695 0 00-.326-1.145c-.515-1.138-1.674-1.613-3.011-1.271-.635.162-1.208.453-1.75.82a.795.795 0 00-.378.695c0 2.005.007 4.01.013 6.014l.011.773c.012.539.241.823.776.939.344.078.692.131 1.082.203z"/><path fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" d="M71.001 105.285c.155.754.152 1.432-.402 1.946-.55.511-1.246.339-1.925.225.063.358.133.662.167.97.247 2.289-.738 3.988-2.861 4.959-.802.366-1.653.502-2.522.572-.432.034-.81.364-.851.719-.042.36.184.73.636.838.533.127 1.089.163 1.636.226 1.174.134 2.361.195 3.521.405 1.754.316 2.733 1.847 2.424 3.609-.275 1.568-1.183 2.709-2.449 3.584-2.133 1.478-4.473 1.91-6.965 1.156-1.425-.432-2.43-1.369-2.777-2.885-.174-.759.011-1.446.582-1.961.679-.61 1.418-1.154 2.129-1.73l.23-.231-.264-.185c-.725-.344-1.305-.815-1.53-1.633-.077-.277.003-.459.238-.601.4-.241.798-.4 Line truncated
],
[
'id' => 'clickhouse',
'name' => 'ClickHouse',
'description' => 'ClickHouse is an open-source column-oriented database known for its simplicity and performance.',
'logo' => '<div class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 dark:fill-white fill-black"><svg width="215" height="90" viewBox="0 0 100 43" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_378_10860)"><rect x="2.70837" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="7.2085" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="11.7086" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="16.2076" y="2.875" width="2.24992" height="20.2493" rx="0.236664" fill="currentColor"/><rect x="20.7087" y="10.7502" width="2.24992" height="4.49985" rx="0.236664" fill="currentColor"/></g></svg></div>',
],
];
return [
'services' => $services,
'gitBasedApplications' => $gitBasedApplications,
'dockerBasedApplications' => $dockerBasedApplications,
'databases' => $databases,
];
}
public function updatedSearch()
{
$this->loadServices();
+16 -1
View File
@@ -108,8 +108,23 @@ class Navbar extends Component
return;
}
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}
public function pullAndRestartEvent()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return;
}
PullImage::run($this->service);
StopService::run($this->service);
StopService::run(service: $this->service, dockerCleanup: false);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
+5 -3
View File
@@ -91,10 +91,12 @@ class Danger extends Component
public function delete($password)
{
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
if (isProduction()) {
if (! Hash::check($password, Auth::user()->password)) {
$this->addError('password', 'The provided password is incorrect.');
return;
return;
}
}
if (! $this->resource) {
@@ -48,14 +48,6 @@ class Add extends Component
public function submit()
{
$this->validate();
// if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
// $type = str($this->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$this->dispatch('saveKey', [
'key' => $this->key,
'value' => $this->value,
@@ -53,30 +53,16 @@ class All extends Component
public function sortEnvironmentVariables()
{
if ($this->resource->type() === 'application') {
$this->resource->load(['environment_variables', 'environment_variables_preview']);
} else {
$this->resource->load(['environment_variables']);
if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) {
if ($this->resource->environment_variables) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values();
}
if ($this->resource->environment_variables_preview) {
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('order')->values();
}
}
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
$sortFunction = function ($variables) use ($sortBy) {
if (! $variables) {
return $variables;
}
if ($sortBy === 'key') {
return $variables->sortBy(function ($item) {
return strtolower($item->key);
}, SORT_NATURAL | SORT_FLAG_CASE)->values();
} else {
return $variables->sortBy('order')->values();
}
};
$this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
$this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
$this->getDevView();
}
@@ -121,6 +107,8 @@ class All extends Component
$this->sortEnvironmentVariables();
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->refreshEnvs();
}
}
@@ -11,6 +11,8 @@ use Livewire\Component;
class ExecuteContainerCommand extends Component
{
public $selected_container = 'default';
public $container;
public Collection $containers;
@@ -83,11 +85,14 @@ class ExecuteContainerCommand extends Component
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
foreach ($containers as $container) {
$payload = [
'server' => $server,
'container' => $container,
];
$this->containers = $this->containers->push($payload);
// if container state is running
if (data_get($container, 'State') === 'running') {
$payload = [
'server' => $server,
'container' => $container,
];
$this->containers = $this->containers->push($payload);
}
}
} elseif (data_get($this->parameters, 'database_uuid')) {
if ($this->resource->isRunning()) {
@@ -100,7 +105,6 @@ class ExecuteContainerCommand extends Component
}
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->resource->applications()->get()->each(function ($application) {
ray($application);
if ($application->isRunning()) {
$this->containers->push([
'server' => $this->resource->server,
@@ -131,9 +135,14 @@ class ExecuteContainerCommand extends Component
#[On('connectToContainer')]
public function connectToContainer()
{
if ($this->selected_container === 'default') {
$this->dispatch('error', 'Please select a container.');
return;
}
try {
$container_name = data_get($this->container, 'container.Names');
if (is_null($container_name)) {
$container = collect($this->containers)->firstWhere('container.Names', $this->selected_container);
if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($this->container, 'server');
@@ -141,11 +150,11 @@ class ExecuteContainerCommand extends Component
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
$this->dispatch('send-terminal-command',
true,
$container_name,
$server->uuid,
$this->dispatch(
'send-terminal-command',
isset($container),
data_get($container, 'container.Names'),
data_get($container, 'server.uuid')
);
} catch (\Throwable $e) {
+2 -2
View File
@@ -34,9 +34,9 @@ class Terminal extends Component
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}
// ssh command is sent back to frontend then to websocket
+24 -10
View File
@@ -15,6 +15,8 @@ class ApiTokens extends Component
public bool $readOnly = true;
public bool $rootAccess = false;
public array $permissions = ['read-only'];
public $isApiEnabled;
@@ -35,12 +37,11 @@ class ApiTokens extends Component
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
if (count($this->permissions) == 0) {
$this->permissions = ['*'];
}
$this->makeSureOneIsSelected();
}
public function updatedReadOnly()
@@ -48,11 +49,30 @@ class ApiTokens extends Component
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
if (count($this->permissions) == 0) {
$this->makeSureOneIsSelected();
}
public function updatedRootAccess()
{
if ($this->rootAccess) {
$this->permissions = ['*'];
$this->readOnly = false;
$this->viewSensitiveData = false;
} else {
$this->readOnly = true;
$this->permissions = ['read-only'];
}
}
public function makeSureOneIsSelected()
{
if (count($this->permissions) == 0) {
$this->permissions = ['read-only'];
$this->readOnly = true;
}
}
@@ -62,12 +82,6 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
// if ($this->viewSensitiveData) {
// $this->permissions[] = 'view:sensitive';
// }
// if ($this->readOnly) {
// $this->permissions[] = 'read-only';
// }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
@@ -30,6 +30,11 @@ class ConfigureCloudflareTunnels extends Component
public function submit()
{
try {
if (str($this->ssh_domain)->contains('https://')) {
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
// remove / from the end
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
}
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;
+5 -2
View File
@@ -4,7 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Docker\GetContainersStatus;
use App\Actions\Proxy\CheckProxy;
use App\Jobs\ContainerStatusJob;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Livewire\Component;
@@ -44,7 +44,10 @@ class Status extends Component
}
$this->numberOfPolls++;
}
CheckProxy::run($this->server, true);
$shouldStart = CheckProxy::run($this->server, true);
if ($shouldStart) {
StartProxy::run($this->server, false);
}
$this->dispatch('proxyStatusUpdated');
if ($this->server->proxy->status === 'running') {
$this->polling = false;
+2 -2
View File
@@ -60,7 +60,7 @@ class Index extends Component
public function mount()
{
if (isInstanceAdmin()) {
$this->settings = InstanceSettings::get();
$this->settings = instanceSettings();
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
@@ -162,7 +162,7 @@ class Index extends Component
{
CheckForUpdatesJob::dispatchSync();
$this->dispatch('updateAvailable');
$settings = InstanceSettings::get();
$settings = instanceSettings();
if ($settings->new_version_available) {
$this->dispatch('success', 'New version available!');
} else {
+1 -1
View File
@@ -29,7 +29,7 @@ class License extends Component
abort(404);
}
$this->instance_id = config('app.id');
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
}
public function render()
+1 -1
View File
@@ -42,7 +42,7 @@ class SettingsBackup extends Component
public function mount()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$settings = instanceSettings();
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
+1 -1
View File
@@ -43,7 +43,7 @@ class SettingsEmail extends Component
public function mount()
{
if (isInstanceAdmin()) {
$this->settings = InstanceSettings::get();
$this->settings = instanceSettings();
$this->emails = auth()->user()->email;
} else {
return redirect()->route('dashboard');
+1 -1
View File
@@ -99,7 +99,7 @@ class Change extends Component
return redirect()->route('source.all');
}
$this->applications = $this->github_app->applications;
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->name = str($this->github_app->name)->kebab();
+10 -8
View File
@@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];
public function mount()
public function updatedEndpoint($value)
{
if (isDev()) {
$this->name = 'Local MinIO';
$this->description = 'Local MinIO';
$this->key = 'minioadmin';
$this->secret = 'minioadmin';
$this->bucket = 'local';
$this->endpoint = 'http://coolify-minio:9000';
if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
$this->endpoint = 'https://'.$value;
$value = $this->endpoint;
}
if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
$this->bucket = str($value)->after('//')->before('.');
} elseif (str($value)->contains('your-objectstorage.com')) {
$this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ class Index extends Component
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}
+11 -3
View File
@@ -104,7 +104,7 @@ class Application extends BaseModel
{
use SoftDeletes;
private static $parserVersion = '3';
private static $parserVersion = '4';
protected $guarded = [];
@@ -143,6 +143,9 @@ class Application extends BaseModel
}
$application->tags()->detach();
$application->previews()->delete();
foreach ($application->deployment_queue as $deployment) {
$deployment->delete();
}
});
}
@@ -304,7 +307,7 @@ class Application extends BaseModel
'application_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid,
]);
$settings = InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$url = Url::fromString($route);
$url = $url->withPort(null);
@@ -710,6 +713,11 @@ class Application extends BaseModel
return $this->hasMany(ApplicationPreview::class);
}
public function deployment_queue()
{
return $this->hasMany(ApplicationDeploymentQueue::class);
}
public function destination()
{
return $this->morphTo();
@@ -1150,7 +1158,7 @@ class Application extends BaseModel
public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->compose_parsing_version === '3') {
if ((int) $this->compose_parsing_version >= 3) {
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
+15
View File
@@ -2,6 +2,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use OpenApi\Attributes as OA;
@@ -39,6 +40,20 @@ class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
public function application(): Attribute
{
return Attribute::make(
get: fn () => Application::find($this->application_id),
);
}
public function server(): Attribute
{
return Attribute::make(
get: fn () => Server::find($this->server_id),
);
}
public function setStatus(string $status)
{
$this->update([
-5
View File
@@ -126,11 +126,6 @@ class EnvironmentVariable extends Model
$env = $this->get_real_environment_variables($this->value, $resource);
return data_get($env, 'value', $env);
if (is_string($env)) {
return $env;
}
return $env->value;
}
);
}
+13
View File
@@ -85,4 +85,17 @@ class InstanceSettings extends Model implements SendsEmail
return "[{$instanceName}]";
}
public function helperVersion(): Attribute
{
return Attribute::make(
get: function ($value) {
if (isDev()) {
return 'latest';
}
return $value;
}
);
}
}
+4 -2
View File
@@ -24,9 +24,11 @@ class Project extends BaseModel
{
protected $guarded = [];
protected $appends = ['default_environment'];
public static function ownedByCurrentTeam()
{
return Project::whereTeamId(currentTeam()->id)->orderBy('name');
return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)');
}
protected static function booted()
@@ -131,7 +133,7 @@ class Project extends BaseModel
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
public function default_environment()
public function getDefaultEnvironmentAttribute()
{
$default = $this->environments()->where('name', 'production')->first();
if ($default) {
+10
View File
@@ -40,6 +40,16 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
public function isHetzner()
{
return str($this->endpoint)->contains('your-objectstorage.com');
}
public function isDigitalOcean()
{
return str($this->endpoint)->contains('digitaloceanspaces.com');
}
public function testConnection(bool $shouldSave = false)
{
try {
+26 -13
View File
@@ -268,7 +268,7 @@ respond 404
public function setupDynamicProxyConfiguration()
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$dynamic_config_path = $this->proxyPath().'/dynamic';
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
$file = "$dynamic_config_path/coolify.yaml";
@@ -448,11 +448,19 @@ $schema://$host {
// Should move everything except /caddy and /nginx to /traefik
// The code needs to be modified as well, so maybe it does not worth it
if ($proxyType === ProxyTypes::TRAEFIK->value) {
$proxy_path = $proxy_path;
// Do nothing
} elseif ($proxyType === ProxyTypes::CADDY->value) {
$proxy_path = $proxy_path.'/caddy';
if (isDev()) {
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
} else {
$proxy_path = $proxy_path.'/caddy';
}
} elseif ($proxyType === ProxyTypes::NGINX->value) {
$proxy_path = $proxy_path.'/nginx';
if (isDev()) {
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
} else {
$proxy_path = $proxy_path.'/nginx';
}
}
return $proxy_path;
@@ -460,15 +468,6 @@ $schema://$host {
public function proxyType()
{
// $proxyType = $this->proxy->get('type');
// if ($proxyType === ProxyTypes::NONE->value) {
// return $proxyType;
// }
// if (is_null($proxyType)) {
// $this->proxy->type = ProxyTypes::TRAEFIK->value;
// $this->proxy->status = ProxyStatus::EXITED->value;
// $this->save();
// }
return data_get($this->proxy, 'type');
}
@@ -1212,4 +1211,18 @@ $schema://$host {
return $this;
}
public function storageCheck(): ?string
{
$commands = [
'df / --output=pcent | tr -cd 0-9',
];
return instant_remote_process($commands, $this, false);
}
public function isIpv6(): bool
{
return str($this->ip)->contains(':');
}
}
+102 -5
View File
@@ -42,7 +42,7 @@ class Service extends BaseModel
{
use HasFactory, SoftDeletes;
private static $parserVersion = '3';
private static $parserVersion = '4';
protected $guarded = [];
@@ -285,6 +285,65 @@ class Service extends BaseModel
foreach ($applications as $application) {
$image = str($application->image)->before(':')->value();
switch ($image) {
case str($image)?->contains('invoiceninja'):
$data = collect([]);
$email = $this->environment_variables()->where('key', 'IN_USER_EMAIL')->first();
$data = $data->merge([
'Email' => [
'key' => 'IN_USER_EMAIL',
'value' => data_get($email, 'value'),
'rules' => 'required|email',
],
]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_INVOICENINJAUSER')->first();
$data = $data->merge([
'Password' => [
'key' => 'IN_PASSWORD',
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
$fields->put('Invoice Ninja', $data->toArray());
break;
case str($image)?->contains('argilla'):
$data = collect([]);
$api_key = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_APIKEY')->first();
$data = $data->merge([
'API Key' => [
'key' => data_get($api_key, 'key'),
'value' => data_get($api_key, 'value'),
'isPassword' => true,
'rules' => 'required',
],
]);
$data = $data->merge([
'API Key' => [
'key' => data_get($api_key, 'key'),
'value' => data_get($api_key, 'value'),
'isPassword' => true,
'rules' => 'required',
],
]);
$username = $this->environment_variables()->where('key', 'ARGILLA_USERNAME')->first();
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ARGILLA')->first();
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
$fields->put('Argilla', $data->toArray());
break;
case str($image)?->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
@@ -770,6 +829,30 @@ class Service extends BaseModel
}
$fields->put('Code Server', $data->toArray());
break;
case str($image)->contains('elestio/strapi'):
$data = collect([]);
$license = $this->environment_variables()->where('key', 'STRAPI_LICENSE')->first();
if ($license) {
$data = $data->merge([
'License' => [
'key' => data_get($license, 'key'),
'value' => data_get($license, 'value'),
],
]);
}
$nodeEnv = $this->environment_variables()->where('key', 'NODE_ENV')->first();
if ($nodeEnv) {
$data = $data->merge([
'Node Environment' => [
'key' => data_get($nodeEnv, 'key'),
'value' => data_get($nodeEnv, 'value'),
],
]);
}
$fields->put('Strapi', $data->toArray());
break;
}
}
$databases = $this->databases()->get();
@@ -1052,12 +1135,12 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function environment_variables_preview(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC");
}
public function workdir()
@@ -1095,7 +1178,21 @@ class Service extends BaseModel
return 3;
});
foreach ($sorted as $env) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
if (version_compare($env->version, '4.0.0-beta.347', '<=')) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
} else {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
if ($env->is_literal || $env->is_multiline) {
$real_value = '\''.$real_value.'\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
}
}
$commands[] = "echo \"{$env->key}={$real_value}\" >> .env";
}
}
if ($sorted->count() === 0) {
$commands[] = 'touch .env';
@@ -1105,7 +1202,7 @@ class Service extends BaseModel
public function parse(bool $isNew = false): Collection
{
if ($this->compose_parsing_version === '3') {
if ((int) $this->compose_parsing_version >= 3) {
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew);
+5
View File
@@ -112,4 +112,9 @@ class ServiceApplication extends BaseModel
{
getFilesystemVolumesFromServer($this, $isInit);
}
public function isBackupSolutionAvailable()
{
return false;
}
}
+9
View File
@@ -115,4 +115,13 @@ class ServiceDatabase extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
public function isBackupSolutionAvailable()
{
return str($this->databaseType())->contains('mysql') ||
str($this->databaseType())->contains('postgres') ||
str($this->databaseType())->contains('postgis') ||
str($this->databaseType())->contains('mariadb') ||
str($this->databaseType())->contains('mongodb');
}
}
+5
View File
@@ -294,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}
+5
View File
@@ -294,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}
+5
View File
@@ -294,4 +294,9 @@ class StandaloneKeydb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}
+5
View File
@@ -294,4 +294,9 @@ class StandaloneMariadb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}
+5
View File
@@ -314,4 +314,9 @@ class StandaloneMongodb extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}
+5
View File
@@ -295,4 +295,9 @@ class StandaloneMysql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}
+5
View File
@@ -296,4 +296,9 @@ class StandalonePostgresql extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return true;
}
}
+5
View File
@@ -290,4 +290,9 @@ class StandaloneRedis extends BaseModel
return $parsedCollection->toArray();
}
}
public function isBackupSolutionAvailable()
{
return false;
}
}
@@ -13,7 +13,7 @@ class TransactionalEmailChannel
{
public function send(User $notifiable, Notification $notification): void
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
Log::info('SMTP/Resend not enabled');
@@ -18,7 +18,7 @@ class ResetPassword extends Notification
public function __construct($token)
{
$this->settings = \App\Models\InstanceSettings::get();
$this->settings = instanceSettings();
$this->token = $token;
}
-6
View File
@@ -2,10 +2,8 @@
namespace App\Providers;
use App\Models\InstanceSettings;
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
@@ -30,9 +28,5 @@ class AppServiceProvider extends ServiceProvider
])->baseUrl($api_url);
}
});
// if (! env('CI')) {
// View::share('instanceSettings', InstanceSettings::get());
// }
}
}
+2 -2
View File
@@ -46,7 +46,7 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::registerView(function () {
$isFirstUser = User::count() === 0;
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
}
@@ -60,7 +60,7 @@ class FortifyServiceProvider extends ServiceProvider
});
Fortify::loginView(function () {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {
+1
View File
@@ -175,4 +175,5 @@ function removeUnnecessaryFieldsFromRequest(Request $request)
$request->offsetUnset('instant_deploy');
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
$request->offsetUnset('use_build_server');
}
+6 -3
View File
@@ -20,12 +20,16 @@ const RESTART_MODE = 'unless-stopped';
const DATABASE_DOCKER_IMAGES = [
'bitnami/mariadb',
'bitnami/mongodb',
'bitnami/mysql',
'bitnami/postgresql',
'bitnami/redis',
'mysql',
'bitnami/mysql',
'mysql/mysql-server',
'mariadb',
'postgis/postgis',
'postgres',
'bitnami/postgresql',
'supabase/postgres',
'elestio/postgres',
'mongo',
'redis',
'memcached',
@@ -33,7 +37,6 @@ const DATABASE_DOCKER_IMAGES = [
'neo4j',
'influxdb',
'clickhouse/clickhouse-server',
'supabase/postgres',
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',
+24 -57
View File
@@ -325,38 +325,16 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push('traefik.http.middlewares.gzip.compress=true');
$labels->push('traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https');
$basic_auth = false;
$basic_auth_middleware = null;
$redirect = false;
$redirect_middleware = null;
$middlewares_from_labels = collect([]);
if ($serviceLabels) {
$basic_auth = $serviceLabels->contains(function ($value) {
return str_contains($value, 'basicauth');
});
if ($basic_auth) {
$basic_auth_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$redirect = $serviceLabels->contains(function ($value) {
return str_contains($value, 'redirectregex');
});
if ($redirect) {
$redirect_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$middlewares_from_labels = $serviceLabels->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)(\.|$)/', $item, $matches)) {
return $matches[1];
}
return null;
})->filter()
->unique();
}
foreach ($domains as $loop => $domain) {
try {
@@ -404,20 +382,15 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
// Middleware handling
$middlewares = collect([]);
if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) {
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -425,10 +398,13 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_non_www);
$middlewares->push($to_non_www_name);
}
if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) {
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -437,13 +413,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$middlewares = collect([]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -455,6 +425,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@@ -490,12 +463,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -507,6 +474,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@@ -516,12 +486,6 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
@@ -533,6 +497,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels = $labels->merge($redirect_to_www);
$middlewares->push($to_www_name);
}
$middlewares_from_labels->each(function ($middleware_name) use ($middlewares) {
$middlewares->push($middleware_name);
});
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
+2 -5
View File
@@ -1,14 +1,11 @@
<?php
use App\Models\S3Storage;
use Illuminate\Support\Str;
function set_s3_target(S3Storage $s3)
{
$is_digital_ocean = false;
if ($s3->endpoint) {
$is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com');
}
config()->set('filesystems.disks.custom-s3', [
'driver' => 's3',
'region' => $s3['region'],
@@ -17,7 +14,7 @@ function set_s3_target(S3Storage $s3)
'bucket' => $s3['bucket'],
'endpoint' => $s3['endpoint'],
'use_path_style_endpoint' => true,
'bucket_endpoint' => $is_digital_ocean,
'bucket_endpoint' => $s3->isHetzner() || $s3->isDigitalOcean(),
'aws_url' => $s3->awsUrl(),
]);
}
+164 -29
View File
@@ -247,7 +247,7 @@ function is_transactional_emails_active(): bool
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
@@ -281,7 +281,7 @@ function base_ip(): string
if (isDev()) {
return 'localhost';
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->public_ipv4) {
return "$settings->public_ipv4";
}
@@ -309,7 +309,7 @@ function getFqdnWithoutPort(string $fqdn)
*/
function base_url(bool $withPort = true): string
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if ($settings->fqdn) {
return $settings->fqdn;
}
@@ -343,6 +343,11 @@ function isSubscribed()
{
return isSubscriptionActive() || auth()->user()->isInstanceAdmin();
}
function isProduction(): bool
{
return ! isDev();
}
function isDev(): bool
{
return config('app.env') === 'local';
@@ -384,7 +389,7 @@ function send_internal_notification(string $message): void
}
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
{
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$type = set_transanctional_email_settings($settings);
if (! $type) {
throw new Exception('No email settings found.');
@@ -703,7 +708,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -753,7 +760,9 @@ function getTopLevelNetworks(Service|Application $resource)
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -819,6 +828,31 @@ function convertToArray($collection)
return $collection;
}
function parseCommandFromMagicEnvVariable(Str|string $key): Stringable
{
$value = str($key);
$count = substr_count($value->value(), '_');
if ($count === 2) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
} else {
// SERVICE_BASE64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
if ($count === 3) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI_1000
$command = $value->after('SERVICE_')->before('_');
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
return str($command);
}
function parseEnvVariable(Str|string $value)
{
$value = str($value);
@@ -850,6 +884,7 @@ function parseEnvVariable(Str|string $value)
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
ray($command);
}
}
}
@@ -970,7 +1005,7 @@ function validate_dns_entry(string $fqdn, Server $server)
if (str($host)->contains('sslip.io')) {
return true;
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) {
return true;
@@ -1090,7 +1125,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId =
if ($domainFound) {
return true;
}
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1162,7 +1197,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
}
if ($resource) {
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -1179,12 +1214,26 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
function parseCommandsByLineForSudo(Collection $commands, Server $server): array
{
$commands = $commands->map(function ($line) {
if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) {
if (
! str(trim($line))->startsWith([
'cd',
'command',
'echo',
'true',
'if',
'fi',
])
) {
return "sudo $line";
}
if (str(trim($line))->startsWith('if')) {
return str_replace('if', 'if sudo', $line);
}
return $line;
});
$commands = $commands->map(function ($line) use ($server) {
if (Str::startsWith($line, 'sudo mkdir -p')) {
return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p');
@@ -1192,6 +1241,7 @@ function parseCommandsByLineForSudo(Collection $commands, Server $server): array
return $line;
});
$commands = $commands->map(function ($line) {
$line = str($line);
if (str($line)->contains('$(')) {
@@ -1588,7 +1638,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2503,7 +2555,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$topLevelNetworks->put($networkDetails, null);
if (is_string($networkDetails) || is_int($networkDetails)) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
@@ -2964,11 +3018,22 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3078,7 +3143,7 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
foreach ($magicEnvironments as $key => $value) {
$key = str($key);
$value = replaceVariables($value);
$command = $key->after('SERVICE_')->before('_');
$command = parseCommandFromMagicEnvVariable($key);
$found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first();
if ($found) {
continue;
@@ -3189,12 +3254,24 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
$savedService = ServiceDatabase::firstOrCreate([
'name' => $applicationFound->name,
'image' => $applicationFound->image,
'service_id' => $applicationFound->service_id,
]);
$applicationFound->delete();
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
@@ -3264,7 +3341,15 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
} elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') {
$volume = $source->value().':'.$target->value();
} else {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
} elseif ($isService) {
$mainDirectory = str(base_configuration_dir().'/services/'.$uuid);
}
} else {
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
}
$source = replaceLocalSource($source, $mainDirectory);
if ($isApplication && $isPullRequest) {
$source = $source."-pr-$pullRequestId";
@@ -3284,6 +3369,17 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
'resource_type' => get_class($originalResource),
]
);
if (isDev()) {
if ((int) $resource->compose_parsing_version >= 4) {
if ($isApplication) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
} elseif ($isService) {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/services/'.$uuid);
}
} else {
$source = $source->replace($mainDirectory, '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/applications/'.$uuid);
}
}
$volume = "$source:$target";
}
} elseif ($type->value() === 'volume') {
@@ -3606,6 +3702,18 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
});
}
$serviceLabels = $labels->merge($defaultLabels);
if ($serviceLabels->count() > 0) {
if ($isApplication) {
$isContainerLabelEscapeEnabled = data_get($resource, 'settings.is_container_label_escape_enabled');
} else {
$isContainerLabelEscapeEnabled = data_get($resource, 'is_container_label_escape_enabled');
}
if ($isContainerLabelEscapeEnabled) {
$serviceLabels = $serviceLabels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
}
if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) {
if ($isApplication) {
$shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels;
@@ -3826,14 +3934,37 @@ function convertComposeEnvironmentToArray($environment)
{
$convertedServiceVariables = collect([]);
if (isAssociativeArray($environment)) {
// Example: $environment = ['FOO' => 'bar', 'BAZ' => 'qux'];
if ($environment instanceof Collection) {
$changedEnvironment = collect([]);
$environment->each(function ($value, $key) use ($changedEnvironment) {
if (is_numeric($key)) {
$parts = explode('=', $value, 2);
if (count($parts) === 2) {
$key = $parts[0];
$realValue = $parts[1] ?? '';
$changedEnvironment->put($key, $realValue);
} else {
$changedEnvironment->put($key, $value);
}
} else {
$changedEnvironment->put($key, $value);
}
});
return $changedEnvironment;
}
$convertedServiceVariables = $environment;
} else {
// Example: $environment = ['FOO=bar', 'BAZ=qux'];
foreach ($environment as $value) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
if (is_string($value)) {
$parts = explode('=', $value, 2);
$key = $parts[0];
$realValue = $parts[1] ?? '';
if ($key) {
$convertedServiceVariables->put($key, $realValue);
}
}
}
}
@@ -3841,3 +3972,7 @@ function convertComposeEnvironmentToArray($environment)
return $convertedServiceVariables;
}
function instanceSettings()
{
return InstanceSettings::get();
}
+1
View File
@@ -48,6 +48,7 @@
"zircote/swagger-php": "^4.10"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.13",
"fakerphp/faker": "^v1.21.0",
"laravel/dusk": "^v8.0",
"laravel/pint": "^1.16",
Generated
+153 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "96f8146407d0e6e897ff097c5eccd3a4",
"content-hash": "42c28ab141b70fcabf75b51afa96c670",
"packages": [
{
"name": "amphp/amp",
@@ -11823,6 +11823,90 @@
}
],
"packages-dev": [
{
"name": "barryvdh/laravel-debugbar",
"version": "v3.13.5",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"reference": "92d86be45ee54edff735e46856f64f14b6a8bb07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07",
"reference": "92d86be45ee54edff735e46856f64f14b6a8bb07",
"shasum": ""
},
"require": {
"illuminate/routing": "^9|^10|^11",
"illuminate/session": "^9|^10|^11",
"illuminate/support": "^9|^10|^11",
"maximebf/debugbar": "~1.22.0",
"php": "^8.0",
"symfony/finder": "^6|^7"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"orchestra/testbench-dusk": "^5|^6|^7|^8|^9",
"phpunit/phpunit": "^9.6|^10.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.13-dev"
},
"laravel": {
"providers": [
"Barryvdh\\Debugbar\\ServiceProvider"
],
"aliases": {
"Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar"
}
}
},
"autoload": {
"files": [
"src/helpers.php"
],
"psr-4": {
"Barryvdh\\Debugbar\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "PHP Debugbar integration for Laravel",
"keywords": [
"debug",
"debugbar",
"laravel",
"profiler",
"webprofiler"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2024-04-12T11:20:37+00:00"
},
{
"name": "brianium/paratest",
"version": "v7.4.3",
@@ -12301,6 +12385,74 @@
},
"time": "2024-09-03T15:00:28+00:00"
},
{
"name": "maximebf/debugbar",
"version": "v1.22.5",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
"reference": "1b5cabe0ce013134cf595bfa427bbf2f6abcd989",
"shasum": ""
},
"require": {
"php": "^7.2|^8",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7"
},
"require-dev": {
"dbrekelmans/bdi": "^1",
"phpunit/phpunit": "^8|^9",
"symfony/panther": "^1|^2.1",
"twig/twig": "^1.38|^2.7|^3.0"
},
"suggest": {
"kriswallsmith/assetic": "The best way to manage assets",
"monolog/monolog": "Log using Monolog",
"predis/predis": "Redis storage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.22-dev"
}
},
"autoload": {
"psr-4": {
"DebugBar\\": "src/DebugBar/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maxime Bouroumeau-Fuseau",
"email": "maxime.bouroumeau@gmail.com",
"homepage": "http://maximebf.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Debug bar in the browser for php application",
"homepage": "https://github.com/maximebf/php-debugbar",
"keywords": [
"debug",
"debugbar"
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.22.5"
},
"time": "2024-09-09T08:05:55+00:00"
},
{
"name": "mockery/mockery",
"version": "1.6.12",
+325
View File
@@ -0,0 +1,325 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Debugbar Settings
|--------------------------------------------------------------------------
|
| Debugbar is enabled by default, when debug is set to true in app.php.
| You can override the value by setting enable to true or false instead of null.
|
| You can provide an array of URI's that must be ignored (eg. 'api/*')
|
*/
'enabled' => env('DEBUGBAR_ENABLED', null),
'except' => [
'telescope*',
'horizon*',
],
/*
|--------------------------------------------------------------------------
| Storage settings
|--------------------------------------------------------------------------
|
| DebugBar stores data for session/ajax requests.
| You can disable this, so the debugbar stores data in headers/session,
| but this can cause problems with large data collectors.
| By default, file storage (in the storage folder) is used. Redis and PDO
| can also be used. For PDO, run the package migrations first.
|
| Warning: Enabling storage.open will allow everyone to access previous
| request, do not enable open storage in publicly available environments!
| Specify a callback if you want to limit based on IP or authentication.
| Leaving it to null will allow localhost only.
*/
'storage' => [
'enabled' => true,
'open' => env('DEBUGBAR_OPEN_STORAGE'), // bool/callback.
'driver' => 'file', // redis, file, pdo, socket, custom
'path' => storage_path('debugbar'), // For file driver
'connection' => null, // Leave null for default connection (Redis/PDO)
'provider' => '', // Instance of StorageInterface for custom driver
'hostname' => '127.0.0.1', // Hostname to use with the "socket" driver
'port' => 2304, // Port to use with the "socket" driver
],
/*
|--------------------------------------------------------------------------
| Editor
|--------------------------------------------------------------------------
|
| Choose your preferred editor to use when clicking file name.
|
| Supported: "phpstorm", "vscode", "vscode-insiders", "vscode-remote",
| "vscode-insiders-remote", "vscodium", "textmate", "emacs",
| "sublime", "atom", "nova", "macvim", "idea", "netbeans",
| "xdebug", "espresso"
|
*/
'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------
| Remote Path Mapping
|--------------------------------------------------------------------------
|
| If you are using a remote dev server, like Laravel Homestead, Docker, or
| even a remote VPS, it will be necessary to specify your path mapping.
|
| Leaving one, or both of these, empty or null will not trigger the remote
| URL changes and Debugbar will treat your editor links as local files.
|
| "remote_sites_path" is an absolute base path for your sites or projects
| in Homestead, Vagrant, Docker, or another remote development server.
|
| Example value: "/home/vagrant/Code"
|
| "local_sites_path" is an absolute base path for your sites or projects
| on your local computer where your IDE or code editor is running on.
|
| Example values: "/Users/<name>/Code", "C:\Users\<name>\Documents\Code"
|
*/
'remote_sites_path' => env('DEBUGBAR_REMOTE_SITES_PATH'),
'local_sites_path' => env('DEBUGBAR_LOCAL_SITES_PATH', env('IGNITION_LOCAL_SITES_PATH')),
/*
|--------------------------------------------------------------------------
| Vendors
|--------------------------------------------------------------------------
|
| Vendor files are included by default, but can be set to false.
| This can also be set to 'js' or 'css', to only include javascript or css vendor files.
| Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
| and for js: jquery and highlight.js
| So if you want syntax highlighting, set it to true.
| jQuery is set to not conflict with existing jQuery scripts.
|
*/
'include_vendors' => true,
/*
|--------------------------------------------------------------------------
| Capture Ajax Requests
|--------------------------------------------------------------------------
|
| The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
| you can use this option to disable sending the data through the headers.
|
| Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools.
|
| Note for your request to be identified as ajax requests they must either send the header
| X-Requested-With with the value XMLHttpRequest (most JS libraries send this), or have application/json as a Accept header.
|
| By default `ajax_handler_auto_show` is set to true allowing ajax requests to be shown automatically in the Debugbar.
| Changing `ajax_handler_auto_show` to false will prevent the Debugbar from reloading.
*/
'capture_ajax' => true,
'add_ajax_timing' => false,
'ajax_handler_auto_show' => true,
'ajax_handler_enable_tab' => true,
/*
|--------------------------------------------------------------------------
| Custom Error Handler for Deprecated warnings
|--------------------------------------------------------------------------
|
| When enabled, the Debugbar shows deprecated warnings for Symfony components
| in the Messages tab.
|
*/
'error_handler' => false,
/*
|--------------------------------------------------------------------------
| Clockwork integration
|--------------------------------------------------------------------------
|
| The Debugbar can emulate the Clockwork headers, so you can use the Chrome
| Extension, without the server-side code. It uses Debugbar collectors instead.
|
*/
'clockwork' => false,
/*
|--------------------------------------------------------------------------
| DataCollectors
|--------------------------------------------------------------------------
|
| Enable/disable DataCollectors
|
*/
'collectors' => [
'phpinfo' => true, // Php version
'messages' => true, // Messages
'time' => true, // Time Datalogger
'memory' => true, // Memory usage
'exceptions' => true, // Exception displayer
'log' => true, // Logs from Monolog (merged in messages if enabled)
'db' => true, // Show database (PDO) queries and bindings
'views' => true, // Views with their data
'route' => true, // Current route information
'auth' => false, // Display Laravel authentication status
'gate' => true, // Display Laravel Gate checks
'session' => true, // Display session data
'symfony_request' => true, // Only one can be enabled..
'mail' => true, // Catch mail messages
'laravel' => false, // Laravel version and environment
'events' => false, // All events fired
'default_request' => false, // Regular or special Symfony request logger
'logs' => false, // Add the latest log messages
'files' => false, // Show the included files
'config' => false, // Display config settings
'cache' => false, // Display cache events
'models' => true, // Display models
'livewire' => true, // Display Livewire (when available)
'jobs' => false, // Display dispatched jobs
],
/*
|--------------------------------------------------------------------------
| Extra options
|--------------------------------------------------------------------------
|
| Configure some DataCollectors
|
*/
'options' => [
'time' => [
'memory_usage' => false, // Calculated by subtracting memory start and end, it may be inaccurate
],
'messages' => [
'trace' => true, // Trace the origin of the debug message
],
'memory' => [
'reset_peak' => false, // run memory_reset_peak_usage before collecting
'with_baseline' => false, // Set boot memory usage as memory peak baseline
'precision' => 0, // Memory rounding precision
],
'auth' => [
'show_name' => true, // Also show the users name/email in the debugbar
'show_guards' => true, // Show the guards that are used
],
'db' => [
'with_params' => true, // Render SQL with the parameters substituted
'backtrace' => true, // Use a backtrace to find the origin of the query in your files.
'backtrace_exclude_paths' => [], // Paths to exclude from backtrace. (in addition to defaults)
'timeline' => false, // Add the queries to the timeline
'duration_background' => true, // Show shaded background on each query relative to how long it took to execute.
'explain' => [ // Show EXPLAIN output on queries
'enabled' => false,
'types' => ['SELECT'], // Deprecated setting, is always only SELECT
],
'hints' => false, // Show hints for common mistakes
'show_copy' => false, // Show copy button next to the query,
'slow_threshold' => false, // Only track queries that last longer than this time in ms
'memory_usage' => false, // Show queries memory usage
'soft_limit' => 100, // After the soft limit, no parameters/backtrace are captured
'hard_limit' => 500, // After the hard limit, queries are ignored
],
'mail' => [
'timeline' => false, // Add mails to the timeline
'show_body' => true,
],
'views' => [
'timeline' => false, // Add the views to the timeline (Experimental)
'data' => false, //true for all data, 'keys' for only names, false for no parameters.
'group' => 50, // Group duplicate views. Pass value to auto-group, or true/false to force
'exclude_paths' => [ // Add the paths which you don't want to appear in the views
'vendor/filament', // Exclude Filament components by default
],
],
'route' => [
'label' => true, // show complete route on bar
],
'session' => [
'hiddens' => [], // hides sensitive values using array paths
],
'symfony_request' => [
'hiddens' => [], // hides sensitive values using array paths, example: request_request.password
],
'events' => [
'data' => false, // collect events data, listeners
],
'logs' => [
'file' => null,
],
'cache' => [
'values' => true, // collect cache values
],
],
/*
|--------------------------------------------------------------------------
| Inject Debugbar in Response
|--------------------------------------------------------------------------
|
| Usually, the debugbar is added just before </body>, by listening to the
| Response after the App is done. If you disable this, you have to add them
| in your template yourself. See http://phpdebugbar.com/docs/rendering.html
|
*/
'inject' => true,
/*
|--------------------------------------------------------------------------
| DebugBar route prefix
|--------------------------------------------------------------------------
|
| Sometimes you want to set route prefix to be used by DebugBar to load
| its resources from. Usually the need comes from misconfigured web server or
| from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97
|
*/
'route_prefix' => '_debugbar',
/*
|--------------------------------------------------------------------------
| DebugBar route middleware
|--------------------------------------------------------------------------
|
| Additional middleware to run on the Debugbar routes
*/
'route_middleware' => [],
/*
|--------------------------------------------------------------------------
| DebugBar route domain
|--------------------------------------------------------------------------
|
| By default DebugBar route served from the same domain that request served.
| To override default domain, specify it as a non-empty value.
*/
'route_domain' => null,
/*
|--------------------------------------------------------------------------
| DebugBar theme
|--------------------------------------------------------------------------
|
| Switches between light and dark theme. If set to auto it will respect system preferences
| Possible values: auto, light, dark
*/
'theme' => env('DEBUGBAR_THEME', 'auto'),
/*
|--------------------------------------------------------------------------
| Backtrace stack limit
|--------------------------------------------------------------------------
|
| By default, the DebugBar limits the number of frames returned by the 'debug_backtrace()' function.
| If you need larger stacktraces, you can increase this number. Setting it to 0 will result in no limit.
*/
'debug_backtrace_limit' => 50,
];
+1 -1
View File
@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.348',
'release' => '4.0.0-beta.356',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
+1 -1
View File
@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.348';
return '4.0.0-beta.356';
@@ -163,7 +163,7 @@ return new class extends Migration
$table->schemalessAttributes('smtp');
});
$instance_setting = InstanceSettings::get();
$instance_setting = instanceSettings();
$instance_setting->smtp = [
'enabled' => $instance_setting->smtp_enabled,
'from_address' => $instance_setting->smtp_from_address,
@@ -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('scheduled_database_backups', function (Blueprint $table) {
$table->boolean('dump_all')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('scheduled_database_backups', function (Blueprint $table) {
$table->dropColumn('dump_all');
});
}
};
+2 -2
View File
@@ -27,14 +27,14 @@ class InstanceSettingsSeeder extends Seeder
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
$ipv4 = trim($ipv4);
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (is_null($settings->public_ipv4) && $ipv4) {
$settings->update(['public_ipv4' => $ipv4]);
}
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
$ipv6 = trim($ipv6);
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
$settings = \App\Models\InstanceSettings::get();
$settings = instanceSettings();
if (is_null($settings->public_ipv6) && $ipv6) {
$settings->update(['public_ipv6' => $ipv6]);
}
+1
View File
@@ -58,6 +58,7 @@ services:
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"]
vite:
image: node:20
pull_policy: always
+1 -1
View File
@@ -113,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
+19 -1
View File
@@ -1,9 +1,27 @@
FROM quay.io/soketi/soketi:1.6-16-alpine
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.4.1
WORKDIR /terminal
RUN apk add --no-cache openssh-client make g++ python3
RUN apk add --no-cache openssh-client make g++ python3 curl
COPY docker/coolify-realtime/package.json ./
RUN npm i
RUN npm rebuild node-pty --update-binary
COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh
COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
echo 'amd64' && \
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
echo 'arm64' && \
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
;fi"
ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"]
+9 -1
View File
@@ -1,11 +1,19 @@
#!/bin/sh
# Function to timestamp logs
# Check if the first argument is 'watch'
if [ "$1" = "watch" ]; then
WATCH_MODE="--watch"
else
WATCH_MODE=""
fi
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
# Start the terminal server in the background with logging
node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
node $WATCH_MODE /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 &
TERMINAL_PID=$!
# Start the Soketi process in the background with logging
+15 -10
View File
@@ -61,9 +61,13 @@ wss.on('connection', (ws) => {
const userSession = { ws, userId, ptyProcess: null, isActive: false };
userSessions.set(userId, userSession);
ws.on('message', (message) => handleMessage(userSession, message));
ws.on('message', (message) => {
handleMessage(userSession, message);
});
ws.on('error', (err) => handleError(err, userId));
ws.on('close', () => handleClose(userId));
});
const messageHandlers = {
@@ -108,7 +112,6 @@ function parseMessage(message) {
async function handleCommand(ws, command, userId) {
const userSession = userSessions.get(userId);
if (userSession && userSession.isActive) {
const result = await killPtyProcess(userId);
if (!result) {
@@ -127,27 +130,29 @@ async function handleCommand(ws, command, userId) {
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: {},
};
// NOTE: - Initiates a process within the Terminal container
// Establishes an SSH connection to root@coolify with RequestTTY enabled
// Executes the 'docker exec' command to connect to a specific container
// If the user types 'exit', it terminates the container connection and reverts to the server.
const ptyProcess = pty.spawn('ssh', sshArgs.concat(['bash']), options);
const ptyProcess = pty.spawn('ssh', sshArgs.concat([hereDocContent]), options);
userSession.ptyProcess = ptyProcess;
userSession.isActive = true;
ptyProcess.write(hereDocContent + '\n');
// clear the terminal if the user has clear command
ptyProcess.write('command -v clear >/dev/null 2>&1 && clear\n');
ws.send('pty-ready');
ptyProcess.onData((data) => ws.send(data));
ptyProcess.onData((data) => {
ws.send(data);
});
// when parent closes
ptyProcess.onExit(({ exitCode, signal }) => {
console.error(`Process exited with code ${exitCode} and signal ${signal}`);
ws.send('pty-exited');
userSession.isActive = false;
});
if (timeout) {
@@ -181,7 +186,7 @@ async function killPtyProcess(userId) {
// session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098
// patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947
session.ptyProcess.write('kill -TERM -$$ && exit\n');
session.ptyProcess.write('set +o history\nkill -TERM -$$ && exit\nset -o history\n');
setTimeout(() => {
if (!session.isActive || !session.ptyProcess) {
@@ -230,5 +235,5 @@ function extractHereDocContent(commandString) {
}
server.listen(6002, () => {
console.log('Server listening on port 6002');
console.log('Coolify realtime terminal server listening on port 6002. Let the hacking begin!');
});
+162 -4
View File
@@ -236,6 +236,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -457,6 +461,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -678,6 +686,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -850,6 +862,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1013,6 +1029,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1067,6 +1087,10 @@ paths:
instant_deploy:
type: boolean
description: 'The flag to indicate if the application should be deployed instantly.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1126,9 +1150,33 @@ paths:
type: string
format: uuid
-
name: cleanup
name: delete_configurations
in: query
description: 'Delete configurations and volumes.'
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
@@ -1351,6 +1399,10 @@ paths:
watch_paths:
type: string
description: 'The watch paths.'
use_build_server:
type: boolean
nullable: true
description: 'Use build server.'
type: object
responses:
'200':
@@ -1738,6 +1790,52 @@ paths:
security:
-
bearerAuth: []
'/applications/{uuid}/execute':
post:
tags:
- Applications
summary: 'Execute Command'
description: "Execute a command on the application's current container."
operationId: execute-command-application
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
format: uuid
requestBody:
description: 'Command to execute.'
required: true
content:
application/json:
schema:
properties:
command:
type: string
description: 'Command to execute.'
type: object
responses:
'200':
description: "Execute a command on the application's current container."
content:
application/json:
schema:
properties:
message: { type: string, example: 'Command executed.' }
response: { type: string }
type: object
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/databases:
get:
tags:
@@ -1809,9 +1907,33 @@ paths:
type: string
format: uuid
-
name: cleanup
name: delete_configurations
in: query
description: 'Delete configurations and volumes.'
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
@@ -3812,6 +3934,38 @@ paths:
required: true
schema:
type: string
-
name: delete_configurations
in: query
description: 'Delete configurations.'
required: false
schema:
type: boolean
default: true
-
name: delete_volumes
in: query
description: 'Delete volumes.'
required: false
schema:
type: boolean
default: true
-
name: docker_cleanup
in: query
description: 'Run docker cleanup.'
required: false
schema:
type: boolean
default: true
-
name: delete_connected_networks
in: query
description: 'Delete connected networks.'
required: false
schema:
type: boolean
default: true
responses:
'200':
description: 'Delete a service by UUID'
@@ -4769,6 +4923,10 @@ components:
type: boolean
swarm_cluster:
type: string
delete_unused_volumes:
type: boolean
delete_unused_networks:
type: boolean
type: object
ServerSetting:
description: 'Server Settings model'
+4 -1
View File
@@ -46,6 +46,9 @@ services:
- PUSHER_APP_ID
- PUSHER_APP_KEY
- PUSHER_APP_SECRET
- TERMINAL_PROTOCOL
- TERMINAL_HOST
- TERMINAL_PORT
- AUTOUPDATE
- SELF_HOSTED
- SSH_MUX_ENABLED
@@ -110,7 +113,7 @@ services:
retries: 10
timeout: 2s
soketi:
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1'
image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3'
ports:
- "${SOKETI_PORT:-6001}:6001"
- "6002:6002"
+4 -4
View File
@@ -1,16 +1,16 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.347"
"version": "4.0.0-beta.354"
},
"nightly": {
"version": "4.0.0-beta.348"
"version": "4.0.0-beta.355"
},
"helper": {
"version": "1.0.1"
"version": "1.0.2"
},
"realtime": {
"version": "1.0.1"
"version": "1.0.3"
}
}
}
+166
View File
@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 245.00 245.00">
<path stroke="#919393" stroke-width="2.00" fill="none" stroke-linecap="butt" vector-effect="non-scaling-stroke" d="
M 187.03 159.00
L 161.49 159.00
A 1.24 1.24 0.0 0 1 160.48 158.48
Q 153.96 149.33 147.89 140.81
C 145.78 137.86 141.80 138.01 139.49 140.67
C 136.43 144.17 133.65 147.56 136.44 151.42
Q 142.22 159.39 148.22 167.98
Q 152.74 174.47 160.49 174.57
Q 173.94 174.73 188.79 174.60
C 196.64 174.52 203.24 168.72 203.23 160.63
Q 203.16 123.84 203.20 83.74
C 203.21 78.41 199.97 73.56 195.17 71.35
Q 191.90 69.85 186.23 69.92
Q 173.94 70.08 161.37 69.94
C 156.39 69.89 152.10 71.63 149.08 75.49
Q 117.03 116.46 84.45 158.09
A 2.37 2.35 -70.9 0 1 82.59 159.00
L 57.21 159.00
Q 56.65 159.00 56.65 158.45
L 56.66 85.95
Q 56.66 85.31 57.30 85.31
L 83.00 85.31
A 0.80 0.79 71.0 0 1 83.63 85.62
Q 90.51 94.60 97.61 104.71
C 99.47 107.36 103.07 108.21 105.48 105.83
Q 107.60 103.72 109.70 100.67
C 110.60 99.36 110.38 97.79 110.28 96.30
Q 110.23 95.62 109.82 95.07
Q 103.03 85.94 96.91 77.34
C 93.63 72.73 89.66 69.98 84.18 70.00
Q 71.60 70.05 56.56 69.96
C 50.37 69.93 45.44 72.61 42.63 78.14
Q 41.19 80.99 41.25 87.27
Q 41.55 120.87 41.27 158.11
Q 41.23 162.77 42.32 165.57
Q 44.32 170.75 49.37 173.22
Q 52.63 174.81 60.03 174.72
Q 72.58 174.56 82.84 174.65
Q 91.13 174.73 95.47 169.18
Q 127.35 128.40 160.51 86.00
A 1.81 1.80 18.9 0 1 161.93 85.31
L 187.42 85.31
A 0.40 0.40 0.0 0 1 187.82 85.71
L 187.82 158.21
Q 187.82 159.00 187.03 159.00"
/>
<path fill="#222627" d="
M 46.00 0.00
L 198.80 0.00
Q 221.03 2.81 233.86 19.40
Q 244.84 33.61 244.80 52.00
Q 244.65 120.20 244.83 188.95
Q 244.86 199.26 243.39 205.30
Q 241.12 214.72 235.86 222.46
C 234.56 224.37 232.98 226.08 231.56 227.86
Q 229.92 229.91 227.88 231.54
C 226.00 233.04 224.26 234.66 222.24 236.01
Q 214.15 241.39 204.78 243.49
Q 198.73 244.86 188.67 244.83
Q 119.30 244.65 52.55 244.80
Q 32.84 244.84 18.81 233.38
Q 2.64 220.18 0.00 198.69
L 0.00 45.76
Q 1.42 35.66 4.33 29.79
Q 17.17 3.86 46.00 0.00
Z
M 187.03 159.00
L 161.49 159.00
A 1.24 1.24 0.0 0 1 160.48 158.48
Q 153.96 149.33 147.89 140.81
C 145.78 137.86 141.80 138.01 139.49 140.67
C 136.43 144.17 133.65 147.56 136.44 151.42
Q 142.22 159.39 148.22 167.98
Q 152.74 174.47 160.49 174.57
Q 173.94 174.73 188.79 174.60
C 196.64 174.52 203.24 168.72 203.23 160.63
Q 203.16 123.84 203.20 83.74
C 203.21 78.41 199.97 73.56 195.17 71.35
Q 191.90 69.85 186.23 69.92
Q 173.94 70.08 161.37 69.94
C 156.39 69.89 152.10 71.63 149.08 75.49
Q 117.03 116.46 84.45 158.09
A 2.37 2.35 -70.9 0 1 82.59 159.00
L 57.21 159.00
Q 56.65 159.00 56.65 158.45
L 56.66 85.95
Q 56.66 85.31 57.30 85.31
L 83.00 85.31
A 0.80 0.79 71.0 0 1 83.63 85.62
Q 90.51 94.60 97.61 104.71
C 99.47 107.36 103.07 108.21 105.48 105.83
Q 107.60 103.72 109.70 100.67
C 110.60 99.36 110.38 97.79 110.28 96.30
Q 110.23 95.62 109.82 95.07
Q 103.03 85.94 96.91 77.34
C 93.63 72.73 89.66 69.98 84.18 70.00
Q 71.60 70.05 56.56 69.96
C 50.37 69.93 45.44 72.61 42.63 78.14
Q 41.19 80.99 41.25 87.27
Q 41.55 120.87 41.27 158.11
Q 41.23 162.77 42.32 165.57
Q 44.32 170.75 49.37 173.22
Q 52.63 174.81 60.03 174.72
Q 72.58 174.56 82.84 174.65
Q 91.13 174.73 95.47 169.18
Q 127.35 128.40 160.51 86.00
A 1.81 1.80 18.9 0 1 161.93 85.31
L 187.42 85.31
A 0.40 0.40 0.0 0 1 187.82 85.71
L 187.82 158.21
Q 187.82 159.00 187.03 159.00
Z"
/>
<path fill="#ffffff" d="
M 187.82 158.21
L 187.82 85.71
A 0.40 0.40 0.0 0 0 187.42 85.31
L 161.93 85.31
A 1.81 1.80 18.9 0 0 160.51 86.00
Q 127.35 128.40 95.47 169.18
Q 91.13 174.73 82.84 174.65
Q 72.58 174.56 60.03 174.72
Q 52.63 174.81 49.37 173.22
Q 44.32 170.75 42.32 165.57
Q 41.23 162.77 41.27 158.11
Q 41.55 120.87 41.25 87.27
Q 41.19 80.99 42.63 78.14
C 45.44 72.61 50.37 69.93 56.56 69.96
Q 71.60 70.05 84.18 70.00
C 89.66 69.98 93.63 72.73 96.91 77.34
Q 103.03 85.94 109.82 95.07
Q 110.23 95.62 110.28 96.30
C 110.38 97.79 110.60 99.36 109.70 100.67
Q 107.60 103.72 105.48 105.83
C 103.07 108.21 99.47 107.36 97.61 104.71
Q 90.51 94.60 83.63 85.62
A 0.80 0.79 71.0 0 0 83.00 85.31
L 57.30 85.31
Q 56.66 85.31 56.66 85.95
L 56.65 158.45
Q 56.65 159.00 57.21 159.00
L 82.59 159.00
A 2.37 2.35 -70.9 0 0 84.45 158.09
Q 117.03 116.46 149.08 75.49
C 152.10 71.63 156.39 69.89 161.37 69.94
Q 173.94 70.08 186.23 69.92
Q 191.90 69.85 195.17 71.35
C 199.97 73.56 203.21 78.41 203.20 83.74
Q 203.16 123.84 203.23 160.63
C 203.24 168.72 196.64 174.52 188.79 174.60
Q 173.94 174.73 160.49 174.57
Q 152.74 174.47 148.22 167.98
Q 142.22 159.39 136.44 151.42
C 133.65 147.56 136.43 144.17 139.49 140.67
C 141.80 138.01 145.78 137.86 147.89 140.81
Q 153.96 149.33 160.48 158.48
A 1.24 1.24 0.0 0 0 161.49 159.00
L 187.03 159.00
Q 187.82 159.00 187.82 158.21
Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd"
viewBox="0 0 4091.27 4091.73"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_1421344023328">
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff. Show more