Compare commits

...

29 Commits

Author SHA1 Message Date
Andras Bacsai
26668c71a1 Merge pull request #1460 from coollabsio/next
v4.0.0-beta.138
2023-11-16 15:28:48 +01:00
Andras Bacsai
bd7637c696 Add healthcheck URL to deployment job and update
version to beta.138
2023-11-16 15:23:07 +01:00
Andras Bacsai
cff54f48a3 Merge pull request #1459 from coollabsio/next
v4.0.0-beta.137
2023-11-16 14:39:48 +01:00
Andras Bacsai
5c0f239f62 Update server readiness check runtime to 1 minute 2023-11-16 14:36:43 +01:00
Andras Bacsai
d56c28c8d9 Remove unused notifications from
ContainerStatusJob
2023-11-16 14:29:23 +01:00
Andras Bacsai
2b666ff121 Refactor server and docker cleanup jobs 2023-11-16 14:29:01 +01:00
Andras Bacsai
fb42c43953 Add isLocalhost method to Server model and
conditionally show Cloudflare Tunnel checkbox in
server form view
2023-11-16 14:28:26 +01:00
Andras Bacsai
81437e6822 Fix high disk usage notification bug in
ServerStatusJob.php and HighDiskUsage.php
2023-11-16 13:49:08 +01:00
Andras Bacsai
2fe429fe92 Comment out logging configuration in
ApplicationDeploymentJob.php
2023-11-16 13:32:07 +01:00
Andras Bacsai
4f0b214042 Add timeout to ApplicationDeploymentJob 2023-11-16 13:27:51 +01:00
Andras Bacsai
c866213f34 fix: when to pull image 2023-11-16 13:22:12 +01:00
Andras Bacsai
7cec6330cf Update server status check and notifications 2023-11-16 11:53:37 +01:00
Andras Bacsai
f5de21a343 Add OTLP exporter and host metrics receiver
configuration to config.yaml.
2023-11-16 11:16:41 +01:00
Andras Bacsai
ecbfc4d790 Add Fluent Bit and New Relic configurations 2023-11-15 15:45:37 +01:00
Andras Bacsai
55ff00e028 Add logging configuration to compose file 2023-11-15 15:19:31 +01:00
Andras Bacsai
a0fc2bbb85 Merge pull request #1457 from coollabsio/next
v4.0.0-beta.136
2023-11-15 10:55:39 +01:00
Andras Bacsai
51a704b22a Remove middleware and uniqueId methods from
DockerCleanupJob
2023-11-15 10:37:55 +01:00
Andras Bacsai
6d49678842 Remove unnecessary echo and add alive message 2023-11-15 10:37:02 +01:00
Andras Bacsai
0459b3a115 Add init-script to prod-ssu Docker container 2023-11-15 10:31:48 +01:00
Andras Bacsai
82592c8222 Add alive request to Init command 2023-11-15 10:26:31 +01:00
Andras Bacsai
25bf8895e2 Add InstanceSettings to Init command 2023-11-15 10:20:48 +01:00
Andras Bacsai
f4f7bdf7d5 Update dependencies and add new feature 2023-11-15 10:18:41 +01:00
Andras Bacsai
c008564aa3 Merge pull request #1456 from coollabsio/next
v4.0.0-beta.135
2023-11-15 09:40:33 +01:00
Andras Bacsai
b825d98b2d Refactor storage connection handling and project
initialization
2023-11-15 09:34:27 +01:00
Andras Bacsai
1f711d9281 Update version and fix webhook generation 2023-11-15 09:15:49 +01:00
Andras Bacsai
1de850f640 Merge pull request #1453 from coollabsio/next
v4.0.0-beta.134
2023-11-14 19:31:24 +01:00
Andras Bacsai
f176247b02 Update application deployment and version numbers 2023-11-14 19:29:59 +01:00
Andras Bacsai
3f3a1283df Merge pull request #1452 from coollabsio/next
v4.0.0-beta.133
2023-11-14 15:07:15 +01:00
Andras Bacsai
087bfcad08 Update server model and version configurations 2023-11-14 15:06:03 +01:00
29 changed files with 429 additions and 169 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\InstanceSettings;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -14,6 +15,7 @@ use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class Init extends Command
@@ -23,7 +25,7 @@ class Init extends Command
public function handle()
{
ray()->clearAll();
$this->alive();
$cleanup = $this->option('cleanup');
if ($cleanup) {
$this->cleanup_stucked_resources();
@@ -31,7 +33,22 @@ class Init extends Command
}
$this->cleanup_in_progress_application_deployments();
}
private function alive()
{
$id = config('app.id');
$settings = InstanceSettings::get();
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
return;
}
try {
echo "I am alive!\n";
Http::get("https://get.coollabs.io/coolify/v4/alive?appId=$id");
} catch (\Throwable $e) {
echo "Error in alive: {$e->getMessage()}\n";
}
}
private function cleanup_ssh()
{
try {

View File

@@ -2,13 +2,12 @@
namespace App\Console;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
@@ -27,7 +26,6 @@ class Kernel extends ConsoleKernel
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->check_scheduled_backups($schedule);
$this->pull_helper_image($schedule);
} else {
@@ -40,7 +38,6 @@ class Kernel extends ConsoleKernel
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->cleanup_servers($schedule);
$this->pull_helper_image($schedule);
}
}
@@ -51,13 +48,6 @@ class Kernel extends ConsoleKernel
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
}
}
private function cleanup_servers($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($servers as $server) {
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
}
}
private function check_resources($schedule)
{
if (isCloud()) {
@@ -66,6 +56,7 @@ class Kernel extends ConsoleKernel
$servers = Server::all();
}
foreach ($servers as $server) {
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
}
}

