Merge branch 'next' into wrong-time-during-a-failed-deployment

This commit is contained in:
Andras Bacsai 2024-05-31 12:56:20 +02:00 committed by GitHub
commit dbb7989027
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
167 changed files with 5222 additions and 2470 deletions

View File

@ -26,7 +26,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
@ -47,7 +47,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64

View File

@ -29,7 +29,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
@ -51,7 +51,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
file: docker/prod-ssu/Dockerfile
file: docker/prod/Dockerfile
platforms: linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64

View File

@ -1,3 +1,6 @@
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
# About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
@ -39,9 +42,11 @@ ## Github Sponsors ($40+)
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
<a href="https://www.runpod.io/?utm_source=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>

View File

@ -60,7 +60,7 @@ public static function decodeOutput(?Activity $activity = null): string
$decoded = json_decode(
data_get($activity, 'description'),
associative: true,
flags: JSON_THROW_ON_ERROR
flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE
);
} catch (\JsonException $exception) {
return '';
@ -164,8 +164,7 @@ protected function elapsedTime(): int
public function encodeOutput($type, $output)
{
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
$outputStack[] = [
'type' => $type,
'output' => $output,
@ -174,12 +173,12 @@ public function encodeOutput($type, $output)
'order' => $this->getLatestCounter(),
];
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR);
return json_encode($outputStack, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
}
protected function getLatestCounter(): int
{
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR);
$description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
if ($description === null || count($description) === 0) {
return 1;
}

View File

@ -29,6 +29,7 @@ public function handle(StandaloneClickhouse $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
@ -93,6 +94,11 @@ public function handle(StandaloneClickhouse $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -32,6 +32,7 @@ public function handle(StandaloneDragonfly $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
@ -94,6 +95,11 @@ public function handle(StandaloneDragonfly $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -32,6 +32,7 @@ public function handle(StandaloneKeydb $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_keydb();
@ -92,6 +93,11 @@ public function handle(StandaloneKeydb $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -28,6 +28,7 @@ public function handle(StandaloneMariadb $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
@ -86,6 +87,11 @@ public function handle(StandaloneMariadb $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -30,6 +30,7 @@ public function handle(StandaloneMongodb $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mongo_conf();
@ -94,6 +95,11 @@ public function handle(StandaloneMongodb $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -28,6 +28,7 @@ public function handle(StandaloneMysql $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_mysql();
@ -86,6 +87,11 @@ public function handle(StandaloneMysql $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -29,6 +29,7 @@ public function handle(StandalonePostgresql $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->generate_init_scripts();
@ -92,6 +93,11 @@ public function handle(StandalonePostgresql $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -32,6 +32,7 @@ public function handle(StandaloneRedis $database)
];
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->database->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
$environment_variables = $this->generate_environment_variables();
$this->add_custom_redis();
@ -96,6 +97,11 @@ public function handle(StandaloneRedis $database)
if (count($persistent_storages) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}

View File

@ -221,7 +221,19 @@ private function sentinel()
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
@ -231,7 +243,7 @@ private function sentinel()
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
@ -258,7 +270,7 @@ private function sentinel()
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
@ -283,7 +295,7 @@ private function sentinel()
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
@ -307,7 +319,7 @@ private function sentinel()
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running
@ -539,7 +551,19 @@ private function old_way()
}
$name = data_get($exitedService, 'name');
$fqdn = data_get($exitedService, 'fqdn');
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
if ($name) {
if ($fqdn) {
$containerName = "$name, available at $fqdn";
} else {
$containerName = $name;
}
} else {
if ($fqdn) {
$containerName = $fqdn;
} else {
$containerName = null;
}
}
$projectUuid = data_get($service, 'environment.project.uuid');
$serviceUuid = data_get($service, 'uuid');
$environmentName = data_get($service, 'environment.name');
@ -549,7 +573,7 @@ private function old_way()
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
$exitedService->update(['status' => 'exited']);
}
@ -576,7 +600,7 @@ private function old_way()
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
@ -601,7 +625,7 @@ private function old_way()
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach ($notRunningDatabases as $database) {
@ -625,7 +649,7 @@ private function old_way()
} else {
$url = null;
}
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
}
// Check if proxy is running

View File

@ -10,7 +10,18 @@ class CheckProxy
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if ($server->proxyType() === 'NONE') {
if (!$server->isFunctional()) {
return false;
}
if ($server->isBuildServer()) {
if ($server->proxy) {
$server->proxy = null;
$server->save();
}
return false;
}
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) {
return false;
}
['uptime' => $uptime, 'error' => $error] = $server->validateConnection();

View File

@ -15,7 +15,7 @@ public function handle(Server $server, bool $async = true): string|Activity
{
try {
$proxyType = $server->proxyType();
if (is_null($proxyType) || $proxyType === 'NONE') {
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
return 'OK';
}
$commands = collect([]);

View File

@ -12,12 +12,10 @@ class UpdateCoolify
public ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public bool $async = false;
public function handle(bool $force = false, bool $async = false)
public function handle($manual_update = false)
{
try {
$this->async = $async;
$settings = InstanceSettings::get();
ray('Running InstanceAutoUpdateJob');
$this->server = Server::find(0);
@ -27,24 +25,18 @@ public function handle(bool $force = false, bool $async = false)
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
// if ($settings->next_channel) {
// ray('next channel enabled');
// $this->latestVersion = 'next';
// }
if ($force) {
$this->update();
} else {
if (!$manual_update) {
if (!$settings->is_auto_update_enabled) {
return 'Auto update is disabled';
return;
}
if ($this->latestVersion === $this->currentVersion) {
return 'Already on latest version';
return;
}
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
return 'Latest version is lower than current version?!';
return;
}
$this->update();
}
$this->update();
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
@ -56,34 +48,16 @@ public function handle(bool $force = false, bool $async = false)
private function update()
{
if (isDev()) {
ray("Running update on local docker container. Updating to $this->latestVersion");
if ($this->async) {
ray('Running async update');
remote_process([
"sleep 10"
], $this->server);
} else {
instant_remote_process([
"sleep 10"
], $this->server);
}
ray('Update done');
return;
} else {
ray('Running update on production server');
if ($this->async) {
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
} else {
instant_remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
}
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
remote_process([
"sleep 10"
], $this->server);
return;
}
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
], $this->server);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
return;
}
}

View File

@ -40,7 +40,7 @@ public function handle()
$serviceTemplatesJson[$name] = $parsed;
}
}
$serviceTemplatesJson = json_encode($serviceTemplatesJson, JSON_PRETTY_PRINT);
$serviceTemplatesJson = json_encode($serviceTemplatesJson);
file_put_contents(base_path('templates/service-templates.json'), $serviceTemplatesJson);
}

View File

@ -10,6 +10,9 @@
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesAndVersions;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\PullVersionsFromCDN;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
@ -29,6 +32,8 @@ protected function schedule(Schedule $schedule): void
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
@ -41,7 +46,8 @@ protected function schedule(Schedule $schedule): void
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();

View File

@ -82,7 +82,7 @@ public function manual(Request $request)
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;

View File

@ -0,0 +1,222 @@
<?php
namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Gitea extends Controller
{
public function manual(Request $request)
{
try {
$return_payloads = collect([]);
$x_gitea_delivery = request()->header('X-Gitea-Delivery');
if (app()->isDownForMaintenance()) {
ray('Maintenance mode is on');
$epoch = now()->valueOf();
$files = Storage::disk('webhooks-during-maintenance')->files();
$gitea_delivery_found = collect($files)->filter(function ($file) use ($x_gitea_delivery) {
return Str::contains($file, $x_gitea_delivery);
})->first();
if ($gitea_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
'attributes' => $request->attributes->all(),
'request' => $request->request->all(),
'query' => $request->query->all(),
'server' => $request->server->all(),
'files' => $request->files->all(),
'cookies' => $request->cookies->all(),
'headers' => $request->headers->all(),
'content' => $request->getContent(),
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitea::manual_{$x_gitea_delivery}", $json);
return;
}
$x_gitea_event = Str::lower($request->header('X-Gitea-Event'));
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
$content_type = $request->header('Content-Type');
$payload = $request->collect();
if ($x_gitea_event === 'ping') {
// Just pong
return response('pong');
}
if ($content_type !== 'application/json') {
$payload = json_decode(data_get($payload, 'payload'), true);
}
if ($x_gitea_event === 'push') {
$branch = data_get($payload, 'ref');
$full_name = data_get($payload, 'repository.full_name');
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
$branch = Str::after($branch, 'refs/heads/');
}
$added_files = data_get($payload, 'commits.*.added');
$removed_files = data_get($payload, 'commits.*.removed');
$modified_files = data_get($payload, 'commits.*.modified');
$changed_files = collect($added_files)->concat($removed_files)->concat($modified_files)->unique()->flatten();
ray($changed_files);
ray('Manual Webhook Gitea Push Event with branch: ' . $branch);
}
if ($x_gitea_event === 'pull_request') {
$action = data_get($payload, 'action');
$full_name = data_get($payload, 'repository.full_name');
$pull_request_id = data_get($payload, 'number');
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
$branch = data_get($payload, 'pull_request.head.ref');
$base_branch = data_get($payload, 'pull_request.base.ref');
ray('Webhook Gitea Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
}
if (!$branch) {
return response('Nothing to do. No branch found in the request.');
}
$applications = Application::where('git_repository', 'like', "%$full_name%");
if ($x_gitea_event === 'push') {
$applications = $applications->where('git_branch', $branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
}
}
if ($x_gitea_event === 'pull_request') {
$applications = $applications->where('git_branch', $base_branch)->get();
if ($applications->isEmpty()) {
return response("Nothing to do. No applications found with branch '$base_branch'.");
}
}
foreach ($applications as $application) {
$webhook_secret = data_get($application, 'manual_webhook_secret_gitea');
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) {
ray('Invalid signature');
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid signature.',
]);
continue;
}
$isFunctional = $application->destination->server->isFunctional();
if (!$isFunctional) {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_gitea_event === 'push') {
if ($application->isDeployable()) {
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
ray('Deploying ' . $application->name . ' with branch ' . $branch);
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'after', 'HEAD'),
is_webhook: true,
);
$return_payloads->push([
'status' => 'success',
'message' => 'Deployment queued.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
} else {
$paths = str($application->watch_paths)->explode("\n");
$return_payloads->push([
'status' => 'failed',
'message' => 'Changed files do not match watch paths. Ignoring deployment.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
'details' => [
'changed_files' => $changed_files,
'watch_paths' => $paths,
],
]);
}
} else {
$return_payloads->push([
'status' => 'failed',
'message' => 'Deployments disabled.',
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
}
}
if ($x_gitea_event === 'pull_request') {
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
if ($application->isPRDeployable()) {
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found) {
ApplicationPreview::create([
'git_type' => 'gitea',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
]);
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: data_get($payload, 'head.sha', 'HEAD'),
is_webhook: true,
git_type: 'gitea'
);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment queued.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Preview deployments disabled.',
]);
}
}
if ($action === 'closed') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
$container_name = generateApplicationContainerName($application, $pull_request_id);
// ray('Stopping container: ' . $container_name);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
$return_payloads->push([
'application' => $application->name,
'status' => 'success',
'message' => 'Preview deployment closed.',
]);
} else {
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'No preview deployment found.',
]);
}
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
}

View File

@ -106,7 +106,7 @@ public function manual(Request $request)
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
continue;
}

View File

@ -109,7 +109,7 @@ public function manual(Request $request)
$return_payloads->push([
'application' => $application->name,
'status' => 'failed',
'message' => 'Invalid token.',
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;

View File

@ -150,6 +150,10 @@ public function events(Request $request)
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
}
if (!$subscription) {
if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired for customer: ' . $customerId);
return response("Subscription incomplete expired", 200);
}
send_internal_notification('No subscription found for: ' . $customerId);
return response("No subscription found", 400);
}
@ -166,9 +170,11 @@ public function events(Request $request)
$quantity = data_get($data, 'items.data.0.quantity', 10);
}
$team = data_get($subscription, 'team');
$team->update([
'custom_server_limit' => $quantity,
]);
if ($team) {
$team->update([
'custom_server_limit' => $quantity,
]);
}
ServerLimitCheckJob::dispatch($team);
}
$subscription->update([
@ -210,7 +216,9 @@ public function events(Request $request)
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
$team->trialEnded();
if ($team) {
$team->trialEnded();
}
$subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id' => null,

View File

@ -67,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Save original server between phases
private Server $original_server;
private Server $mainServer;
private bool $is_this_additional_server = false;
private ?ApplicationPreview $preview = null;
private ?string $git_type = null;
private bool $only_this_server = false;
@ -112,6 +113,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
public $tries = 1;
public function __construct(int $application_deployment_queue_id)
{
ray()->clearAll();
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
$this->application = Application::find($this->application_deployment_queue->application_id);
$this->build_pack = data_get($this->application, 'build_pack');
@ -123,6 +125,7 @@ public function __construct(int $application_deployment_queue_id)
$this->rollback = $this->application_deployment_queue->rollback;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only;
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
$this->only_this_server = $this->application_deployment_queue->only_this_server;
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
@ -136,6 +139,8 @@ public function __construct(int $application_deployment_queue_id)
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
$this->server = $this->mainServer = $this->destination->server;
$this->serverUser = $this->server->user;
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
@ -149,28 +154,7 @@ public function __construct(int $application_deployment_queue_id)
// Set preview fqdn
if ($this->pull_request_id !== 0) {
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
if ($this->application->fqdn) {
if (str($this->application->fqdn)->contains(',')) {
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->application->fqdn);
if (data_get($this->preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
}
}
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$this->preview->fqdn = $preview_fqdn;
$this->preview->save();
}
$this->preview = $this->application->generate_preview_fqdn($this->pull_request_id);
if ($this->application->is_github_based()) {
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
}
@ -184,6 +168,9 @@ public function __construct(int $application_deployment_queue_id)
public function handle(): void
{
$this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
if (!$this->server->isFunctional()) {
$this->application_deployment_queue->addLogEntry("Server is not functional.");
$this->fail("Server is not functional.");
@ -281,7 +268,7 @@ public function handle(): void
}
private function decide_what_to_do()
{
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
if ($this->restart_only) {
$this->just_restart();
return;
} else if ($this->pull_request_id !== 0) {
@ -331,18 +318,6 @@ private function deploy_simple_dockerfile()
],
);
$this->generate_image_names();
// Always rebuild dockerfile based container.
// if (!$this->force_rebuild) {
// $this->check_image_locally_or_remotely();
// if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
// $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
// $this->generate_compose_file();
// $this->push_to_docker_registry();
// $this->rolling_update();
// return;
// }
// }
$this->generate_compose_file();
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
@ -390,15 +365,29 @@ private function deploy_docker_compose_buildpack()
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
$yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables();
} else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
$this->save_environment_variables();
if (!is_null($this->env_filename)) {
$services = collect($composeFile['services']);
$services = $services->map(function ($service, $name) {
$service['env_file'] = [$this->env_filename];
return $service;
});
$composeFile['services'] = $services->toArray();
}
if (is_null($composeFile)) {
$this->application_deployment_queue->addLogEntry("Failed to parse docker-compose file.");
$this->fail("Failed to parse docker-compose file.");
return;
}
$yaml = Yaml::dump($composeFile->toArray(), 10);
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
]);
$this->save_environment_variables();
// Build new container to limit downtime.
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
@ -407,13 +396,18 @@ private function deploy_docker_compose_buildpack()
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
);
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
}
$this->stop_running_container(force: true);
$this->application_deployment_queue->addLogEntry("Starting new application.");
$networkId = $this->application->uuid;
if ($this->pull_request_id !== 0) {
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
@ -422,7 +416,7 @@ private function deploy_docker_compose_buildpack()
// TODO
} else {
$this->execute_remote_command([
"docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
"docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true
], [
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
@ -438,9 +432,15 @@ private function deploy_docker_compose_buildpack()
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
["command" => $command, "hidden" => true],
);
}
} else {
@ -450,8 +450,13 @@ private function deploy_docker_compose_buildpack()
);
$this->write_deployment_configurations();
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
);
$this->write_deployment_configurations();
}
@ -470,16 +475,11 @@ private function deploy_dockerfile_buildpack()
}
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->clone_repository();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@ -499,21 +499,12 @@ private function deploy_nixpacks_buildpack()
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
ray('pushing to docker registry');
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
if ($this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
}
$this->clone_repository();
$this->cleanup_git();
@ -532,15 +523,10 @@ private function deploy_static_buildpack()
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
if (!$this->force_rebuild) {
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->should_skip_build()) {
return;
}
}
@ -608,7 +594,7 @@ private function push_to_docker_registry()
ray('additional_servers');
$forceFail = true;
}
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
if ($this->is_this_additional_server) {
ray('this is an additional_servers, no pushy pushy');
return;
}
@ -623,8 +609,8 @@ private function push_to_docker_registry()
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
// Tag image with docker_registry_image_tag
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
@ -634,7 +620,6 @@ private function push_to_docker_registry()
],
);
}
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
if ($forceFail) {
@ -665,9 +650,9 @@ private function generate_image_names()
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_tag) {
$this->dockerImageTag = $this->application->docker_registry_image_tag;
}
// if ($this->application->docker_registry_image_tag) {
// $this->dockerImageTag = $this->application->docker_registry_image_tag;
// }
if ($this->application->docker_registry_image_name) {
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build";
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}";
@ -682,19 +667,42 @@ private function just_restart()
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if ($this->should_skip_build()) {
return;
}
}
private function should_skip_build()
{
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->generate_compose_file();
$this->rolling_update();
$this->post_deployment();
if ($this->is_this_additional_server) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
if ($this->restart_only) {
$this->post_deployment();
}
return true;
}
if (!$this->application->isConfigurationChanged()) {
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return true;
} else {
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
}
} else {
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application.");
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Building new image.");
}
if ($this->restart_only) {
$this->restart_only = false;
$this->decide_what_to_do();
}
return false;
}
private function check_image_locally_or_remotely()
{
@ -814,16 +822,29 @@ private function save_environment_variables()
$this->env_filename = null;
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
if ($this->use_build_server) {
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
$this->server = $this->build_server;
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
} else {
$this->execute_remote_command(
[
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
"hidden" => true,
"ignore_errors" => true
]
);
}
} else {
$envs_base64 = base64_encode($envs->implode("\n"));
@ -835,38 +856,20 @@ private function save_environment_variables()
);
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
if ($this->use_build_server) {
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
$this->server = $this->build_server;
} else {
$this->execute_remote_command(
[
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
]
);
}
}
// $this->execute_remote_command([
// executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
// "hidden" => true,
// "save" => "dotenv"
// ]);
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
// $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
// $this->execute_remote_command(
// [
// "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
// ]
// );
// } else {
// $this->execute_remote_command(
// [
// "command" => "rm -f $this->configuration_dir/.env",
// "hidden" => true,
// "ignore_errors" => true
// ]
// );
// }
}
@ -988,6 +991,7 @@ private function health_check()
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
$this->newVersionIsHealthy = false;
$this->query_logs();
break;
}
$counter++;
@ -997,9 +1001,25 @@ private function health_check()
$sleeptime++;
}
}
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
$this->query_logs();
}
}
}
}
private function query_logs()
{
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Container logs:");
$this->execute_remote_command(
[
"command" => "docker logs -n 100 {$this->container_name}",
"type" => "stderr",
"ignore_errors" => true,
],
);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
}
private function deploy_pull_request()
{
if ($this->application->build_pack === 'dockercompose') {
@ -1015,7 +1035,6 @@ private function deploy_pull_request()
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->clone_repository();
$this->set_base_dir();
$this->cleanup_git();
if ($this->application->build_pack === 'nixpacks') {
$this->generate_nixpacks_confs();
@ -1032,14 +1051,32 @@ private function deploy_pull_request()
}
private function create_workdir()
{
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
if ($this->use_build_server) {
$this->server = $this->original_server;
$this->execute_remote_command(
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
$this->server = $this->build_server;
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
} else {
$this->execute_remote_command(
[
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
],
[
"command" => "mkdir -p {$this->configuration_dir}"
],
);
}
}
private function prepare_builder_image()
{
@ -1119,10 +1156,6 @@ private function deploy_to_additional_destinations()
]));
}
}
private function set_base_dir()
{
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
}
private function set_coolify_variables()
{
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
@ -1175,7 +1208,6 @@ private function check_git_if_build_needed()
],
);
}
ray("GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}");
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
$this->application_deployment_queue->commit = $this->commit;
@ -1191,7 +1223,6 @@ private function clone_repository()
if ($this->pull_request_id !== 0) {
$this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head.");
}
ray($importCommands);
$this->execute_remote_command(
[
$importCommands, "hidden" => true
@ -1205,8 +1236,6 @@ private function clone_repository()
"save" => "commit_message"
]
);
ray($this->saved_outputs->get('commit_message'));
raY($this->commit);
if ($this->saved_outputs->get('commit_message')) {
$commit_message = str($this->saved_outputs->get('commit_message'))->limit(47);
$this->application_deployment_queue->commit_message = $commit_message->value();
@ -1257,8 +1286,21 @@ private function generate_nixpacks_confs()
// Do any modifications here
$this->generate_env_variables();
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
if (count($aptPkgs) === 0) {
data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']);
} else {
if (!in_array('curl', $aptPkgs)) {
$aptPkgs[] = 'curl';
}
if (!in_array('wget', $aptPkgs)) {
$aptPkgs[] = 'wget';
}
data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs);
}
data_set($parsed, 'variables', $merged_envs->toArray());
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
}
}
}
@ -1326,6 +1368,7 @@ private function generate_compose_file()
$onlyPort = $ports[0];
}
$persistent_storages = $this->generate_local_persistent_volumes();
$persistent_file_volumes = $this->application->fileStorages()->get();
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
// $environment_variables = $this->generate_environment_variables($ports);
$this->save_environment_variables();
@ -1526,6 +1569,11 @@ private function generate_compose_file()
if (count($persistent_storages) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages;
}
if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path";
})->toArray();
}
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
@ -1764,7 +1812,11 @@ private function build_image()
} else {
// Pure Dockerfile based deployment
if ($this->application->dockerfile) {
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
if ($this->force_rebuild) {
$build_command = "docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} else {
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
}
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
@ -1935,16 +1987,16 @@ private function run_pre_deployment_command()
if ($containers->count() == 0) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}");
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output).");
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"';
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->pre_deployment_command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
'command' => $exec, 'hidden' => true
],
);
return;
@ -1958,17 +2010,17 @@ private function run_post_deployment_command()
if (empty($this->application->post_deployment_command)) {
return;
}
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}");
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output).");
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"';
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'";
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
'command' => $exec, 'hidden' => true
],
);
return;

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ public function handle(): void
}
if (!$this->server->log_drain_notification_sent) {
ray('Log drain container still unhealthy. Sending notification...');
$this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
// $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
$this->server->update(['log_drain_notification_sent' => true]);
}
} else {

View File

@ -18,12 +18,12 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
public $timeout = 600;
public $tries = 1;
public function __construct(private bool $force = false)
public function __construct()
{
}
public function handle(): void
{
UpdateCoolify::run(force: $this->force, async: false);
UpdateCoolify::run();
}
}

