Merge branch 'next' into custom-traefik-middlewares
This commit is contained in:
103
.github/workflows/coolify-realtime-next.yml
vendored
Normal file
103
.github/workflows/coolify-realtime-next.yml
vendored
Normal 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 }}
|
||||
2
.github/workflows/coolify-realtime.yml
vendored
2
.github/workflows/coolify-realtime.yml
vendored
@@ -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
|
||||
|
||||
@@ -46,9 +46,6 @@ class StartDragonfly
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'ulimits' => [
|
||||
'memlock' => '-1',
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
50
app/Console/Commands/CheckApplicationDeploymentQueue.php
Normal file
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
app/Jobs/ServerStorageCheckJob.php
Normal file
59
app/Jobs/ServerStorageCheckJob.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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('.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
@@ -460,15 +460,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 +1203,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(':');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class Service extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '3';
|
||||
private static $parserVersion = '4';
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
@@ -770,6 +770,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();
|
||||
@@ -1095,7 +1119,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 +1143,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);
|
||||
|
||||
@@ -112,4 +112,9 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
getFilesystemVolumesFromServer($this, $isInit);
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneClickhouse extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneDragonfly extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneKeydb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,4 +294,9 @@ class StandaloneMariadb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,4 +314,9 @@ class StandaloneMongodb extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,4 +295,9 @@ class StandaloneMysql extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,4 +296,9 @@ class StandalonePostgresql extends BaseModel
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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('$(')) {
|
||||
@@ -1236,8 +1286,6 @@ function parseLineForSudo(string $command, Server $server): string
|
||||
function get_public_ips()
|
||||
{
|
||||
try {
|
||||
echo "Refreshing public ips!\n";
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
[$first, $second] = Process::concurrently(function (Pool $pool) {
|
||||
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
|
||||
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
|
||||
@@ -1251,7 +1299,7 @@ function get_public_ips()
|
||||
|
||||
return;
|
||||
}
|
||||
$settings->update(['public_ipv4' => $ipv4]);
|
||||
InstanceSettings::get()->update(['public_ipv4' => $ipv4]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
@@ -1266,7 +1314,7 @@ function get_public_ips()
|
||||
|
||||
return;
|
||||
}
|
||||
$settings->update(['public_ipv6' => $ipv6]);
|
||||
InstanceSettings::get()->update(['public_ipv6' => $ipv6]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
@@ -1590,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2505,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2966,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,
|
||||
@@ -3080,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;
|
||||
@@ -3191,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,
|
||||
@@ -3266,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";
|
||||
@@ -3286,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') {
|
||||
@@ -3828,14 +3922,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3843,3 +3960,7 @@ function convertComposeEnvironmentToArray($environment)
|
||||
return $convertedServiceVariables;
|
||||
|
||||
}
|
||||
function instanceSettings()
|
||||
{
|
||||
return InstanceSettings::get();
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
154
composer.lock
generated
154
composer.lock
generated
@@ -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
config/debugbar.php
Normal file
325
config/debugbar.php
Normal 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,
|
||||
];
|
||||
@@ -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.347',
|
||||
'release' => '4.0.0-beta.355',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.347';
|
||||
return '4.0.0-beta.355';
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ class PopulateSshKeysDirectorySeeder extends Seeder
|
||||
|
||||
PrivateKey::chunk(100, function ($keys) {
|
||||
foreach ($keys as $key) {
|
||||
echo 'Storing key: '.$key->name."\n";
|
||||
$key->storeInFileSystem();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -101,18 +101,13 @@ class ProductionSeeder extends Seeder
|
||||
}
|
||||
|
||||
if (! isCloud() && config('coolify.is_windows_docker_desktop') == false) {
|
||||
echo "Checking localhost key.\n";
|
||||
$coolify_key_name = '@host.docker.internal';
|
||||
$ssh_keys_directory = Storage::disk('ssh-keys')->files();
|
||||
$coolify_key = collect($ssh_keys_directory)->firstWhere(fn ($item) => str($item)->contains($coolify_key_name));
|
||||
|
||||
$found = PrivateKey::find(0);
|
||||
if ($found) {
|
||||
echo 'Private Key found in database.\n';
|
||||
if ($coolify_key) {
|
||||
echo "SSH key found for the Coolify host machine (localhost).\n";
|
||||
}
|
||||
} else {
|
||||
$server = Server::find(0);
|
||||
$found = $server->privateKey;
|
||||
if (! $found) {
|
||||
if ($coolify_key) {
|
||||
$user = str($coolify_key)->before('@')->after('id.');
|
||||
$coolify_key = Storage::disk('ssh-keys')->get($coolify_key);
|
||||
@@ -125,17 +120,7 @@ class ProductionSeeder extends Seeder
|
||||
]);
|
||||
$server->update(['user' => $user]);
|
||||
echo "SSH key found for the Coolify host machine (localhost).\n";
|
||||
|
||||
} else {
|
||||
PrivateKey::create(
|
||||
[
|
||||
'id' => 0,
|
||||
'team_id' => 0,
|
||||
'name' => 'localhost\'s key',
|
||||
'description' => 'The private key for the Coolify host machine (localhost).',
|
||||
'private_key' => 'Paste here you private key!!',
|
||||
]
|
||||
);
|
||||
echo "No SSH key found for the Coolify host machine (localhost).\n";
|
||||
echo "Please read the following documentation (point 3) to fix it: https://coolify.io/docs/knowledge-base/server/openssh/\n";
|
||||
echo "Your localhost connection won't work until then.";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!');
|
||||
});
|
||||
|
||||
166
openapi.yaml
166
openapi.yaml
@@ -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'
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
"realtime": {
|
||||
"version": "1.0.1"
|
||||
"version": "1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
public/svgs/bitcoin.svg
Normal file
15
public/svgs/bitcoin.svg
Normal 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 |
11
public/svgs/homarr.svg
Normal file
11
public/svgs/homarr.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="484px" height="329px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.947" fill="#f95251" d="M 157.5,-0.5 C 158.833,-0.5 160.167,-0.5 161.5,-0.5C 200.102,3.59921 222.268,24.9325 228,63.5C 228.667,88.5 228.667,113.5 228,138.5C 222.962,132.265 216.629,130.265 209,132.5C 208.667,109.167 208.333,85.8333 208,62.5C 202.332,36.3319 186.165,21.9986 159.5,19.5C 141.783,20.273 127.949,27.9397 118,42.5C 112.924,38.3791 107.757,34.3791 102.5,30.5C 115.97,11.5969 134.303,1.26361 157.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.947" fill="#f95251" d="M 321.5,-0.5 C 322.833,-0.5 324.167,-0.5 325.5,-0.5C 348.697,1.26361 367.03,11.5969 380.5,30.5C 375.243,34.3791 370.076,38.3791 365,42.5C 349.005,21.3908 328.505,15.2242 303.5,24C 287.107,31.7273 277.607,44.5606 275,62.5C 274.667,85.8333 274.333,109.167 274,132.5C 266.371,130.265 260.038,132.265 255,138.5C 254.333,113.5 254.333,88.5 255,63.5C 260.732,24.9325 282.898,3.59921 321.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.984" fill="#f95251" d="M -0.5,139.5 C -0.5,137.167 -0.5,134.833 -0.5,132.5C 19.5,132.5 39.5,132.5 59.5,132.5C 51.7127,98.1844 43.3793,64.0177 34.5,30C 83.3876,36.5512 118.554,62.0512 140,106.5C 159.513,155.397 153.846,201.064 123,243.5C 114.899,253.77 105.399,262.437 94.5,269.5C 90.7735,258.059 87.6068,246.392 85,234.5C 39.0428,218.384 10.5428,186.717 -0.5,139.5 Z"/></g>
|
||||
<g><path style="opacity:0.984" fill="#f95251" d="M 483.5,132.5 C 483.5,134.833 483.5,137.167 483.5,139.5C 472.457,186.717 443.957,218.384 398,234.5C 395.393,246.392 392.226,258.059 388.5,269.5C 351.514,243.037 332.514,206.871 331.5,161C 333.865,105.236 359.865,64.9024 409.5,40C 421.97,34.3953 434.97,31.0619 448.5,30C 439.621,64.0177 431.287,98.1844 423.5,132.5C 443.5,132.5 463.5,132.5 483.5,132.5 Z"/></g>
|
||||
<g><path style="opacity:0.95" fill="#f95251" d="M 211.5,170.5 C 225.127,170.958 231.627,177.958 231,191.5C 226.507,203.825 218.007,207.659 205.5,203C 197.535,196.871 195.369,189.037 199,179.5C 201.917,174.637 206.083,171.637 211.5,170.5 Z"/></g>
|
||||
<g><path style="opacity:0.949" fill="#f95251" d="M 265.5,170.5 C 280.848,170.68 287.348,178.347 285,193.5C 279.06,204.76 270.227,207.593 258.5,202C 249.176,192.625 249.176,183.292 258.5,174C 260.925,172.787 263.259,171.621 265.5,170.5 Z"/></g>
|
||||
<g><path style="opacity:0.987" fill="#f95251" d="M 388.5,328.5 C 386.5,328.5 384.5,328.5 382.5,328.5C 285.992,327.966 189.325,326.966 92.5,325.5C 120.96,261.579 170.294,227.913 240.5,224.5C 299.804,226.887 345.304,252.887 377,302.5C 381.676,310.846 385.509,319.513 388.5,328.5 Z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
6
public/svgs/it-tools.svg
Normal file
6
public/svgs/it-tools.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="512px" height="512px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.995" fill="#18a057" d="M 230.5,-0.5 C 247.5,-0.5 264.5,-0.5 281.5,-0.5C 290.12,3.28885 296.287,9.62218 300,18.5C 299.871,28.5889 300.704,38.4222 302.5,48C 326.717,53.6219 349.217,63.1219 370,76.5C 376.469,71.0323 382.636,65.199 388.5,59C 398.925,52.0844 409.591,51.7511 420.5,58C 432,68.1667 442.833,79 453,90.5C 459,100.5 459,110.5 453,120.5C 447.017,127.317 440.851,133.984 434.5,140.5C 447.998,161.821 457.498,184.821 463,209.5C 474.144,210.648 485.31,211.815 496.5,213C 503.496,216.822 508.496,222.322 511.5,229.5C 511.5,246.5 511.5,263.5 511.5,280.5C 508.841,288.664 503.508,294.497 495.5,298C 484.573,299.444 473.573,300.277 462.5,300.5C 457.369,325.4 448.036,348.566 434.5,370C 441.693,377.526 448.526,385.359 455,393.5C 458.667,402.825 458.001,411.825 453,420.5C 443.167,430.333 433.333,440.167 423.5,450C 414.895,457.565 405.229,459.232 394.5,455C 386.322,448.153 378.155,441.32 370,434.5C 348.501,447.828 325.334,457.161 300.5,462.5C 301.01,473.958 300.177,485.291 298,496.5C 294.006,504.342 287.839,509.342 279.5,511.5C 263.167,511.5 246.833,511.5 230.5,511.5C 222.336,508.841 216.503,503.508 213,495.5C 212,484.833 211,474.167 210,463.5C 198.505,459.749 187.005,455.915 175.5,452C 173.842,451.275 173.342,450.108 174,448.5C 192.195,429.971 210.695,411.805 229.5,394C 293.836,401.915 343.336,378.748 378,324.5C 408.025,263.569 400.691,207.569 356,156.5C 315.267,118.147 267.767,106.314 213.5,121C 164.374,138.458 132.874,172.291 119,222.5C 117.485,228.075 116.485,233.742 116,239.5C 115.261,253.906 115.594,268.239 117,282.5C 98.8333,300.667 80.6667,318.833 62.5,337C 61.552,337.483 60.552,337.649 59.5,337.5C 54.8294,325.486 51.1627,313.153 48.5,300.5C 37.4331,300.188 26.4331,299.355 15.5,298C 7.189,293.843 1.85567,287.343 -0.5,278.5C -0.5,262.833 -0.5,247.167 -0.5,231.5C 2.22646,223.578 7.22646,217.411 14.5,213C 25.7307,211.098 37.0641,210.265 48.5,210.5C 53.718,185.511 63.0513,162.178 76.5,140.5C 71.2189,133.716 65.3855,127.383 59,121.5C 51.8558,110.353 52.1892,99.353 60,88.5C 69.8333,78.6667 79.6667,68.8333 89.5,59C 98.3002,53.1199 107.633,52.1199 117.5,56C 125.629,62.4604 133.463,69.2938 141,76.5C 162.028,62.9274 184.861,53.4274 209.5,48C 210.93,37.2282 212.097,26.3949 213,15.5C 216.503,7.49214 222.336,2.15881 230.5,-0.5 Z"/></g>
|
||||
<g><path style="opacity:0.99" fill="#1d1d1d" d="M 52.5,511.5 C 46.5,511.5 40.5,511.5 34.5,511.5C 16.5,506.167 4.83333,494.5 -0.5,476.5C -0.5,470.167 -0.5,463.833 -0.5,457.5C 1.14258,451.876 3.64258,446.542 7,441.5C 55.9723,391.528 105.306,341.861 155,292.5C 141.187,245.876 152.02,206.042 187.5,173C 216.657,150.235 248.991,143.902 284.5,154C 289.202,158.922 290.702,164.755 289,171.5C 275,186.167 261,200.833 247,215.5C 234.564,236.922 238.73,254.422 259.5,268C 272.178,272.999 284.178,271.666 295.5,264C 309.596,248.899 324.596,234.899 340.5,222C 353.68,220.169 360.513,226.003 361,239.5C 365.554,290.573 345.387,328.073 300.5,352C 273.591,363.046 246.258,364.379 218.5,356C 169.472,405.361 120.139,454.361 70.5,503C 64.8398,506.712 58.8398,509.545 52.5,511.5 Z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
5
public/svgs/mailpit.svg
Normal file
5
public/svgs/mailpit.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="500" height="460" viewBox="0 0 132.292 121.708" version="1.1" id="svg6" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs10"/>
|
||||
<path d="M12.321 0l53.861 53.918L120.365 0zM5.155 9.025l60.842 59.673 61.211-59.489-.185 36.835L66.921 70.54l15.164 12.616-8.137 5.986-41.609.184c-4.838-.022-25.877-18.34-27.185-41.255z" fill-opacity=".941" fill="#2d4a5f" id="path2" style="fill:#ffffff;fill-opacity:1"/>
|
||||
<path d="M78.385 72.049l53.907-21.679-8.031 57.318-11.845-9.132c-21.727 23.171-45.255 26.289-67.997 20.837S12.281 98.39 5.155 83.8-.67 53.116 2.843 38.769c1.13 10.511-1.313 16.316 6.38 33.612 6.31 11.399 14.413 20.417 25.89 24.956 13.9 6.195 32.247 3.357 41.701-3.039l14.24-12.156z" fill="#00b786" id="path4"/>
|
||||
<script xmlns=""/></svg>
|
||||
|
After Width: | Height: | Size: 792 B |
5
public/svgs/mixpost.svg
Normal file
5
public/svgs/mixpost.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 170 170" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="27" y="25" width="100" height="100" rx="20" fill="#84E9F5"/>
|
||||
<rect x="45" y="44" width="100" height="100" rx="20" fill="#FFAB4C"/>
|
||||
<rect x="36" y="36" width="100" height="100" rx="20" fill="#4F46BB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 304 B |
8
public/svgs/strapi.svg
Normal file
8
public/svgs/strapi.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 22.1867C0 11.7278 0 6.49832 3.24916 3.24916C6.49832 0 11.7278 0 22.1867 0H41.8133C52.2722 0 57.5017 0 60.7508 3.24916C64 6.49832 64 11.7278 64 22.1867V41.8133C64 52.2722 64 57.5017 60.7508 60.7508C57.5017 64 52.2722 64 41.8133 64H22.1867C11.7278 64 6.49832 64 3.24916 60.7508C0 57.5017 0 52.2722 0 41.8133V22.1867Z" fill="#4945FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.156 19.4131H22.6094V30.4004H33.596V41.3864H44.5827V19.8398C44.5827 19.6041 44.3917 19.4131 44.156 19.4131Z" fill="white"/>
|
||||
<rect x="33.1719" y="30.4004" width="0.426667" height="0.426667" fill="white"/>
|
||||
<path d="M22.6172 30.4004H33.1772C33.4128 30.4004 33.6039 30.5914 33.6039 30.8271V41.3871H23.0439C22.8082 41.3871 22.6172 41.196 22.6172 40.9604V30.4004Z" fill="#9593FF"/>
|
||||
<path d="M33.6016 41.3867H44.5882L33.9657 52.0092C33.8314 52.1436 33.6016 52.0484 33.6016 51.8584V41.3867Z" fill="#9593FF"/>
|
||||
<path d="M22.6151 30.3998H12.1434C11.9534 30.3998 11.8582 30.17 11.9926 30.0356L22.6151 19.4131V30.3998Z" fill="#9593FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -16,6 +16,7 @@ export function initializeTerminalComponent() {
|
||||
paused: false,
|
||||
MAX_PENDING_WRITES: 5,
|
||||
keepAliveInterval: null,
|
||||
reconnectInterval: null,
|
||||
|
||||
init() {
|
||||
this.setupTerminal();
|
||||
@@ -48,6 +49,9 @@ export function initializeTerminalComponent() {
|
||||
document.addEventListener(event, () => {
|
||||
this.checkIfProcessIsRunningAndKillIt();
|
||||
clearInterval(this.keepAliveInterval);
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
@@ -103,11 +107,27 @@ export function initializeTerminalComponent() {
|
||||
};
|
||||
this.socket.onclose = () => {
|
||||
console.log('WebSocket connection closed');
|
||||
|
||||
this.reconnect();
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
reconnect() {
|
||||
if (this.reconnectInterval) {
|
||||
clearInterval(this.reconnectInterval);
|
||||
}
|
||||
this.reconnectInterval = setInterval(() => {
|
||||
console.log('Attempting to reconnect...');
|
||||
this.initializeWebSocket();
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
console.log('Reconnected successfully');
|
||||
clearInterval(this.reconnectInterval);
|
||||
this.reconnectInterval = null;
|
||||
window.location.reload();
|
||||
}
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
handleSocketMessage(event) {
|
||||
this.message = '(connection closed)';
|
||||
if (event.data === 'pty-ready') {
|
||||
@@ -125,6 +145,10 @@ export function initializeTerminalComponent() {
|
||||
if (this.term) this.term.reset();
|
||||
this.terminalActive = false;
|
||||
this.message = '(sorry, something went wrong, please try again)';
|
||||
} else if (event.data === 'pty-exited') {
|
||||
this.terminalActive = false;
|
||||
this.term.reset();
|
||||
this.commandBuffer = '';
|
||||
} else {
|
||||
this.pendingWrites++;
|
||||
this.term.write(event.data, this.flowControlCallback.bind(this));
|
||||
@@ -136,9 +160,12 @@ export function initializeTerminalComponent() {
|
||||
if (this.pendingWrites > this.MAX_PENDING_WRITES && !this.paused) {
|
||||
this.paused = true;
|
||||
this.socket.send(JSON.stringify({ pause: true }));
|
||||
} else if (this.pendingWrites <= this.MAX_PENDING_WRITES && this.paused) {
|
||||
return;
|
||||
}
|
||||
if (this.pendingWrites <= this.MAX_PENDING_WRITES && this.paused) {
|
||||
this.paused = false;
|
||||
this.socket.send(JSON.stringify({ resume: true }));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -147,15 +174,7 @@ export function initializeTerminalComponent() {
|
||||
|
||||
this.term.onData((data) => {
|
||||
this.socket.send(JSON.stringify({ message: data }));
|
||||
// Handle CTRL + D or exit command
|
||||
if (data === '\x04' || (data === '\r' && this.stripAnsiCommands(this.commandBuffer).trim().includes('exit'))) {
|
||||
this.checkIfProcessIsRunningAndKillIt();
|
||||
setTimeout(() => {
|
||||
this.terminalActive = false;
|
||||
this.term.reset();
|
||||
}, 500);
|
||||
this.commandBuffer = '';
|
||||
} else if (data === '\r') {
|
||||
if (data === '\r') {
|
||||
this.commandBuffer = '';
|
||||
} else {
|
||||
this.commandBuffer += data;
|
||||
@@ -183,10 +202,6 @@ export function initializeTerminalComponent() {
|
||||
});
|
||||
},
|
||||
|
||||
stripAnsiCommands(input) {
|
||||
return input.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
||||
},
|
||||
|
||||
keepAlive() {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.send(JSON.stringify({ ping: true }));
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="w-96">
|
||||
<form action="/user/confirm-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" autofocus />
|
||||
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
|
||||
<x-forms.button type="submit">{{ __('auth.confirm_password') }}</x-forms.button>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user