View File

@@ -33,7 +33,7 @@ class Index extends Component
public ?string $remoteServerUser = 'root';
public ?Server $createdServer = null;
public Collection|array $projects = [];
public Collection $projects;
public ?int $selectedExistingProject = null;
public ?Project $createdProject = null;

View File

@@ -64,21 +64,10 @@ class Create extends Component
}
$this->storage->team_id = currentTeam()->id;
$this->storage->testConnection();
$this->storage->is_usable = true;
$this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function test_s3_connection()
{
try {
$this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
}

View File

@@ -32,7 +32,7 @@ class Form extends Component
public function test_s3_connection()
{
try {
$this->storage->testConnection();
$this->storage->testConnection(shouldSave: true);
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -53,10 +53,7 @@ class Form extends Component
{
$this->validate();
try {
$this->storage->testConnection();
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
$this->storage->save();
$this->emit('success', 'Storage settings saved.');
$this->test_s3_connection();
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -33,6 +33,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
public $timeout = 3600;
public static int $batch_counter = 0;
private int $application_deployment_queue_id;
@@ -73,6 +75,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private ?string $buildTarget = null;
private $log_model;
private Collection $saved_outputs;
private ?string $full_healthcheck_url = null;
private string $serverUser = 'root';
private string $serverUserHomeDir = '/root';
@@ -192,7 +195,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->customRepository = $this->application->git_repository;
}
try {
if ($this->restart_only) {
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
$this->just_restart();
} else if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile();
@@ -490,8 +493,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command(
[
"echo 'Waiting for healthcheck to pass on the new version of your application.'"
],
]
);
if ($this->full_healthcheck_url) {
$this->execute_remote_command(
[
"echo 'Healthcheck URL inside your container: {$this->full_healthcheck_url}'"
]
);
}
while ($counter < $this->application->health_check_retries) {
$this->execute_remote_command(
[
@@ -827,6 +837,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
'networks' => [
$this->destination->network,
],
// 'logging' => [
// 'driver' => 'fluentd',
// 'options' => [
// 'fluentd-async' => 'true',
// 'tag' => $this->application->name . '-' . $this->application->uuid
// ]
// ],
'healthcheck' => [
'test' => [
'CMD-SHELL',
@@ -951,10 +968,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$health_check_port = $this->application->health_check_port;
}
if ($this->application->health_check_path) {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
];
} else {
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
$generated_healthchecks_commands = [
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
];
@@ -1089,21 +1108,19 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function start_by_compose_file()
{
if (
!$this->application->dockerfile &&
(
$this->application->build_pack === 'dockerimage' ||
$this->application->build_pack === 'dockerfile')
) {
if ($this->application->build_pack === 'dockerimage') {
$this->execute_remote_command(
["echo -n 'Pulling latest images from the registry.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir}"), "hidden" => true],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
} else {
$this->execute_remote_command(
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
}
$this->execute_remote_command(
["echo -n 'Starting application (could take a while).'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
);
}
private function generate_build_env_variables()

View File

@@ -8,8 +8,6 @@ use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
@@ -41,76 +39,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
// ray("checking server status for {$this->server->id}");
try {
// ray()->clearAll();
$serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3;
// ray('checking # ' . $serverUptimeCheckNumber);
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->server->unreachable_email_sent === false) {
ray('Server unreachable, sending notification...');
$this->server->team->notify(new Unreachable($this->server));
$this->server->update(['unreachable_email_sent' => true]);
}
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => 0,
]);
// Update all applications, databases and services to exited
foreach ($this->server->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->server->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->server->services() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
return;
}
$result = $this->server->validateConnection();
if ($result) {
$this->server->settings()->update([
'is_reachable' => true,
]);
$this->server->update([
'unreachable_count' => 0,
]);
} else {
$serverUptimeCheckNumber++;
$this->server->settings()->update([
'is_reachable' => false,
]);
$this->server->update([
'unreachable_count' => $serverUptimeCheckNumber,
]);
return;
}
if (data_get($this->server, 'unreachable_email_sent') === true) {
ray('Server is reachable again, sending notification...');
$this->server->team->notify(new Revived($this->server));
$this->server->update(['unreachable_email_sent' => false]);
}
if (
data_get($this->server, 'settings.is_reachable') === false ||
data_get($this->server, 'settings.is_usable') === false
) {
$this->server->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
// $this->server->validateDockerEngine(true);
$this->server->checkServerRediness();
$containers = instant_remote_process(["docker container ls -q"], $this->server);
if (!$containers) {
return;

View File

@@ -434,7 +434,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
// $region = $this->s3->region;
$bucket = $this->s3->bucket;
$endpoint = $this->s3->endpoint;
$this->s3->testConnection();
$this->s3->testConnection(shouldSave: true);
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$network = $this->database->service->destination->network;
} else {

View File

@@ -3,6 +3,7 @@
namespace App\Jobs;
use App\Models\Server;
use App\Notifications\Server\HighDiskUsage;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -18,45 +19,35 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 300;
public ?string $dockerRootFilesystem = null;
public ?int $usageBefore = null;
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function handle(): void
{
$isInprogress = false;
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
if ($isInprogress) {
throw new Exception('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
}
try {
$isInprogress = false;
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
if ($isInprogress) {
throw new Exception('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
}
if (!$this->server->isFunctional()) {
return;
}
$this->dockerRootFilesystem = "/";
$this->usageBefore = $this->getFilesystemUsage();
$this->usageBefore = $this->server->getDiskUsage();
ray('Usage before: ' . $this->usageBefore);
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
ray('Cleaning up ' . $this->server->name);
instant_remote_process(['docker image prune -af'], $this->server);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
instant_remote_process(['docker builder prune -af'], $this->server);
$usageAfter = $this->getFilesystemUsage();
instant_remote_process(['docker image prune -af'], $this->server, false);
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server, false);
instant_remote_process(['docker builder prune -af'], $this->server, false);
$usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) {
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
@@ -74,9 +65,4 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function getFilesystemUsage()
{
return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use App\Notifications\Server\HighDiskUsage;
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\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?int $disk_usage = null;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
}
public function uniqueId(): int
{
return $this->server->id;
}
public function handle(): void
{
ray("checking server status for {$this->server->id}");
try {
$this->server->checkServerRediness();
$this->cleanup(notify: false);
} catch (\Throwable $e) {
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
handleError($e);
}
}
public function cleanup(bool $notify = false): void
{
$this->disk_usage = $this->server->getDiskUsage();
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
if ($notify) {
if ($this->server->high_disk_usage_notification_sent) {
ray('high disk usage notification already sent');
return;
} else {
$this->server->high_disk_usage_notification_sent = true;
$this->server->save();
$this->server->team->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
}
} else {
DockerCleanupJob::dispatchSync($this->server);
$this->cleanup(notify: true);
}
} else {
$this->server->high_disk_usage_notification_sent = false;
$this->server->save();
}
}
}

View File

@@ -36,14 +36,13 @@ class S3Storage extends BaseModel
return "{$this->endpoint}/{$this->bucket}";
}
public function testConnection()
public function testConnection(bool $shouldSave = false)
{
try {
set_s3_target($this);
Storage::disk('custom-s3')->files();
$this->unusable_email_sent = false;
$this->is_usable = true;
return;
} catch (\Throwable $e) {
$this->is_usable = false;
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
@@ -65,7 +64,9 @@ class S3Storage extends BaseModel
throw $e;
} finally {
$this->save();
if ($shouldSave) {
$this->save();
}
}
}
}

View File

@@ -4,8 +4,11 @@ namespace App\Models;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Sleep;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Illuminate\Support\Str;
@@ -109,11 +112,88 @@ class Server extends BaseModel
return $this->proxy->modelScope();
}
public function isLocalhost()
{
return $this->ip === 'host.docker.internal' || $this->id === 0;
}
public function checkServerRediness()
{
$serverUptimeCheckNumber = $this->unreachable_count;
$serverUptimeCheckNumberMax = 5;
$currentTime = now()->timestamp;
$runtime5Minutes = 1 * 60;
// Run for 1 minutes max and check every 5 seconds
while ($currentTime + $runtime5Minutes > now()->timestamp) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
if ($this->unreachable_notification_sent === false) {
ray('Server unreachable, sending notification...');
$this->team->notify(new Unreachable($this));
$this->update(['unreachable_notification_sent' => true]);
}
$this->settings()->update([
'is_reachable' => false,
]);
$this->update([
'unreachable_count' => 0,
]);
foreach ($this->applications() as $application) {
$application->update(['status' => 'exited']);
}
foreach ($this->databases() as $database) {
$database->update(['status' => 'exited']);
}
foreach ($this->services() as $service) {
$apps = $service->applications()->get();
$dbs = $service->databases()->get();
foreach ($apps as $app) {
$app->update(['status' => 'exited']);
}
foreach ($dbs as $db) {
$db->update(['status' => 'exited']);
}
}
throw new \Exception('Server is not reachable.');
}
$result = $this->validateConnection();
ray('validateConnection: ' . $result);
if (!$result) {
$serverUptimeCheckNumber++;
$this->update([
'unreachable_count' => $serverUptimeCheckNumber,
]);
Sleep::for(5)->seconds();
return;
}
$this->update([
'unreachable_count' => 0,
]);
if (data_get($this, 'unreachable_notification_sent') === true) {
ray('Server is reachable again, sending notification...');
$this->team->notify(new Revived($this));
$this->update(['unreachable_notification_sent' => false]);
}
if (
data_get($this, 'settings.is_reachable') === false ||
data_get($this, 'settings.is_usable') === false
) {
$this->settings()->update([
'is_reachable' => true,
'is_usable' => true
]);
}
break;
}
}
public function getDiskUsage()
{
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
}
public function hasDefinedResources()
{
$applications = $this->applications()->count() === 0;
$databases = $this->databases()->count() === 0;
$services = $this->services()->count() === 0;
$applications = $this->applications()->count() > 0;
$databases = $this->databases()->count() > 0;
$services = $this->services()->count() > 0;
if ($applications || $databases || $services) {
return true;
}
@@ -148,7 +228,7 @@ class Server extends BaseModel
if (isDev()) {
return '127.0.0.1';
}
if ($this->ip === 'host.docker.internal') {
if ($this->isLocalhost()) {
return base_ip();
}
return $this->ip;

View File

@@ -36,7 +36,7 @@ class ServiceDatabase extends BaseModel
{
$port = $this->public_port;
$realIp = $this->service->server->ip;
if ($realIp === 'host.docker.internal' || isDev()) {
if ($this->service->server->isLocalhost() || isDev()) {
$realIp = base_ip();
}
$url = "{$realIp}:{$port}";

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use App\Notifications\Channels\TelegramChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class HighDiskUsage extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage)
{
}
public function via(object $notifiable): array
{
$channels = [];
$isEmailEnabled = isEmailEnabled($notifiable);
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isEmailEnabled) {
$channels[] = EmailChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!");
$mail->view('emails.high-disk-usage', [
'name' => $this->server->name,
'disk_usage' => $this->disk_usage,
'threshold' => $this->cleanup_after_percentage,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup.";
return $message;
}
public function toTelegram(): array
{
return [
"message" => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/automated-cleanup."
];
}
}

View File

@@ -18,7 +18,7 @@ class Revived extends Notification implements ShouldQueue
public $tries = 1;
public function __construct(public Server $server)
{
if ($this->server->unreachable_email_sent === false) {
if ($this->server->unreachable_notification_sent === false) {
return;
}
}

View File

@@ -191,7 +191,7 @@ function refresh_server_connection(?PrivateKey $private_key = null)
// if (!$uptime) {
// $server->settings->is_reachable = false;
// $server->team->notify(new Unreachable($server));
// $server->unreachable_email_sent = true;
// $server->unreachable_notification_sent = true;
// $server->save();
// return [
// "uptime" => null,
@@ -213,9 +213,9 @@ function refresh_server_connection(?PrivateKey $private_key = null)
// $server->settings->is_usable = false;
// } else {
// $server->settings->is_usable = true;
// if (data_get($server, 'unreachable_email_sent') === true) {
// if (data_get($server, 'unreachable_notification_sent') === true) {
// $server->team->notify(new Revived($server));
// $server->unreachable_email_sent = false;
// $server->unreachable_notification_sent = false;
// $server->save();
// }
// }

View File

@@ -511,7 +511,7 @@ function generateDeployWebhook($resource)
return $url;
}
function generateGitManualWebhook($resource, $type) {
if ($resource->source_id !== 0) {
if ($resource->source_id !== 0 && !is_null($resource->source_id)) {
return null;
}
if ($resource->getMorphClass() === 'App\Models\Application') {

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.132',
'release' => '4.0.0-beta.138',
// 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.132';
return '4.0.0-beta.138';

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('servers', function (Blueprint $table) {
$table->boolean('high_disk_usage_notification_sent')->default(false);
$table->renameColumn('unreachable_email_sent', 'unreachable_notification_sent');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('servers', function (Blueprint $table) {
$table->dropColumn('high_disk_usage_notification_sent');
$table->renameColumn('unreachable_notification_sent', 'unreachable_email_sent');
});
}
};

View File

@@ -0,0 +1,16 @@
[SERVICE]
Flush 1
Daemon off
[INPUT]
Name forward
Buffer_Chunk_Size 1M
Buffer_Max_Size 6M
# [OUTPUT]
# Name nrlogs
# Match *
# license_key ${LICENSE_KEY}
# base_uri https://log-api.eu.newrelic.com/log/v1
[OUTPUT]
Name stdout
Match *

View File

@@ -0,0 +1,9 @@
version: '3'
services:
coolify-fluent-bit:
image: cr.fluentbit.io/fluent/fluent-bit:2.0
command: -c /fluent-bit.conf
volumes:
- ./fluent-bit.conf:/fluent-bit.conf
ports:
- 24224:24224

21
examples/newrelic.yaml Normal file
View File

@@ -0,0 +1,21 @@
version: '3'
services:
newrelic-infra:
container_name: newrelic-infra
image: newrelic/infrastructure:latest
networks:
- coolify
cap_add:
- SYS_PTRACE
privileged: true
pid: host
volumes:
- "/:/host:ro"
- "/var/run/docker.sock:/var/run/docker.sock"
- "newrelic-infra:/etc/newrelic-infra"
environment:
- NRIA_LICENSE_KEY=${NRIA_LICENSE_KEY}
- NRIA_DISPLAY_NAME=${HOSTNAME}
networks:
coolify:

34
examples/otl/config.yaml Normal file
View File

@@ -0,0 +1,34 @@
receivers:
hostmetrics:
collection_interval: 5s
scrapers:
cpu:
metrics:
system.cpu.utilization:
enabled: true
processors:
resourcedetection:
detectors: [env, system]
system:
hostname_sources: ["os"]
resource_attributes:
host.id:
enabled: true
batch:
memory_limiter:
check_interval: 1s
limit_mib: 1000
spike_limit_mib: 200
exporters:
debug:
verbosity: detailed
otlp:
endpoint: ${OTLP_ENDPOINT}
headers:
api-key: ${OTLP_API_KEY}
service:
pipelines:
metrics:
receivers: [hostmetrics]
processors: [memory_limiter, resourcedetection, batch]
exporters: [debug, otlp]

View File

@@ -0,0 +1,9 @@
<x-emails.layout>
Your server ({{ $name }}) has high disk usage ({{ $disk_usage }}% used). Threshold is {{ $threshold }}%.
Please cleanup your disk to prevent data-loss. Here are some [tips](https://coolify.io/docs/automated-cleanup).
(You can change the threshold in the Server Settings menu.)
</x-emails.layout>

View File

@@ -49,11 +49,13 @@
<x-forms.input type="number" id="server.port" label="Port" required />
</div>
</div>
<div class="w-64">
<x-forms.checkbox instantSave
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
</div>
@if (!$server->isLocalhost())
<div class="w-64">
<x-forms.checkbox instantSave
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
</div>
@endif
</div>
@if ($server->isFunctional())

View File

@@ -1,6 +1,6 @@
<div>
<h1>Create a new S3 Storage</h1>
<div class="subtitle ">S3 Storage used to save backups / files</div>
<div class="subtitle">S3 Storage used to save backups / files</div>
<form class="flex flex-col gap-2" wire:submit.prevent='submit'>
<div class="flex gap-2">
<x-forms.input label="Name" id="name" />

View File

@@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.132"
"version": "4.0.0-beta.138"
}
}
}