View File

@ -0,0 +1,42 @@
<?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;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullTemplatesFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 10;
public function __construct()
{
}
public function handle(): void
{
try {
if (!isDev()) {
ray('PullTemplatesAndVersions service-templates');
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
if ($response->successful()) {
$services = $response->json();
File::put(base_path('templates/service-templates.json'), json_encode($services));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage());
ray($e->getMessage());
}
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Jobs;
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;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullVersionsFromCDN implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 10;
public function __construct()
{
}
public function handle(): void
{
try {
if (!isDev() && !isCloud()) {
ray('PullTemplatesAndVersions versions.json');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
} else {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
}
}
} catch (\Throwable $e) {
send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage());
ray($e->getMessage());
}
}
}

View File

@ -43,7 +43,7 @@ public function handle()
try {
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->removeCoolifyYaml();
$this->remove_unnecessary_coolify_yaml();
if (config('coolify.is_sentinel_enabled')) {
$this->server->checkSentinel();
}
@ -53,8 +53,44 @@ public function handle()
ray($e->getMessage());
return handleError($e);
}
try {
// $this->check_docker_engine();
} catch (\Throwable $e) {
// Do nothing
}
}
private function removeCoolifyYaml()
private function check_docker_engine()
{
$version = instant_remote_process([
"docker info",
], $this->server, false);
if (is_null($version)) {
$os = instant_remote_process([
"cat /etc/os-release | grep ^ID=",
], $this->server, false);
$os = str($os)->after('ID=')->trim();
if ($os === 'ubuntu') {
try {
instant_remote_process([
"systemctl start docker",
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
} else {
try {
instant_remote_process([
"service docker start",
], $this->server);
} catch (\Throwable $e) {
ray($e->getMessage());
return handleError($e);
}
}
}
}
private function remove_unnecessary_coolify_yaml()
{
// This will remote the coolify.yaml file from the server as it is not needed on cloud servers
if (isCloud() && $this->server->id !== 0) {

View File

@ -17,5 +17,7 @@ public function handle(ProxyStarted $event): void
$this->server = data_get($event, 'data');
$this->server->setupDefault404Redirect();
$this->server->setupDynamicProxyConfiguration();
$this->server->proxy->force_stop = false;
$this->server->save();
}
}

View File

@ -10,7 +10,7 @@ class Compose extends Component
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
$this->services = get_service_templates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');

View File

@ -24,7 +24,7 @@ public function mount()
}
public function render()
{
return view('livewire.force-password-reset');
return view('livewire.force-password-reset')->layout('layouts.simple');
}
public function submit()
{

View File

@ -141,7 +141,7 @@ public function mount()
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
$this->initLoadingCompose = true;
$this->dispatch('info', 'Loading docker compose file...');
$this->dispatch('info', 'Loading docker compose file.');
}
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
@ -287,7 +287,7 @@ public function checkFqdns($showToaster = true)
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
}
}
@ -352,7 +352,7 @@ public function submit($showToaster = true)
$domain = data_get($service, 'domain');
if ($domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
}
check_domain_usage(resource: $this->application);
}

