Merge branch 'next' into feat/disable-default-redirect
This commit is contained in:
@@ -209,6 +209,8 @@ Files:
|
||||
];
|
||||
$command = array_merge($command, $add_envs_command, $restart_command);
|
||||
|
||||
StopLogDrain::run($server);
|
||||
|
||||
return instant_remote_process($command, $server);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
|
||||
@@ -9,25 +9,21 @@ class StartSentinel
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, bool $restart = false, bool $is_dev = false)
|
||||
public function handle(Server $server, bool $restart = false, ?string $latestVersion = null)
|
||||
{
|
||||
// TODO: Sentinel is not available in this version (soon).
|
||||
if (! $is_dev) {
|
||||
return;
|
||||
}
|
||||
$version = get_latest_sentinel_version();
|
||||
if ($server->isSwarm() || $server->isBuildServer()) {
|
||||
return;
|
||||
}
|
||||
if ($restart) {
|
||||
StopSentinel::run($server);
|
||||
}
|
||||
$metrics_history = data_get($server, 'settings.sentinel_metrics_history_days');
|
||||
$refresh_rate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
|
||||
$push_interval = data_get($server, 'settings.sentinel_push_interval_seconds');
|
||||
$version = $latestVersion ?? get_latest_sentinel_version();
|
||||
$metricsHistory = data_get($server, 'settings.sentinel_metrics_history_days');
|
||||
$refreshRate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
|
||||
$pushInterval = data_get($server, 'settings.sentinel_push_interval_seconds');
|
||||
$token = data_get($server, 'settings.sentinel_token');
|
||||
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
||||
$mount_dir = '/data/coolify/sentinel';
|
||||
$mountDir = '/data/coolify/sentinel';
|
||||
$image = "ghcr.io/coollabsio/sentinel:$version";
|
||||
if (! $endpoint) {
|
||||
throw new \Exception('You should set FQDN in Instance Settings.');
|
||||
@@ -35,26 +31,29 @@ class StartSentinel
|
||||
$environments = [
|
||||
'TOKEN' => $token,
|
||||
'PUSH_ENDPOINT' => $endpoint,
|
||||
'PUSH_INTERVAL_SECONDS' => $push_interval,
|
||||
'PUSH_INTERVAL_SECONDS' => $pushInterval,
|
||||
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
|
||||
'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate,
|
||||
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history,
|
||||
'COLLECTOR_REFRESH_RATE_SECONDS' => $refreshRate,
|
||||
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metricsHistory,
|
||||
];
|
||||
$labels = [
|
||||
'coolify.managed' => 'true',
|
||||
];
|
||||
if (isDev()) {
|
||||
// data_set($environments, 'DEBUG', 'true');
|
||||
$mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||
// $image = 'sentinel';
|
||||
$mountDir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||
}
|
||||
$docker_environments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
|
||||
|
||||
$docker_command = "docker run -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mount_dir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway $image";
|
||||
$dockerEnvironments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
|
||||
$dockerLabels = implode(' ', array_map(fn ($key, $value) => "$key=$value", array_keys($labels), $labels));
|
||||
$dockerCommand = "docker run -d $dockerEnvironments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mountDir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway --label $dockerLabels $image";
|
||||
|
||||
instant_remote_process([
|
||||
'docker rm -f coolify-sentinel || true',
|
||||
"mkdir -p $mount_dir",
|
||||
$docker_command,
|
||||
"chown -R 9999:root $mount_dir",
|
||||
"chmod -R 700 $mount_dir",
|
||||
"mkdir -p $mountDir",
|
||||
$dockerCommand,
|
||||
"chown -R 9999:root $mountDir",
|
||||
"chmod -R 700 $mountDir",
|
||||
], $server);
|
||||
|
||||
$server->settings->is_sentinel_enabled = true;
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Jobs\CheckHelperImageJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\PullTemplatesFromCDN;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\ServerCheckJob;
|
||||
@@ -23,11 +23,11 @@ use Illuminate\Support\Carbon;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
private $all_servers;
|
||||
private $allServers;
|
||||
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$this->all_servers = Server::all();
|
||||
$this->allServers = Server::all();
|
||||
$settings = instanceSettings();
|
||||
|
||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||
@@ -37,9 +37,9 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$this->checkScheduledBackups($schedule);
|
||||
$this->checkResources($schedule);
|
||||
$this->checkScheduledTasks($schedule);
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
|
||||
$schedule->command('telescope:prune')->daily();
|
||||
@@ -51,32 +51,27 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$this->schedule_updates($schedule);
|
||||
$this->scheduleUpdates($schedule);
|
||||
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->pull_images($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$this->checkScheduledBackups($schedule);
|
||||
$this->checkResources($schedule);
|
||||
$this->pullImages($schedule);
|
||||
$this->checkScheduledTasks($schedule);
|
||||
|
||||
$schedule->command('cleanup:database --yes')->daily();
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
private function pull_images($schedule)
|
||||
private function pullImages($schedule): void
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
$servers = $this->allServers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$schedule->job(function () use ($server) {
|
||||
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
if ($status !== 'running') {
|
||||
PullSentinelImageJob::dispatch($server);
|
||||
}
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
}
|
||||
}
|
||||
@@ -86,7 +81,7 @@ class Kernel extends ConsoleKernel
|
||||
->onOneServer();
|
||||
}
|
||||
|
||||
private function schedule_updates($schedule)
|
||||
private function scheduleUpdates($schedule): void
|
||||
{
|
||||
$settings = instanceSettings();
|
||||
|
||||
@@ -105,21 +100,21 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
}
|
||||
|
||||
private function check_resources($schedule)
|
||||
private function checkResources($schedule): void
|
||||
{
|
||||
if (isCloud()) {
|
||||
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||
$servers = $this->allServers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||
$own = Team::find(0)->servers;
|
||||
$servers = $servers->merge($own);
|
||||
} else {
|
||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||
$servers = $this->allServers->where('ip', '!=', '1.2.3.4');
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$last_sentinel_update = $server->sentinel_updated_at;
|
||||
if (Carbon::parse($last_sentinel_update)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||
$serverTimezone = $server->settings->server_timezone;
|
||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
$serverTimezone = $server->settings->server_timezone;
|
||||
if ($server->settings->force_docker_cleanup) {
|
||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||
} else {
|
||||
@@ -128,7 +123,7 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
}
|
||||
|
||||
private function check_scheduled_backups($schedule)
|
||||
private function checkScheduledBackups($schedule): void
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
@@ -161,7 +156,7 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
}
|
||||
|
||||
private function check_scheduled_tasks($schedule)
|
||||
private function checkScheduledTasks($schedule): void
|
||||
{
|
||||
$scheduled_tasks = ScheduledTask::all();
|
||||
if ($scheduled_tasks->isEmpty()) {
|
||||
|
||||
56
app/Jobs/CheckAndStartSentinelJob.php
Normal file
56
app/Jobs/CheckAndStartSentinelJob.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 120;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$latestVersion = get_latest_sentinel_version();
|
||||
|
||||
// Check if sentinel is running
|
||||
$sentinelFound = instant_remote_process(['docker inspect coolify-sentinel'], $this->server, false);
|
||||
$sentinelFoundJson = json_decode($sentinelFound, true);
|
||||
$sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited');
|
||||
if ($sentinelStatus !== 'running') {
|
||||
StartSentinel::run(server: $this->server, restart: true, latestVersion: $latestVersion);
|
||||
|
||||
return;
|
||||
}
|
||||
// If sentinel is running, check if it needs an update
|
||||
$runningVersion = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
||||
if (empty($runningVersion)) {
|
||||
$runningVersion = '0.0.0';
|
||||
}
|
||||
if ($latestVersion === '0.0.0' && $runningVersion === '0.0.0') {
|
||||
StartSentinel::run(server: $this->server, restart: true, latestVersion: 'latest');
|
||||
|
||||
return;
|
||||
} else {
|
||||
if (version_compare($runningVersion, $latestVersion, '<')) {
|
||||
StartSentinel::run(server: $this->server, restart: true, latestVersion: $latestVersion);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$version = get_latest_sentinel_version();
|
||||
if (! $version) {
|
||||
ray('Failed to get latest Sentinel version');
|
||||
|
||||
return;
|
||||
}
|
||||
$local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
||||
if (empty($local_version)) {
|
||||
$local_version = '0.0.0';
|
||||
}
|
||||
if (version_compare($local_version, $version, '<')) {
|
||||
StartSentinel::run($this->server, true);
|
||||
|
||||
return;
|
||||
}
|
||||
ray('Sentinel image is up to date');
|
||||
} catch (\Throwable $e) {
|
||||
// send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,6 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
$data = collect($this->data);
|
||||
|
||||
$this->serverStatus();
|
||||
|
||||
$this->server->sentinelHeartbeat();
|
||||
|
||||
$this->containers = collect(data_get($data, 'containers'));
|
||||
@@ -212,16 +210,6 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function serverStatus()
|
||||
{
|
||||
if ($this->server->isFunctional() === false) {
|
||||
throw new \Exception('Server is not ready.');
|
||||
}
|
||||
if ($this->server->status() === false) {
|
||||
throw new \Exception('Server is not reachable.');
|
||||
}
|
||||
}
|
||||
|
||||
private function updateApplicationStatus(string $applicationId, string $containerStatus)
|
||||
{
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
|
||||
@@ -43,22 +43,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
if ($this->server->serverStatus() === false) {
|
||||
return 'Server is not reachable or not ready.';
|
||||
}
|
||||
|
||||
$this->applications = $this->server->applications();
|
||||
$this->databases = $this->server->databases();
|
||||
$this->services = $this->server->services()->get();
|
||||
$this->previews = $this->server->previews();
|
||||
|
||||
$up = $this->serverStatus();
|
||||
if (! $up) {
|
||||
ray('Server is not reachable.');
|
||||
|
||||
return 'Server is not reachable.';
|
||||
}
|
||||
if (! $this->server->isFunctional()) {
|
||||
ray('Server is not ready.');
|
||||
|
||||
return 'Server is not ready.';
|
||||
}
|
||||
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||
if (is_null($this->containers)) {
|
||||
@@ -67,9 +60,14 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
ServerStorageCheckJob::dispatch($this->server);
|
||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
CheckAndStartSentinelJob::dispatch($this->server);
|
||||
}
|
||||
|
||||
if ($this->server->isLogDrainEnabled()) {
|
||||
$this->checkLogDrainContainer();
|
||||
}
|
||||
|
||||
if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||
@@ -106,39 +104,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function serverStatus()
|
||||
{
|
||||
['uptime' => $uptime] = $this->server->validateConnection(false);
|
||||
if ($uptime) {
|
||||
if ($this->server->unreachable_notification_sent === true) {
|
||||
$this->server->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
} else {
|
||||
// $this->server->team?->notify(new Unreachable($this->server));
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private function checkLogDrainContainer()
|
||||
{
|
||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int|string|null $disk_usage = null;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->server->isServerReady($this->tries)) {
|
||||
throw new \RuntimeException('Server is not ready.');
|
||||
}
|
||||
try {
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->remove_unnecessary_coolify_yaml();
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
$this->server->checkSentinel();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
||||
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) {
|
||||
$file = $this->server->proxyPath().'/dynamic/coolify.yaml';
|
||||
|
||||
return instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this->server, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
@@ -39,7 +38,7 @@ class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
if (is_null($this->percentage)) {
|
||||
$this->percentage = $this->server->storageCheck();
|
||||
Log::info('Server storage check percentage: '.$this->percentage);
|
||||
loggy('Server storage check percentage: '.$this->percentage);
|
||||
}
|
||||
if (! $this->percentage) {
|
||||
return 'No percentage could be retrieved.';
|
||||
|
||||
@@ -14,6 +14,18 @@ class Index extends Component
|
||||
|
||||
public $search = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! isCloud()) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
if (auth()->user()->id !== 0) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->getSubscribers();
|
||||
}
|
||||
|
||||
public function submitSearch()
|
||||
{
|
||||
if ($this->search !== '') {
|
||||
@@ -38,17 +50,6 @@ class Index extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! isCloud()) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (auth()->user()->id !== 0) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->getSubscribers();
|
||||
}
|
||||
|
||||
public function getSubscribers()
|
||||
{
|
||||
$this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
|
||||
|
||||
@@ -19,7 +19,7 @@ class NavbarDeleteTeam extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class BackupEdit extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class BackupExecutions extends Component
|
||||
|
||||
public function deleteBackup($executionId, $password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class FileStorage extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class ServiceApplicationView extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class Danger extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class Destination extends Component
|
||||
|
||||
public function removeServer(int $network_id, int $server_id, $password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class GetLogs extends Component
|
||||
|
||||
public ?bool $showTimeStamps = true;
|
||||
|
||||
public int $numberOfLines = 100;
|
||||
public ?int $numberOfLines = 100;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -98,7 +98,7 @@ class GetLogs extends Component
|
||||
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
|
||||
return;
|
||||
}
|
||||
if ($this->numberOfLines <= 0) {
|
||||
if ($this->numberOfLines <= 0 || is_null($this->numberOfLines)) {
|
||||
$this->numberOfLines = 1000;
|
||||
}
|
||||
if ($this->container) {
|
||||
|
||||
@@ -41,7 +41,7 @@ class Show extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class Delete extends Component
|
||||
|
||||
public function delete($password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -174,7 +174,21 @@ class Form extends Component
|
||||
$this->server->settings->refresh();
|
||||
|
||||
return handleError($e, $this);
|
||||
} finally {}
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
public function saveSentinel()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->server->settings->save();
|
||||
$this->dispatch('success', 'Sentinel updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->checkSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public function restartSentinel($notification = true)
|
||||
@@ -184,7 +198,8 @@ class Form extends Component
|
||||
$this->validate([
|
||||
'server.settings.sentinel_custom_url' => 'required|url',
|
||||
]);
|
||||
$this->server->restartSentinel();
|
||||
$this->server->settings->save();
|
||||
$this->server->restartSentinel(async: false);
|
||||
if ($notification) {
|
||||
$this->dispatch('success', 'Sentinel restarted.');
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ namespace App\Livewire\Settings;
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
@@ -185,8 +187,14 @@ class Index extends Component
|
||||
return view('livewire.settings.index');
|
||||
}
|
||||
|
||||
public function toggleTwoStepConfirmation()
|
||||
public function toggleTwoStepConfirmation($password)
|
||||
{
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->settings->disable_two_step_confirmation = true;
|
||||
$this->settings->save();
|
||||
$this->disable_two_step_confirmation = true;
|
||||
|
||||
@@ -28,6 +28,9 @@ class License extends Component
|
||||
if (! isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
if (! isInstanceAdmin()) {
|
||||
return redirect()->route('home');
|
||||
}
|
||||
$this->instance_id = config('app.id');
|
||||
$this->settings = instanceSettings();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ class SettingsOauth extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (! isInstanceAdmin()) {
|
||||
return redirect()->route('home');
|
||||
}
|
||||
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function ($carry, $setting) {
|
||||
$carry[$setting->provider] = $setting;
|
||||
|
||||
|
||||
@@ -7,19 +7,19 @@ use Livewire\Component;
|
||||
|
||||
class Deployments extends Component
|
||||
{
|
||||
public $deployments_per_tag_per_server = [];
|
||||
public $deploymentsPerTagPerServer = [];
|
||||
|
||||
public $resource_ids = [];
|
||||
public $resourceIds = [];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.tags.deployments');
|
||||
}
|
||||
|
||||
public function get_deployments()
|
||||
public function getDeployments()
|
||||
{
|
||||
try {
|
||||
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resource_ids)->get([
|
||||
$this->deploymentsPerTagPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('application_id', $this->resourceIds)->get([
|
||||
'id',
|
||||
'application_id',
|
||||
'application_name',
|
||||
@@ -29,7 +29,7 @@ class Deployments extends Component
|
||||
'server_id',
|
||||
'status',
|
||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||
$this->dispatch('deployments', $this->deployments_per_tag_per_server);
|
||||
$this->dispatch('deployments', $this->deploymentsPerTagPerServer);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ namespace App\Livewire\Tags;
|
||||
use App\Http\Controllers\Api\DeployController;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Title('Tags | Coolify')]
|
||||
class Index extends Component
|
||||
{
|
||||
#[Url()]
|
||||
@@ -21,33 +23,47 @@ class Index extends Component
|
||||
|
||||
public $webhook = null;
|
||||
|
||||
public $deployments_per_tag_per_server = [];
|
||||
public $deploymentsPerTagPerServer = [];
|
||||
|
||||
protected $listeners = ['deployments' => 'update_deployments'];
|
||||
protected $listeners = ['deployments' => 'updateDeployments'];
|
||||
|
||||
public function update_deployments($deployments)
|
||||
public function render()
|
||||
{
|
||||
$this->deployments_per_tag_per_server = $deployments;
|
||||
return view('livewire.tags.index');
|
||||
}
|
||||
|
||||
public function tag_updated()
|
||||
public function mount()
|
||||
{
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
|
||||
if ($this->tag) {
|
||||
$this->tagUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
public function updateDeployments($deployments)
|
||||
{
|
||||
$this->deploymentsPerTagPerServer = $deployments;
|
||||
}
|
||||
|
||||
public function tagUpdated()
|
||||
{
|
||||
if ($this->tag == '') {
|
||||
return;
|
||||
}
|
||||
$tag = $this->tags->where('name', $this->tag)->first();
|
||||
$sanitizedTag = htmlspecialchars($this->tag, ENT_QUOTES, 'UTF-8');
|
||||
$tag = $this->tags->where('name', $sanitizedTag)->first();
|
||||
if (! $tag) {
|
||||
$this->dispatch('error', "Tag ({$this->tag}) not found.");
|
||||
$this->dispatch('error', 'Tag ('.e($sanitizedTag).') not found.');
|
||||
$this->tag = '';
|
||||
|
||||
return;
|
||||
}
|
||||
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||
$this->webhook = generateTagDeployWebhook($tag->name);
|
||||
$this->applications = $tag->applications()->get();
|
||||
$this->services = $tag->services()->get();
|
||||
}
|
||||
|
||||
public function redeploy_all()
|
||||
public function redeployAll()
|
||||
{
|
||||
try {
|
||||
$this->applications->each(function ($resource) {
|
||||
@@ -63,17 +79,4 @@ class Index extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
|
||||
if ($this->tag) {
|
||||
$this->tag_updated();
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.tags.index');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace App\Livewire\Tags;
|
||||
use App\Http\Controllers\Api\DeployController;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Tag;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
#[Title('Tags | Coolify')]
|
||||
class Show extends Component
|
||||
{
|
||||
public $tags;
|
||||
@@ -28,7 +30,7 @@ class Show extends Component
|
||||
if (! $tag) {
|
||||
return redirect()->route('tags.index');
|
||||
}
|
||||
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||
$this->webhook = generateTagDeployWebhook($tag->name);
|
||||
$this->applications = $tag->applications()->get();
|
||||
$this->services = $tag->services()->get();
|
||||
$this->tag = $tag;
|
||||
|
||||
@@ -78,7 +78,7 @@ class AdminView extends Component
|
||||
|
||||
public function delete($id, $password)
|
||||
{
|
||||
if (! InstanceSettings::get('disable_two_step_confirmation')) {
|
||||
if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
|
||||
if (! Hash::check($password, Auth::user()->password)) {
|
||||
$this->addError('password', 'The provided password is incorrect.');
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ class InviteLink extends Component
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if (auth()->user()->role() === 'admin' && $this->role === 'owner') {
|
||||
throw new \Exception('Admins cannot invite owners.');
|
||||
}
|
||||
$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.'.');
|
||||
|
||||
@@ -4,10 +4,12 @@ namespace App\Livewire\Team;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class Member extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public User $member;
|
||||
|
||||
public function makeAdmin()
|
||||
|
||||
@@ -5,7 +5,9 @@ namespace App\Models;
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Notifications\Server\Reachable;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -61,6 +63,7 @@ class Server extends BaseModel
|
||||
$payload['ip'] = str($server->ip)->trim();
|
||||
}
|
||||
$server->forceFill($payload);
|
||||
|
||||
});
|
||||
static::created(function ($server) {
|
||||
ServerSetting::create([
|
||||
@@ -115,12 +118,15 @@ class Server extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
public $casts = [
|
||||
protected $casts = [
|
||||
'proxy' => SchemalessAttributes::class,
|
||||
'logdrain_axiom_api_key' => 'encrypted',
|
||||
'logdrain_newrelic_license_key' => 'encrypted',
|
||||
'delete_unused_volumes' => 'boolean',
|
||||
'delete_unused_networks' => 'boolean',
|
||||
'unreachable_notification_sent' => 'boolean',
|
||||
'is_build_server' => 'boolean',
|
||||
'force_disabled' => 'boolean',
|
||||
];
|
||||
|
||||
protected $schemalessAttributes = [
|
||||
@@ -139,11 +145,11 @@ class Server extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
||||
public function type()
|
||||
{
|
||||
return 'server';
|
||||
}
|
||||
|
||||
public static function isReachable()
|
||||
{
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true);
|
||||
@@ -514,16 +520,14 @@ $schema://$host {
|
||||
|
||||
public function forceEnableServer()
|
||||
{
|
||||
$this->settings->update([
|
||||
'force_disabled' => false,
|
||||
]);
|
||||
$this->settings->force_disabled = false;
|
||||
$this->settings->save();
|
||||
}
|
||||
|
||||
public function forceDisableServer()
|
||||
{
|
||||
$this->settings->update([
|
||||
'force_disabled' => true,
|
||||
]);
|
||||
$this->settings->force_disabled = true;
|
||||
$this->settings->save();
|
||||
$sshKeyFileLocation = "id.root@{$this->uuid}";
|
||||
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
|
||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||
@@ -570,21 +574,9 @@ $schema://$host {
|
||||
return $this->settings->is_sentinel_enabled;
|
||||
}
|
||||
|
||||
|
||||
public function checkSentinel()
|
||||
{
|
||||
// ray("Checking sentinel on server: {$this->name}");
|
||||
if ($this->isSentinelEnabled()) {
|
||||
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
if ($status !== 'running') {
|
||||
// ray('Sentinel is not running, starting it...');
|
||||
PullSentinelImageJob::dispatch($this);
|
||||
} else {
|
||||
// ray('Sentinel is running');
|
||||
}
|
||||
}
|
||||
CheckAndStartSentinelJob::dispatch($this);
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
@@ -631,72 +623,6 @@ $schema://$host {
|
||||
}
|
||||
}
|
||||
|
||||
public function isServerReady(int $tries = 3)
|
||||
{
|
||||
if ($this->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
$serverUptimeCheckNumber = $this->unreachable_count;
|
||||
if ($this->unreachable_count < $tries) {
|
||||
$serverUptimeCheckNumber = $this->unreachable_count + 1;
|
||||
}
|
||||
if ($this->unreachable_count > $tries) {
|
||||
$serverUptimeCheckNumber = $tries;
|
||||
}
|
||||
|
||||
$serverUptimeCheckNumberMax = $tries;
|
||||
|
||||
// ray('server: ' . $this->name);
|
||||
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
|
||||
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
|
||||
|
||||
['uptime' => $uptime] = $this->validateConnection();
|
||||
if ($uptime) {
|
||||
if ($this->unreachable_notification_sent === true) {
|
||||
$this->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
// Reached max number of retries
|
||||
if ($this->unreachable_notification_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
// $this->team?->notify(new Unreachable($this));
|
||||
$this->update(['unreachable_notification_sent' => true]);
|
||||
}
|
||||
if ($this->settings->is_reachable === true) {
|
||||
$this->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->applications() as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($this->databases() as $database) {
|
||||
$database->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($this->services()->get() 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']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->update([
|
||||
'unreachable_count' => $this->unreachable_count + 1,
|
||||
]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDiskUsage(): ?string
|
||||
{
|
||||
return instant_remote_process(['df / --output=pcent | tr -cd 0-9'], $this, false);
|
||||
@@ -1045,29 +971,43 @@ $schema://$host {
|
||||
return data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
|
||||
public function serverStatus(): bool
|
||||
{
|
||||
if ($this->status() === false) {
|
||||
return false;
|
||||
}
|
||||
if ($this->isFunctional() === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function status(): bool
|
||||
{
|
||||
if ($this->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
['uptime' => $uptime] = $this->validateConnection(false);
|
||||
if ($uptime) {
|
||||
if ($this->unreachable_notification_sent === true) {
|
||||
$this->update(['unreachable_notification_sent' => false]);
|
||||
if ($uptime === false) {
|
||||
foreach ($this->applications() as $application) {
|
||||
$application->status = 'exited';
|
||||
$application->save();
|
||||
}
|
||||
} else {
|
||||
// $this->server->team?->notify(new Unreachable($this->server));
|
||||
foreach ($this->applications as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
foreach ($this->databases() as $database) {
|
||||
$database->status = 'exited';
|
||||
$database->save();
|
||||
}
|
||||
foreach ($this->databases as $database) {
|
||||
$database->update(['status' => 'exited']);
|
||||
}
|
||||
foreach ($this->services as $service) {
|
||||
foreach ($this->services() as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
$app->update(['status' => 'exited']);
|
||||
$app->status = 'exited';
|
||||
$app->save();
|
||||
}
|
||||
foreach ($dbs as $db) {
|
||||
$db->update(['status' => 'exited']);
|
||||
$db->status = 'exited';
|
||||
$db->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,39 +1017,65 @@ $schema://$host {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isReachableChanged()
|
||||
{
|
||||
$this->refresh();
|
||||
$unreachableNotificationSent = (bool) $this->unreachable_notification_sent;
|
||||
$isReachable = (bool) $this->settings->is_reachable;
|
||||
loggy('Server setting is_reachable changed to '.$isReachable.' for server '.$this->id.'. Unreachable notification sent: '.$unreachableNotificationSent);
|
||||
// If the server is reachable, send the reachable notification if it was sent before
|
||||
if ($isReachable === true) {
|
||||
if ($unreachableNotificationSent === true) {
|
||||
$this->sendReachableNotification();
|
||||
}
|
||||
} else {
|
||||
// If the server is unreachable, send the unreachable notification if it was not sent before
|
||||
if ($unreachableNotificationSent === false) {
|
||||
$this->sendUnreachableNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function sendReachableNotification()
|
||||
{
|
||||
$this->unreachable_notification_sent = false;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Reachable($this));
|
||||
}
|
||||
|
||||
public function sendUnreachableNotification()
|
||||
{
|
||||
$this->unreachable_notification_sent = true;
|
||||
$this->save();
|
||||
$this->refresh();
|
||||
$this->team->notify(new Unreachable($this));
|
||||
}
|
||||
|
||||
public function validateConnection($isManualCheck = true)
|
||||
{
|
||||
config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
|
||||
// ray('Manual Check: ' . ($isManualCheck ? 'true' : 'false'));
|
||||
|
||||
$server = Server::find($this->id);
|
||||
if (! $server) {
|
||||
return ['uptime' => false, 'error' => 'Server not found.'];
|
||||
}
|
||||
if ($server->skipServer()) {
|
||||
if ($this->skipServer()) {
|
||||
return ['uptime' => false, 'error' => 'Server skipped.'];
|
||||
}
|
||||
try {
|
||||
// Make sure the private key is stored
|
||||
if ($server->privateKey) {
|
||||
$server->privateKey->storeInFileSystem();
|
||||
if ($this->privateKey) {
|
||||
$this->privateKey->storeInFileSystem();
|
||||
}
|
||||
instant_remote_process(['ls /'], $server);
|
||||
$server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
$server->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
if (data_get($server, 'unreachable_notification_sent') === true) {
|
||||
$server->update(['unreachable_notification_sent' => false]);
|
||||
instant_remote_process(['ls /'], $this);
|
||||
if ($this->settings->is_reachable === false) {
|
||||
$this->settings->is_reachable = true;
|
||||
$this->settings->save();
|
||||
}
|
||||
|
||||
return ['uptime' => true, 'error' => null];
|
||||
} catch (\Throwable $e) {
|
||||
$server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
if ($this->settings->is_reachable === true) {
|
||||
$this->settings->is_reachable = false;
|
||||
$this->settings->save();
|
||||
}
|
||||
|
||||
return ['uptime' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
@@ -1265,14 +1231,19 @@ $schema://$host {
|
||||
return str($this->ip)->contains(':');
|
||||
}
|
||||
|
||||
public function restartSentinel()
|
||||
public function restartSentinel(bool $async = true): void
|
||||
{
|
||||
try {
|
||||
StartSentinel::dispatch($this,true);
|
||||
if ($async) {
|
||||
StartSentinel::dispatch($this, true);
|
||||
} else {
|
||||
StartSentinel::run($this, true);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
loggy('Error restarting Sentinel: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function url()
|
||||
{
|
||||
return base_url().'/server/'.$this->uuid;
|
||||
|
||||
@@ -54,6 +54,8 @@ class ServerSetting extends Model
|
||||
'force_docker_cleanup' => 'boolean',
|
||||
'docker_cleanup_threshold' => 'integer',
|
||||
'sentinel_token' => 'encrypted',
|
||||
'is_reachable' => 'boolean',
|
||||
'is_usable' => 'boolean',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
@@ -70,15 +72,18 @@ class ServerSetting extends Model
|
||||
loggy('Error creating server setting: '.$e->getMessage());
|
||||
}
|
||||
});
|
||||
static::updated(function ($setting) {
|
||||
static::updated(function ($settings) {
|
||||
if (
|
||||
$setting->isDirty('sentinel_token') ||
|
||||
$setting->isDirty('sentinel_custom_url') ||
|
||||
$setting->isDirty('sentinel_metrics_refresh_rate_seconds') ||
|
||||
$setting->isDirty('sentinel_metrics_history_days') ||
|
||||
$setting->isDirty('sentinel_push_interval_seconds')
|
||||
$settings->isDirty('sentinel_token') ||
|
||||
$settings->isDirty('sentinel_custom_url') ||
|
||||
$settings->isDirty('sentinel_metrics_refresh_rate_seconds') ||
|
||||
$settings->isDirty('sentinel_metrics_history_days') ||
|
||||
$settings->isDirty('sentinel_push_interval_seconds')
|
||||
) {
|
||||
$setting->server->restartSentinel();
|
||||
$settings->server->restartSentinel();
|
||||
}
|
||||
if ($settings->isDirty('is_reachable')) {
|
||||
$settings->server->isReachableChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,12 +111,13 @@ class ServerSetting extends Model
|
||||
$domain = 'http://host.docker.internal:8000';
|
||||
} elseif ($settings->fqdn) {
|
||||
$domain = $settings->fqdn;
|
||||
} elseif ($settings->ipv4) {
|
||||
$domain = $settings->ipv4.':8000';
|
||||
} elseif ($settings->ipv6) {
|
||||
$domain = $settings->ipv6.':8000';
|
||||
} elseif ($settings->public_ipv4) {
|
||||
$domain = 'http://'.$settings->public_ipv4.':8000';
|
||||
} elseif ($settings->public_ipv6) {
|
||||
$domain = 'http://'.$settings->public_ipv6.':8000';
|
||||
}
|
||||
$this->sentinel_custom_url = $domain;
|
||||
loggy('Sentinel URL: '.$domain);
|
||||
if ($save) {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\Dto\DiscordMessage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
@@ -13,25 +11,28 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class Revived extends Notification implements ShouldQueue
|
||||
class Reachable extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
protected bool $isRateLimited = false;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
if ($this->server->unreachable_notification_sent === false) {
|
||||
return;
|
||||
}
|
||||
GetContainersStatus::dispatch($server)->onQueue('high');
|
||||
// dispatch(new ContainerStatusJob($server));
|
||||
$this->isRateLimited = isEmailRateLimited(
|
||||
limiterKey: 'server-reachable:'.$this->server->id,
|
||||
);
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
if ($this->isRateLimited) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
@@ -46,20 +47,8 @@ class Revived extends Notification implements ShouldQueue
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
$executed = RateLimiter::attempt(
|
||||
'notification-server-revived-'.$this->server->uuid,
|
||||
1,
|
||||
function () use ($channels) {
|
||||
return $channels;
|
||||
},
|
||||
7200,
|
||||
);
|
||||
|
||||
if (! $executed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $executed;
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -11,7 +11,6 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class Unreachable extends Notification implements ShouldQueue
|
||||
{
|
||||
@@ -19,10 +18,21 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
protected bool $isRateLimited = false;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->isRateLimited = isEmailRateLimited(
|
||||
limiterKey: 'server-unreachable:'.$this->server->id,
|
||||
);
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
if ($this->isRateLimited) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$channels = [];
|
||||
$isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
@@ -37,23 +47,11 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
$executed = RateLimiter::attempt(
|
||||
'notification-server-unreachable-'.$this->server->uuid,
|
||||
1,
|
||||
function () use ($channels) {
|
||||
return $channels;
|
||||
},
|
||||
7200,
|
||||
);
|
||||
|
||||
if (! $executed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $executed;
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
public function toMail(): ?MailMessage
|
||||
{
|
||||
$mail = new MailMessage;
|
||||
$mail->subject("Coolify: Your server ({$this->server->name}) is unreachable.");
|
||||
@@ -64,7 +62,7 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): DiscordMessage
|
||||
public function toDiscord(): ?DiscordMessage
|
||||
{
|
||||
$message = new DiscordMessage(
|
||||
title: ':cross_mark: Server unreachable',
|
||||
@@ -77,7 +75,7 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function toTelegram(): array
|
||||
public function toTelegram(): ?array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.",
|
||||
|
||||
Reference in New Issue
Block a user