View File

@ -2,10 +2,12 @@
namespace App\Livewire\Project\Application;
use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use Illuminate\Support\Collection;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class Previews extends Component
@ -16,6 +18,9 @@ class Previews extends Component
public Collection $pull_requests;
public int $rate_limit_remaining;
protected $rules = [
'application.previews.*.fqdn' => 'string|nullable',
];
public function mount()
{
$this->pull_requests = collect();
@ -33,7 +38,71 @@ public function load_prs()
return handleError($e, $this);
}
}
public function save_preview($preview_id)
{
try {
$success = true;
$preview = $this->application->previews->find($preview_id);
if (isset($preview->fqdn)) {
$preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim();
$preview->fqdn = str($preview->fqdn)->trim()->lower();
if (!validate_dns_entry($preview->fqdn, $this->application->destination->server)) {
$this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$success = false;
}
check_domain_usage(resource: $this->application, domain: $preview->fqdn);
}
if (!$preview) {
throw new \Exception('Preview not found');
}
$success && $preview->save();
$success && $this->dispatch('success', 'Preview saved.<br><br>Do not forget to redeploy the preview to apply the changes.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function generate_preview($preview_id)
{
$preview = $this->application->previews->find($preview_id);
if (!$preview) {
$this->dispatch('error', 'Preview not found.');
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
$url = Url::fromString($fqdn);
$template = $this->application->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $preview_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
$this->dispatch('success', 'Domain generated.');
}
public function add(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (!$found && !is_null($pull_request_html_url)) {
ApplicationPreview::create([
'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url
]);
}
$this->application->generate_preview_fqdn($pull_request_id);
$this->application->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
{
try {
@ -71,6 +140,25 @@ protected function setDeploymentUuid()
}
public function stop(int $pull_request_id)
{
try {
if ($this->application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
} else {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
foreach ($containers as $container) {
$name = str_replace('/', '', $container['Names']);
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
GetContainersStatus::dispatchSync($this->application->destination->server);
$this->application->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function delete(int $pull_request_id)
{
try {
if ($this->application->destination->server->isSwarm()) {

View File

@ -2,12 +2,13 @@
namespace App\Livewire\Project\Database\Backup;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class Execution extends Component
{
public $database;
public $backup;
public ?ScheduledDatabaseBackup $backup;
public $executions;
public $s3s;
public function mount()

View File

@ -2,12 +2,13 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
use Spatie\Url\Url;
class BackupEdit extends Component
{
public $backup;
public ?ScheduledDatabaseBackup $backup;
public $s3s;
public ?string $status = null;
public array $parameters;
@ -36,7 +37,7 @@ public function mount()
{
$this->parameters = get_route_parameters();
if (is_null(data_get($this->backup, 's3_storage_id'))) {
$this->backup->s3_storage_id = 'default';
data_set($this->backup, 's3_storage_id', 'default');
}
}

View File

@ -2,11 +2,12 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class BackupExecutions extends Component
{
public $backup;
public ?ScheduledDatabaseBackup $backup = null;
public $executions = [];
public $setDeletableBackup;
public function getListeners()
@ -20,8 +21,11 @@ public function getListeners()
public function cleanupFailed()
{
$this->backup?->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
if ($this->backup) {
$this->backup->executions()->where('status', 'failed')->delete();
$this->refreshBackupExecutions();
$this->dispatch('success', 'Failed backups cleaned up.');
}
}
public function deleteBackup($exeuctionId)
{
@ -45,6 +49,8 @@ public function download_file($exeuctionId)
}
public function refreshBackupExecutions(): void
{
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
if ($this->backup) {
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
}
}
}

View File

@ -3,6 +3,7 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Illuminate\Support\Collection;
use Livewire\Component;
class CreateScheduledBackup extends Component
@ -12,7 +13,7 @@ class CreateScheduledBackup extends Component
public bool $enabled = true;
public bool $save_s3 = false;
public $s3_storage_id;
public $s3s;
public Collection $s3s;
protected $rules = [
'frequency' => 'required|string',

View File

@ -2,6 +2,7 @@
namespace App\Livewire\Project\Database;
use App\Models\ScheduledDatabaseBackup;
use Livewire\Component;
class ScheduledBackups extends Component
@ -9,7 +10,7 @@ class ScheduledBackups extends Component
public $database;
public $parameters;
public $type;
public $selectedBackup;
public ?ScheduledDatabaseBackup $selectedBackup;
public $selectedBackupId;
public $s3s;
protected $listeners = ['refreshScheduledBackups'];

View File

@ -15,10 +15,10 @@ class Select extends Component
public string $type;
public string $server_id;
public string $destination_uuid;
public Countable|array|Server $allServers = [];
public Countable|array|Server $servers = [];
public Collection|array $standaloneDockers = [];
public Collection|array $swarmDockers = [];
public Collection|null|Server $allServers;
public Collection|null|Server $servers;
public ?Collection $standaloneDockers;
public ?Collection $swarmDockers;
public array $parameters;
public Collection|array $services = [];
public Collection|array $allServices = [];
@ -91,7 +91,7 @@ public function loadServices(bool $force = false)
});
} else {
$this->search = null;
$this->allServices = getServiceTemplates();
$this->allServices = get_service_templates($force);
$this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search));
});
@ -107,7 +107,11 @@ public function instantSave()
if ($this->includeSwarm) {
$this->servers = $this->allServers;
} else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
}
}
public function setType(string $type)
@ -126,13 +130,21 @@ public function setType(string $type)
case 'mongodb':
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
break;
}
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true;
$this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
if ($this->allServers instanceof Collection) {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} else {
$this->servers = $this->allServers;
}
}
if ($type === "existing-postgresql") {
$this->current_step = $type;

View File

@ -25,7 +25,7 @@ public function mount()
return redirect()->route('dashboard');
}
if (isset($type) && isset($destination_uuid) && isset($server_id)) {
$services = getServiceTemplates();
$services = get_service_templates();
if (in_array($type, DATABASE_TYPES)) {
if ($type->value() === "postgresql") {

View File

@ -67,7 +67,6 @@ public function check_status()
GetContainersStatus::run($this->service->server);
// dispatch_sync(new ContainerStatusJob($this->service->server));
$this->dispatch('refresh')->self();
$this->dispatch('updateStatus');
} catch (\Exception $e) {
return handleError($e, $this);
}

View File

@ -7,13 +7,20 @@
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Livewire\Component;
use Illuminate\Support\Str;
class FileStorage extends Component
{
public LocalFileVolume $fileStorage;
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
public ServiceApplication|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase|Application $resource;
public string $fs_path;
public ?string $workdir = null;
@ -27,7 +34,7 @@ public function mount()
{
$this->resource = $this->fileStorage->service;
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
$this->workdir = $this->resource->service->workdir();
$this->workdir = $this->resource->service?->workdir();
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
} else {
$this->workdir = null;

View File

@ -30,7 +30,6 @@ public function getListeners()
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
"updateStatus"=> '$refresh',
];
}
public function serviceStarted()

View File

@ -42,7 +42,7 @@ public function mount()
$this->validationAttributes["fields.$key.value"] = $fieldKey;
}
}
$this->fields = $this->fields->sortDesc();
$this->fields = $this->fields->sortBy('name');
}
public function saveCompose($raw)
{
@ -52,7 +52,7 @@ public function saveCompose($raw)
public function instantSave()
{
$this->service->save();
$this->dispatch('success', 'Service settings saved.');
$this->dispatch('success', 'Service settings saved.');
}
public function submit()

View File

@ -24,6 +24,7 @@ public function mount()
public function delete()
{
try {
// $this->authorize('delete', $this->resource);
$this->resource->delete();
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations);
return redirect()->route('project.resource.index', [

View File

@ -43,6 +43,11 @@ public function mount()
$this->showTimeStamps = $this->resource->is_include_timestamps;
}
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
}
}
}
}
public function doSomethingWithThisChunkOfOutput($output)
@ -77,13 +82,6 @@ public function getLogs($refresh = false)
if (!$this->server->isFunctional()) {
return;
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
} else {
$this->pull_request = 'branch';
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
if (!$this->numberOfLines) {
$this->numberOfLines = 1000;

View File

@ -103,6 +103,14 @@ public function mount()
}
}
$this->containers = $this->containers->sort();
if (data_get($this->query,'pull_request_id')) {
$this->containers = $this->containers->filter(function ($container) {
return str_contains($container, $this->query['pull_request_id']);
});
ray($this->containers);
}
$this->loadMetrics();
} catch (\Exception $e) {
return handleError($e, $this);

View File

@ -3,21 +3,31 @@
namespace App\Livewire\Project\Shared\Storages;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Livewire\Component;
class Add extends Component
{
public $resource;
public $uuid;
public $parameters;
public $isSwarm = false;
public string $name;
public string $mount_path;
public ?string $host_path = null;
public string $file_storage_path;
public ?string $file_storage_content = null;
public string $file_storage_directory_source;
public string $file_storage_directory_destination;
public $rules = [
'name' => 'required|string',
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
'file_storage_path' => 'string',
'file_storage_content' => 'nullable|string',
'file_storage_directory_source' => 'string',
'file_storage_directory_destination' => 'string',
];
protected $listeners = ['clearAddStorage' => 'clear'];
@ -26,10 +36,16 @@ class Add extends Component
'name' => 'name',
'mount_path' => 'mount',
'host_path' => 'host',
'file_storage_path' => 'file storage path',
'file_storage_content' => 'file storage content',
'file_storage_directory_source' => 'file storage directory source',
'file_storage_directory_destination' => 'file storage directory destination',
];
public function mount()
{
$this->file_storage_directory_source = application_configuration_dir() . "/{$this->resource->uuid}";
$this->uuid = $this->resource->uuid;
$this->parameters = get_route_parameters();
if (data_get($this->parameters, 'application_uuid')) {
$applicationUuid = $this->parameters['application_uuid'];
@ -43,18 +59,75 @@ public function mount()
}
}
}
public function submit()
public function submitFileStorage()
{
try {
$this->validate($this->rules);
$this->validate([
'file_storage_path' => 'string',
'file_storage_content' => 'nullable|string',
]);
$this->file_storage_path = trim($this->file_storage_path);
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
if ($this->resource->getMorphClass() === 'App\Models\Application') {
$fs_path = application_configuration_dir() . '/' . $this->resource->uuid . $this->file_storage_path;
}
LocalFileVolume::create(
[
'fs_path' => $fs_path,
'mount_path' => $this->file_storage_path,
'content' => $this->file_storage_content,
'is_directory' => false,
'resource_id' => $this->resource->id,
'resource_type' => get_class($this->resource)
],
);
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitFileStorageDirectory()
{
try {
$this->validate([
'file_storage_directory_source' => 'string',
'file_storage_directory_destination' => 'string',
]);
$this->file_storage_directory_source = trim($this->file_storage_directory_source);
$this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value();
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
LocalFileVolume::create(
[
'fs_path' => $this->file_storage_directory_source,
'mount_path' => $this->file_storage_directory_destination,
'is_directory' => true,
'resource_id' => $this->resource->id,
'resource_type' => get_class($this->resource)
],
);
$this->dispatch('refresh_storages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submitPersistentVolume()
{
try {
$this->validate([
'name' => 'required|string',
'mount_path' => 'required|string',
'host_path' => 'string|nullable',
]);
$name = $this->uuid . '-' . $this->name;
$this->dispatch('addNewVolume', [
'name' => $name,
'mount_path' => $this->mount_path,
'host_path' => $this->host_path,
]);
$this->dispatch('closeStorageModal');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@ -12,6 +12,8 @@ class Show extends Component
public bool $isReadOnly = false;
public ?string $modalId = null;
public bool $isFirst = true;
public bool $isService = false;
public ?string $startedAt = null;
protected $rules = [
'storage.name' => 'required|string',

View File

@ -11,10 +11,12 @@ class Webhooks extends Component
public ?string $githubManualWebhook = null;
public ?string $gitlabManualWebhook = null;
public ?string $bitbucketManualWebhook = null;
public ?string $giteaManualWebhook = null;
protected $rules = [
'resource.manual_webhook_secret_github' => 'nullable|string',
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
'resource.manual_webhook_secret_gitea' => 'nullable|string',
];
public function saveSecret()
{
@ -32,6 +34,7 @@ public function mount()
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
$this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea');
}
public function render()
{

View File

@ -58,6 +58,12 @@ public function serverInstalled()
$this->server->refresh();
$this->server->settings->refresh();
}
public function updatedServerSettingsIsBuildServer()
{
$this->dispatch('serverInstalled');
$this->dispatch('serverRefresh');
$this->dispatch('proxyStatusUpdated');
}
public function instantSave()
{
try {

View File

@ -97,6 +97,9 @@ public function submit()
if ($this->is_swarm_worker) {
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
}
if ($this->is_build_server) {
data_forget($payload, 'proxy');
}
$server = Server::create($payload);
if ($this->is_build_server) {
$this->is_swarm_manager = false;

View File

@ -72,6 +72,8 @@ public function checkProxy()
public function startProxy()
{
try {
$this->server->proxy->force_stop = false;
$this->server->save();
$activity = StartProxy::run($this->server);
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) {
@ -86,17 +88,15 @@ public function stop()
instant_remote_process([
"docker service rm coolify-proxy_traefik",
], $this->server);
$this->server->proxy->status = 'exited';
$this->server->save();
$this->dispatch('proxyStatusUpdated');
} else {
instant_remote_process([
"docker rm -f coolify-proxy",
], $this->server);
$this->server->proxy->status = 'exited';
$this->server->save();
$this->dispatch('proxyStatusUpdated');
}
$this->server->proxy->status = 'exited';
$this->server->proxy->force_stop = true;
$this->server->save();
$this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@ -129,6 +129,9 @@ public function validateDockerVersion()
}
}
if ($this->server->isBuildServer()) {
return;
}
$this->dispatch('startProxy');
}
public function render()

View File

@ -36,7 +36,7 @@ class Backup extends Component
public function mount()
{
$this->backup = $this->database?->scheduledBackups->first() ?? [];
$this->backup = $this->database?->scheduledBackups->first() ?? null;
$this->executions = $this->backup?->executions ?? [];
}
public function add_coolify_database()

View File

@ -70,9 +70,8 @@ public function submit()
$this->validate();
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
ray('asdf');
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
$error_show = true;
}
}

View File

@ -10,6 +10,8 @@ class AdminView extends Component
{
public $users;
public ?string $search = "";
public bool $lots_of_users = false;
private $number_of_users_to_show = 20;
public function mount()
{
if (!isInstanceAdmin()) {
@ -32,8 +34,14 @@ public function submitSearch()
}
public function getUsers()
{
$this->users = User::where('id', '!=', auth()->id())->get();
// $this->users = User::all();
$users = User::where('id', '!=', auth()->id())->get();
if ($users->count() > $this->number_of_users_to_show) {
$this->lots_of_users = true;
$this->users = $users->take($this->number_of_users_to_show);
} else {
$this->lots_of_users = false;
$this->users = $users;
}
}
private function finalizeDeletion(User $user, Team $team)
{
@ -59,6 +67,9 @@ private function finalizeDeletion(User $user, Team $team)
}
public function delete($id)
{
if (!auth()->user()->isInstanceAdmin()) {
return $this->dispatch('error', 'You are not authorized to delete users');
}
$user = User::find($id);
$teams = $user->teams;
foreach ($teams as $team) {

View File

@ -17,6 +17,10 @@ class InviteLink extends Component
public string $email;
public string $role = 'member';
protected $rules = [
'email' => 'required|email',
'role' => 'required|string',
];
public function mount()
{
$this->email = isDev() ? 'test3@example.com' : '';
@ -34,6 +38,7 @@ public function viaLink()
private function generate_invite_link(bool $sendEmail = false)
{
try {
$this->validate();
$member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) {
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");

View File

@ -5,11 +5,9 @@
use App\Actions\Server\UpdateCoolify;
use Livewire\Component;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
class Upgrade extends Component
{
use WithRateLimiting;
public bool $showProgress = false;
public bool $updateInProgress = false;
public bool $isUpgradeAvailable = false;
@ -31,9 +29,8 @@ public function upgrade()
if ($this->updateInProgress) {
return;
}
$this->rateLimit(1, 60);
$this->updateInProgress = true;
UpdateCoolify::run(force: true, async: true);
UpdateCoolify::run(manual_update: true);
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@ -10,6 +10,7 @@
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
@ -213,6 +214,13 @@ public function gitCommitLink($link): string
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
return "https://{$git_repository}/commit/{$link}";
}
if (str($this->git_repository)->contains('bitbucket')) {
$git_repository = str_replace('.git', '', $this->git_repository);
$url = Url::fromString($git_repository);
$url = $url->withUserInfo('');
$url = $url->withPath($url->getPath() . '/commits/' . $link);
return $url->__toString();
}
return $this->git_repository;
}
public function dockerfileLocation(): Attribute
@ -661,7 +669,9 @@ function generateGitImportCommands(string $deployment_uuid, int $pull_request_id
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
}
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
if (!$only_checkout) {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
} else {
@ -880,7 +890,8 @@ function loadComposeFile($isInit = false)
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
"mkdir -p /tmp/{$uuid}",
"cd /tmp/{$uuid}",
$cloneCommand,
"git sparse-checkout init --cone",
"git sparse-checkout set {$fileList->implode(' ')}",
@ -891,29 +902,15 @@ function loadComposeFile($isInit = false)
if (!$composeFileContent) {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
} else {
$this->docker_compose_raw = $composeFileContent;
$this->save();
}
// if ($composeFile === $prComposeFile) {
// $this->docker_compose_pr_raw = $composeFileContent;
// $this->save();
// } else {
// $commands = collect([
// "cd /tmp/{$uuid}",
// "cat .$workdir$prComposeFile",
// ]);
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
// if (!$composePrFileContent) {
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
// $this->save();
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
// } else {
// $this->docker_compose_pr_raw = $composePrFileContent;
// $this->save();
// }
// }
$commands = collect([
"rm -rf /tmp/{$uuid}",
@ -1055,4 +1052,29 @@ public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false
}
}
}
function generate_preview_fqdn(int $pull_request_id) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id);
if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) {
if (str($this->fqdn)->contains(',')) {
$url = Url::fromString(str($this->fqdn)->explode(',')[0]);
$preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]);
} else {
$url = Url::fromString($this->fqdn);
if (data_get($preview, 'fqdn')) {
$preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn'));
}
}
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2(7);
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
}
return $preview;
}
}

View File

@ -525,7 +525,7 @@ public function isServerReady(int $tries = 3)
// Reached max number of retries
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team?->notify(new Unreachable($this));
// $this->team?->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
if ($this->settings->is_reachable === true) {
@ -825,7 +825,7 @@ public function validateConnection()
'unreachable_count' => 0,
]);
if (data_get($server, 'unreachable_notification_sent') === true) {
$server->team?->notify(new Revived($server));
// $server->team?->notify(new Revived($server));
$server->update(['unreachable_notification_sent' => false]);
}
return ['uptime' => true, 'error' => null];
@ -897,7 +897,9 @@ public function validateDockerSwarm()
}
public function validateDockerEngineVersion()
{
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
$dockerVersionRaw = instant_remote_process(["docker version --format json"], $this, false);
$dockerVersionJson = json_decode($dockerVersionRaw, true);
$dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0');
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->settings->is_usable = false;
@ -927,4 +929,7 @@ public function isNonRoot()
}
return $this->user !== 'root';
}
public function isBuildServer() {
return $this->settings->is_build_server;
}
}

View File

@ -472,6 +472,72 @@ public function extraFields()
}
$fields->put('Admin', $data->toArray());
break;
case str($image)?->contains('vaultwarden'):
$data = collect([]);
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
$ADMIN_TOKEN = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_ADMIN')->first();
$SIGNUP_ALLOWED = $this->environment_variables()->where('key', 'SIGNUP_ALLOWED')->first();
$PUSH_ENABLED = $this->environment_variables()->where('key', 'PUSH_ENABLED')->first();
$PUSH_INSTALLATION_ID = $this->environment_variables()->where('key', 'PUSH_SERVICE_ID')->first();
$PUSH_INSTALLATION_KEY = $this->environment_variables()->where('key', 'PUSH_SERVICE_KEY')->first();
if ($DATABASE_URL) {
$data = $data->merge([
'Database URL' => [
'key' => data_get($DATABASE_URL, 'key'),
'value' => data_get($DATABASE_URL, 'value'),
],
]);
}
if ($ADMIN_TOKEN) {
$data = $data->merge([
'Admin Password' => [
'key' => data_get($ADMIN_TOKEN, 'key'),
'value' => data_get($ADMIN_TOKEN, 'value'),
'isPassword' => true,
],
]);
}
if ($SIGNUP_ALLOWED) {
$data = $data->merge([
'Signup Allowed' => [
'key' => data_get($SIGNUP_ALLOWED, 'key'),
'value' => data_get($SIGNUP_ALLOWED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_ENABLED) {
$data = $data->merge([
'Push Enabled' => [
'key' => data_get($PUSH_ENABLED, 'key'),
'value' => data_get($PUSH_ENABLED, 'value'),
'rules' => 'required|string|in:true,false',
],
]);
}
if ($PUSH_INSTALLATION_ID) {
$data = $data->merge([
'Push Installation ID' => [
'key' => data_get($PUSH_INSTALLATION_ID, 'key'),
'value' => data_get($PUSH_INSTALLATION_ID, 'value'),
],
]);
}
if ($PUSH_INSTALLATION_KEY) {
$data = $data->merge([
'Push Installation Key' => [
'key' => data_get($PUSH_INSTALLATION_KEY, 'key'),
'value' => data_get($PUSH_INSTALLATION_KEY, 'value'),
'isPassword' => true,
],
]);
}
$fields->put('Vaultwarden', $data);
break;
}
}
$databases = $this->databases()->get();
@ -659,7 +725,7 @@ public function failedTaskLink($task_uuid)
return route('project.service.scheduled-tasks', [
'project_uuid' => data_get($this, 'environment.project.uuid'),
'environment_name' => data_get($this, 'environment.name'),
'application_uuid' => data_get($this, 'uuid'),
'service_uuid' => data_get($this, 'uuid'),
'task_uuid' => $task_uuid
]);
}
@ -667,7 +733,7 @@ public function failedTaskLink($task_uuid)
}
public function documentation()
{
$services = getServiceTemplates();
$services = get_service_templates();
$service = data_get($services, str($this->name)->beforeLast('-')->value, []);
return data_get($service, 'documentation', config('constants.docs.base_url'));
}

View File

@ -40,6 +40,13 @@ public function type()
{
return 'service';
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function workdir() {
return service_configuration_dir() . "/{$this->service->uuid}";
}
public function serviceType()
{
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {

View File

@ -59,6 +59,13 @@ public function getServiceDatabaseUrl()
}
return "{$realIp}:{$port}";
}
public function team()
{
return data_get($this, 'environment.project.team');
}
public function workdir() {
return service_configuration_dir() . "/{$this->service->uuid}";
}
public function service()
{
return $this->belongsTo(Service::class);

View File

@ -0,0 +1,69 @@
<?php
namespace App\Policies;
use App\Models\Application;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ApplicationPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Application $application): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Application $application): bool
{
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Application $application): bool
{
return true;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Policies;
use App\Models\Service;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class ServicePolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Service $service): bool
{
return true;
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
public function stop(User $user, Service $service): bool
{
if ($user->isAdmin()) {
return true;
}
return false;
}
}

View File

@ -43,16 +43,10 @@ function queue_application_deployment(Application $application, string $deployme
]);
if ($no_questions_asked) {
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
} else if (next_queuable($server_id, $application_id)) {
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
@ -63,6 +57,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
$deployment->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id,
));
@ -75,6 +70,7 @@ function queue_next_deployment(Application $application)
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id,
));

View File

@ -173,14 +173,14 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
{
if ($resource->getMorphClass() === 'App\Models\ServiceApplication') {
$uuid = $resource->uuid;
$server = $resource->service->server;
$environment_variables = $resource->service->environment_variables;
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'service.server');
$environment_variables = data_get($resource, 'service.environment_variables');
$type = $resource->serviceType();
} else if ($resource->getMorphClass() === 'App\Models\Application') {
$uuid = $resource->uuid;
$server = $resource->destination->server;
$environment_variables = $resource->environment_variables;
$uuid = data_get($resource, 'uuid');
$server = data_get($resource, 'destination.server');
$environment_variables = data_get($resource, 'environment_variables');
$type = $resource->serviceType();
}
if (is_null($server) || is_null($type)) {
@ -234,7 +234,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
}
return $payload;
}
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null)
{
$labels = collect([]);
if ($serviceLabels) {
@ -247,7 +247,6 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
// $stripped_path = str($path)->replaceEnd('/', '');
$schema = $url->getScheme();
$port = $url->getPort();
@ -273,7 +272,7 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains,
}
return $labels->sort();
}
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false)
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, bool $generate_unique_uuid = false, ?string $image = null)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
@ -331,7 +330,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$http_label = "http-{$loop}-{$uuid}-{$service_name}";
$https_label = "https-{$loop}-{$uuid}-{$service_name}";
}
if (str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)");
$labels->push("traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1");
}
if ($schema === 'https') {
// Set labels for https
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
@ -341,9 +343,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
}
if ($path !== '/') {
if ($is_stripprefix_enabled) {
$middlewares = collect([]);
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$https_label}-stripprefix"]);
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
@ -354,6 +357,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@ -369,6 +375,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
@ -396,9 +405,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
}
if ($path !== '/') {
if ($is_stripprefix_enabled) {
$middlewares = collect([]);
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$http_label}-stripprefix"]);
$middlewares->push("{$https_label}-stripprefix");
}
if ($is_gzip_enabled) {
$middlewares->push('gzip');
@ -409,6 +419,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@ -424,6 +437,9 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if (str($image)->contains('ghost')) {
$middlewares->push('redir-ghost');
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
@ -449,13 +465,32 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$appUuid = $appUuid . '-pr-' . $pull_request_id;
}
$labels = collect([]);
if ($application->fqdn) {
if ($pull_request_id !== 0) {
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
} else {
if ($pull_request_id === 0) {
if ($application->fqdn) {
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
// Add Caddy labels
$labels = $labels->merge(fqdnLabelsForCaddy(
network: $application->destination->network,
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
} else {
if ($preview->fqdn) {
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
}
// Add Traefik labels
$labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
@ -474,6 +509,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
}
return $labels->all();
}

View File

@ -110,16 +110,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$fqdn = Url::fromString($resourceFqdns);
$port = $fqdn->getPort();
$path = $fqdn->getPath();
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
if ($generatedEnv) {
$generatedEnv->value = $fqdn;
$generatedEnv->value = $fqdn . $path;
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName . "_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
// ray($generatedEnv);
if ($generatedEnv) {
$generatedEnv->value = $fqdn . ':' . $port;
$generatedEnv->value = $fqdn . $path;
$generatedEnv->save();
}
}
@ -127,17 +129,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$url = Url::fromString($fqdn);
$port = $url->getPort();
$path = $url->getPath();
$url = $url->getHost();
if ($generatedEnv) {
$url = Str::of($fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->value = $url . $path;
$generatedEnv->save();
}
if ($port) {
$variableName = $variableName . "_$port";
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $url . ':' . $port;
$generatedEnv->value = $url . $path;
$generatedEnv->save();
}
}
@ -146,6 +149,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$host = Url::fromString($fqdn);
$port = $host->getPort();
$url = $host->getHost();
$path = $host->getPath();
$host = $host->getScheme() . '://' . $host->getHost();
if ($port) {
$port_envs = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_FQDN_%_$port")->get();
@ -153,10 +157,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_' . $service_fqdn)->first();
if ($env) {
$env->value = $host;
$env->value = $host . $path;
$env->save();
}
$port_env->value = $host . ':' . $port;
$port_env->value = $host . $path;
$port_env->save();
}
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
@ -164,17 +168,17 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_');
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_URL_' . $service_url)->first();
if ($env) {
$env->value = $url;
$env->value = $url . $path;
$env->save();
}
$port_env_url->value = $url . ':' . $port;
$port_env_url->value = $url . $path;
$port_env_url->save();
}
} else {
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$fqdn = Url::fromString($fqdn);
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost() . $fqdn->getPath();
if ($generatedEnv) {
$generatedEnv->value = $fqdn;
$generatedEnv->save();
@ -182,7 +186,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$url = Url::fromString($fqdn);
$url = $url->getHost();
$url = $url->getHost() . $url->getPath();
if ($generatedEnv) {
$url = Str::of($fqdn)->after('://');
$generatedEnv->value = $url;

View File

@ -165,9 +165,12 @@ function get_latest_sentinel_version(): string
function get_latest_version_of_coolify(): string
{
try {
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
$versions = $response->json();
$versions = File::get(base_path('versions.json'));
$versions = json_decode($versions, true);
return data_get($versions, 'coolify.v4.version');
// $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
// $versions = $response->json();
// return data_get($versions, 'coolify.v4.version');
} catch (\Throwable $e) {
//throw $e;
ray($e->getMessage());
@ -462,24 +465,24 @@ function sslip(Server $server)
return "http://{$server->ip}.sslip.io";
}
function getServiceTemplates()
function get_service_templates(bool $force = false): Collection
{
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
} else {
if ($force) {
try {
$response = Http::retry(3, 50)->get(config('constants.services.official'));
if ($response->failed()) {
return collect([]);
}
$services = $response->json();
$services = collect($services)->sortKeys();
return collect($services);
} catch (\Throwable $e) {
$services = collect([]);
$services = File::get(base_path('templates/service-templates.json'));
return collect(json_decode($services))->sortKeys();
}
} else {
$services = File::get(base_path('templates/service-templates.json'));
return collect(json_decode($services))->sortKeys();
}
return $services;
}
function getResourceByUuid(string $uuid, ?int $teamId = null)
@ -573,6 +576,13 @@ function getTopLevelNetworks(Service|Application $resource)
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@ -615,6 +625,13 @@ function getTopLevelNetworks(Service|Application $resource)
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@ -639,9 +656,10 @@ function getTopLevelNetworks(Service|Application $resource)
return $topLevelNetworks->keys();
}
}
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, bool $is_pr = false)
{
// ray()->clearAll();
ray()->clearAll();
if ($resource->getMorphClass() === 'App\Models\Service') {
if ($resource->docker_compose_raw) {
try {
@ -649,7 +667,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
$allServices = getServiceTemplates();
$allServices = get_service_templates();
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$services = data_get($yaml, 'services');
@ -663,6 +681,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
}
$definedNetwork = collect([$resource->uuid]);
if ($topLevelVolumes->count() > 0) {
$tempTopLevelVolumes = collect([]);
foreach ($topLevelVolumes as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$tempTopLevelVolumes->put($volumeName, $volume);
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
// Workarounds for beta users.
if ($serviceName === 'registry') {
@ -761,6 +789,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@ -812,7 +847,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// default:
// ipv4_address: 192.168.203.254
// $networks->put($serviceNetwork, null);
ray($key);
$networks->put($key, $serviceNetwork);
}
}
@ -876,6 +910,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]
);
} else if ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
return $volume;
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
if (is_string($volume)) {
@ -1007,7 +1044,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id,
])->first();
if ($env) {
$env_url = Url::fromString($savedService->fqdn);
$env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) {
@ -1049,6 +1085,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
// if ($savedService->fqdn) {
// $savedServiceFqdn = Url::fromString($savedService->fqdn);
// $parsedFqdn = Url::fromString($fqdn);
// $savedServicePath = $savedServiceFqdn->getPath();
// $parsedFqdnPath = $parsedFqdn->getPath();
// if ($savedServicePath != $parsedFqdnPath) {
// $fqdn = $parsedFqdn->withPath($savedServicePath)->__toString();
// $foundEnv->value = $fqdn;
// $foundEnv->save();
// }
// }
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
@ -1153,7 +1200,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName
service_name: $serviceName,
image: data_get($service, 'image')
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $resource->destination->network,
@ -1163,7 +1211,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName
service_name: $serviceName,
image: data_get($service, 'image')
));
}
}
@ -1189,13 +1238,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if (!data_get($service, 'restart')) {
data_set($service, 'restart', RESTART_MODE);
}
if (data_get($service, 'restart') === 'no') {
if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) {
$savedService->update(['exclude_from_status' => true]);
}
data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
data_forget($service, 'volumes.*.is_directory');
data_forget($service, 'exclude_from_hc');
// Remove unnecessary variables from service.environment
// $withoutServiceEnvs = collect([]);
@ -1217,6 +1267,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
];
$yaml = data_forget($yaml, 'services.*.volumes.*.content');
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
$resource->save();
@ -1249,6 +1300,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0) {
$topLevelVolumes = collect([]);
}
if ($topLevelVolumes->count() > 0) {
$tempTopLevelVolumes = collect([]);
foreach ($topLevelVolumes as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$tempTopLevelVolumes->put($volumeName, $volume);
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$services = data_get($yaml, 'services');
@ -1312,13 +1375,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount");
$topLevelVolumes->put($name, [
'name' => $name,
]);
if (!$topLevelVolumes->has($name)) {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
if (!$topLevelVolumes->has($name->value())) {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
@ -1362,9 +1429,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_set($volume, 'source', $source . ':' . $target);
}
if (!str($source)->startsWith('/')) {
$topLevelVolumes->put($source, [
'name' => $source,
]);
if (!$topLevelVolumes->has($source)) {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
}
@ -1391,6 +1460,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
if ($networkName === 'default') {
continue;
}
// ignore alias
if ($networkDetails['aliases'] ?? false) {
continue;
}
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
@ -1642,13 +1718,15 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
uuid: $resource->uuid,
domains: $fqdns,
serviceLabels: $serviceLabels,
generate_unique_uuid: $resource->build_pack === 'dockercompose'
generate_unique_uuid: $resource->build_pack === 'dockercompose',
image: data_get($service, 'image')
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $resource->destination->network,
uuid: $resource->uuid,
domains: $fqdns,
serviceLabels: $serviceLabels
serviceLabels: $serviceLabels,
image: data_get($service, 'image')
));
}
}

View File

@ -14,16 +14,16 @@
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0",
"laravel/framework": "^v10.7.1",
"laravel/horizon": "^5.15",
"laravel/horizon": "^5.23.1",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1",
"laravel/socialite": "^5.12",
"laravel/socialite": "^v5.14.0",
"laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0",
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-sftp-v3": "^3.0",
"livewire/livewire": "^3.0",
"livewire/livewire": "3.4.9",
"lorisleiva/laravel-actions": "^2.7",
"nubs/random-name-generator": "^2.2",
"phpseclib/phpseclib": "~3.0",

1106
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -21,8 +21,8 @@
],
'services' => [
// Temporary disabled until cache is implemented
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
// 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
],
'limits' => [
'trial_period' => 0,

View File

@ -7,7 +7,7 @@
// 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.285',
'release' => '4.0.0-beta.294',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.285';
return '4.0.0-beta.294';

View File

@ -0,0 +1,30 @@
<?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('applications', function (Blueprint $table) {
$table->text('post_deployment_command')->nullable()->change();
$table->text('pre_deployment_command')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->string('post_deployment_command')->nullable()->change();
$table->string('pre_deployment_command')->nullable()->change();
});
}
};

View File

@ -0,0 +1,29 @@
<?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('applications', function (Blueprint $table) {
$table->string('manual_webhook_secret_gitea')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('applications', function (Blueprint $table) {
$table->dropColumn('manual_webhook_secret_gitea');
});
}
};

View File

@ -2,7 +2,7 @@ services:
coolify:
build:
context: .
dockerfile: ./docker/dev-ssu/Dockerfile
dockerfile: ./docker/dev/Dockerfile
ports:
- "${APP_PORT:-8000}:80"
environment:

View File

@ -13,6 +13,7 @@ services:
- /data/coolify/backups:/var/www/html/storage/app/backups
- /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance
environment:
- PHP_MEMORY_LIMIT
- APP_ID
- APP_ENV=production
- APP_DEBUG

View File

@ -18,9 +18,9 @@ RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
# Coolify requirements
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
COPY docker/dev-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc

View File

@ -31,10 +31,10 @@ RUN apt-get update
RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
# Coolify requirements
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
COPY docker/prod-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf
COPY --from=base --chown=9999:9999 /var/www/html .
@ -42,7 +42,7 @@ COPY --chown=9999:9999 . .
RUN composer dump-autoload
COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build
COPY --chmod=755 docker/prod-ssu/etc/s6-overlay/ /etc/s6-overlay/
COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/
RUN php artisan route:cache
RUN php artisan view:cache

Some files were not shown because too many files have changed in this diff Show More