sentinel updates
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -16,11 +17,25 @@ class StartSentinel
|
|||||||
}
|
}
|
||||||
$metrics_history = $server->settings->metrics_history_days;
|
$metrics_history = $server->settings->metrics_history_days;
|
||||||
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
||||||
$token = $server->settings->metrics_token;
|
$token = $server->settings->sentinel_token;
|
||||||
instant_remote_process([
|
$fqdn = InstanceSettings::get()->fqdn;
|
||||||
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
if (str($fqdn)->startsWith('http')) {
|
||||||
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
|
throw new \Exception('You should use https to run Sentinel.');
|
||||||
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
|
}
|
||||||
], $server, true);
|
$environments = [
|
||||||
|
'TOKEN' => $token,
|
||||||
|
'ENDPOINT' => InstanceSettings::get()->fqdn,
|
||||||
|
'COLLECTOR_ENABLED' => 'true',
|
||||||
|
'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate,
|
||||||
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history
|
||||||
|
];
|
||||||
|
$docker_environments = "-e \"" . implode("\" -e \"", array_map(fn($key, $value) => "$key=$value", array_keys($environments), $environments)) . "\"";
|
||||||
|
ray($docker_environments);
|
||||||
|
return true;
|
||||||
|
// instant_remote_process([
|
||||||
|
// "docker run --rm --pull always -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/sentinel:/app/sentinel --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
||||||
|
// 'chown -R 9999:root /data/coolify/sentinel',
|
||||||
|
// 'chmod -R 700 /data/coolify/sentinel',
|
||||||
|
// ], $server, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ class ServersController extends Controller
|
|||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
}
|
}
|
||||||
$settings = $settings->makeHidden([
|
$settings = $settings->makeHidden([
|
||||||
'metrics_token',
|
'sentinel_token',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return serializeApiResponse($settings);
|
return serializeApiResponse($settings);
|
||||||
|
53
app/Jobs/PushServerUpdateJob.php
Normal file
53
app/Jobs/PushServerUpdateJob.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?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\Arr;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class PushServerUpdateJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 1;
|
||||||
|
|
||||||
|
public $timeout = 60;
|
||||||
|
|
||||||
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public Server $server, public $data) {}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if (!$this->data) {
|
||||||
|
throw new \Exception('No data provided');
|
||||||
|
}
|
||||||
|
$data = collect($this->data);
|
||||||
|
$containers = collect(data_get($data, 'containers'));
|
||||||
|
if ($containers->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerStatus = data_get($container, 'status', 'exited');
|
||||||
|
$containerHealth = data_get($container, 'health', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = collect(data_get($container, 'labels'));
|
||||||
|
if ($labels->has('coolify.applicationId')) {
|
||||||
|
$applicationId = $labels->get('coolify.applicationId');
|
||||||
|
}
|
||||||
|
Log::info("$applicationId, $containerStatus");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -7,6 +7,7 @@ use App\Actions\Server\StopSentinel;
|
|||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
@@ -54,9 +55,9 @@ class Form extends Component
|
|||||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||||
'server.settings.is_metrics_enabled' => 'required|boolean',
|
'server.settings.is_metrics_enabled' => 'required|boolean',
|
||||||
'server.settings.metrics_token' => 'required',
|
'server.settings.sentinel_token' => 'required',
|
||||||
'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1',
|
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'required|integer|min:1',
|
||||||
'server.settings.metrics_history_days' => 'required|integer|min:1',
|
'server.settings.sentinel_metrics_history_days' => 'required|integer|min:1',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
'server.settings.is_server_api_enabled' => 'required|boolean',
|
'server.settings.is_server_api_enabled' => 'required|boolean',
|
||||||
'server.settings.server_timezone' => 'required|string|timezone',
|
'server.settings.server_timezone' => 'required|string|timezone',
|
||||||
@@ -81,9 +82,9 @@ class Form extends Component
|
|||||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||||
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
||||||
'server.settings.is_metrics_enabled' => 'Metrics',
|
'server.settings.is_metrics_enabled' => 'Metrics',
|
||||||
'server.settings.metrics_token' => 'Metrics Token',
|
'server.settings.sentinel_token' => 'Metrics Token',
|
||||||
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
|
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'Metrics Interval',
|
||||||
'server.settings.metrics_history_days' => 'Metrics History',
|
'server.settings.sentinel_metrics_history_days' => 'Metrics History',
|
||||||
'server.settings.is_server_api_enabled' => 'Server API',
|
'server.settings.is_server_api_enabled' => 'Server API',
|
||||||
'server.settings.server_timezone' => 'Server Timezone',
|
'server.settings.server_timezone' => 'Server Timezone',
|
||||||
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
|
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
|
||||||
@@ -100,7 +101,15 @@ class Form extends Component
|
|||||||
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
|
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
|
||||||
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
|
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
|
||||||
}
|
}
|
||||||
|
public function regenerateSentinelToken() {
|
||||||
|
try {
|
||||||
|
$this->server->generateSentinelToken();
|
||||||
|
$this->server->settings->refresh();
|
||||||
|
$this->dispatch('success', 'Metrics token regenerated.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function updated($field)
|
public function updated($field)
|
||||||
{
|
{
|
||||||
if ($field === 'server.settings.docker_cleanup_frequency') {
|
if ($field === 'server.settings.docker_cleanup_frequency') {
|
||||||
@@ -174,6 +183,28 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPushData()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!isDev()) {
|
||||||
|
throw new \Exception('This feature is only available in dev mode.');
|
||||||
|
}
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'Authorization' => 'Bearer ' . $this->server->settings->sentinel_token,
|
||||||
|
])->post('http://host.docker.internal:8888/api/push', [
|
||||||
|
'data' => 'test',
|
||||||
|
]);
|
||||||
|
if ($response->successful()) {
|
||||||
|
$this->dispatch('success', 'Push data sent.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$error = data_get($response->json(), 'error');
|
||||||
|
throw new \Exception($error);
|
||||||
|
|
||||||
|
} catch(\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function restartSentinel()
|
public function restartSentinel()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@@ -1406,7 +1406,7 @@ class Application extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -21,6 +21,7 @@ class InstanceSettings extends Model implements SendsEmail
|
|||||||
'is_auto_update_enabled' => 'boolean',
|
'is_auto_update_enabled' => 'boolean',
|
||||||
'auto_update_frequency' => 'string',
|
'auto_update_frequency' => 'string',
|
||||||
'update_check_frequency' => 'string',
|
'update_check_frequency' => 'string',
|
||||||
|
'sentinel_token' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function fqdn(): Attribute
|
public function fqdn(): Attribute
|
||||||
|
@@ -17,6 +17,8 @@ use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
|||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
description: 'Server model',
|
description: 'Server model',
|
||||||
@@ -166,7 +168,7 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
public function setupDefault404Redirect()
|
public function setupDefault404Redirect()
|
||||||
{
|
{
|
||||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
$dynamic_conf_path = $this->proxyPath() . '/dynamic';
|
||||||
$proxy_type = $this->proxyType();
|
$proxy_type = $this->proxyType();
|
||||||
$redirect_url = $this->proxy->redirect_url;
|
$redirect_url = $this->proxy->redirect_url;
|
||||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||||
@@ -180,8 +182,8 @@ class Server extends BaseModel
|
|||||||
respond 404
|
respond 404
|
||||||
}';
|
}';
|
||||||
$conf =
|
$conf =
|
||||||
"# This file is automatically generated by Coolify.\n".
|
"# This file is automatically generated by Coolify.\n" .
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||||
$conf;
|
$conf;
|
||||||
$base64 = base64_encode($conf);
|
$base64 = base64_encode($conf);
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
@@ -243,8 +245,8 @@ respond 404
|
|||||||
];
|
];
|
||||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||||
$conf =
|
$conf =
|
||||||
"# This file is automatically generated by Coolify.\n".
|
"# This file is automatically generated by Coolify.\n" .
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||||
$conf;
|
$conf;
|
||||||
|
|
||||||
$base64 = base64_encode($conf);
|
$base64 = base64_encode($conf);
|
||||||
@@ -253,8 +255,8 @@ respond 404
|
|||||||
redir $redirect_url
|
redir $redirect_url
|
||||||
}";
|
}";
|
||||||
$conf =
|
$conf =
|
||||||
"# This file is automatically generated by Coolify.\n".
|
"# This file is automatically generated by Coolify.\n" .
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||||
$conf;
|
$conf;
|
||||||
$base64 = base64_encode($conf);
|
$base64 = base64_encode($conf);
|
||||||
}
|
}
|
||||||
@@ -272,7 +274,7 @@ respond 404
|
|||||||
public function setupDynamicProxyConfiguration()
|
public function setupDynamicProxyConfiguration()
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$dynamic_config_path = $this->proxyPath().'/dynamic';
|
$dynamic_config_path = $this->proxyPath() . '/dynamic';
|
||||||
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
|
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||||
$file = "$dynamic_config_path/coolify.yaml";
|
$file = "$dynamic_config_path/coolify.yaml";
|
||||||
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
|
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
|
||||||
@@ -391,8 +393,8 @@ respond 404
|
|||||||
}
|
}
|
||||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||||
$yaml =
|
$yaml =
|
||||||
"# This file is automatically generated by Coolify.\n".
|
"# This file is automatically generated by Coolify.\n" .
|
||||||
"# Do not edit it manually (only if you know what are you doing).\n\n".
|
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||||
$yaml;
|
$yaml;
|
||||||
|
|
||||||
$base64 = base64_encode($yaml);
|
$base64 = base64_encode($yaml);
|
||||||
@@ -456,13 +458,13 @@ $schema://$host {
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
|
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/caddy';
|
||||||
} else {
|
} else {
|
||||||
$proxy_path = $proxy_path.'/caddy';
|
$proxy_path = $proxy_path . '/caddy';
|
||||||
}
|
}
|
||||||
} elseif ($proxyType === ProxyTypes::NGINX->value) {
|
} elseif ($proxyType === ProxyTypes::NGINX->value) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
|
$proxy_path = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/nginx';
|
||||||
} else {
|
} else {
|
||||||
$proxy_path = $proxy_path.'/nginx';
|
$proxy_path = $proxy_path . '/nginx';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,6 +527,17 @@ $schema://$host {
|
|||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generateSentinelToken()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'server_uuid' => $this->uuid,
|
||||||
|
];
|
||||||
|
$token = json_encode($data);
|
||||||
|
$encrypted = encrypt($token);
|
||||||
|
$this->settings->sentinel_token = $encrypted;
|
||||||
|
$this->settings->save();
|
||||||
|
return $encrypted;
|
||||||
|
}
|
||||||
public function isSentinelEnabled()
|
public function isSentinelEnabled()
|
||||||
{
|
{
|
||||||
return $this->isMetricsEnabled() || $this->isServerApiEnabled();
|
return $this->isMetricsEnabled() || $this->isServerApiEnabled();
|
||||||
@@ -555,7 +568,6 @@ $schema://$host {
|
|||||||
ray($process->exitCode(), $process->output(), $process->errorOutput());
|
ray($process->exitCode(), $process->output(), $process->errorOutput());
|
||||||
throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
|
throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,7 +591,7 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
if ($this->isMetricsEnabled()) {
|
if ($this->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
||||||
if (str($cpu)->contains('error')) {
|
if (str($cpu)->contains('error')) {
|
||||||
$error = json_decode($cpu, true);
|
$error = json_decode($cpu, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
@@ -606,7 +618,7 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
if ($this->isMetricsEnabled()) {
|
if ($this->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
|
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
|
||||||
if (str($memory)->contains('error')) {
|
if (str($memory)->contains('error')) {
|
||||||
$error = json_decode($memory, true);
|
$error = json_decode($memory, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -35,9 +35,9 @@ use OpenApi\Attributes as OA;
|
|||||||
'logdrain_highlight_project_id' => ['type' => 'string'],
|
'logdrain_highlight_project_id' => ['type' => 'string'],
|
||||||
'logdrain_newrelic_base_uri' => ['type' => 'string'],
|
'logdrain_newrelic_base_uri' => ['type' => 'string'],
|
||||||
'logdrain_newrelic_license_key' => ['type' => 'string'],
|
'logdrain_newrelic_license_key' => ['type' => 'string'],
|
||||||
'metrics_history_days' => ['type' => 'integer'],
|
'sentinel_metrics_history_days' => ['type' => 'integer'],
|
||||||
'metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
'sentinel_metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
||||||
'metrics_token' => ['type' => 'string'],
|
'sentinel_token' => ['type' => 'string'],
|
||||||
'docker_cleanup_frequency' => ['type' => 'string'],
|
'docker_cleanup_frequency' => ['type' => 'string'],
|
||||||
'docker_cleanup_threshold' => ['type' => 'integer'],
|
'docker_cleanup_threshold' => ['type' => 'integer'],
|
||||||
'server_id' => ['type' => 'integer'],
|
'server_id' => ['type' => 'integer'],
|
||||||
@@ -53,6 +53,7 @@ class ServerSetting extends Model
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'force_docker_cleanup' => 'boolean',
|
'force_docker_cleanup' => 'boolean',
|
||||||
'docker_cleanup_threshold' => 'integer',
|
'docker_cleanup_threshold' => 'integer',
|
||||||
|
'sentinel_token' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
|
@@ -272,7 +272,7 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -272,7 +272,7 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -272,7 +272,7 @@ class StandaloneKeydb extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -272,7 +272,7 @@ class StandaloneMariadb extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -292,7 +292,7 @@ class StandaloneMongodb extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -273,7 +273,7 @@ class StandaloneMysql extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -274,7 +274,7 @@ class StandalonePostgresql extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -268,7 +268,7 @@ class StandaloneRedis extends BaseModel
|
|||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
@@ -1338,13 +1338,6 @@ function isAnyDeploymentInprogress()
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSentinelToken()
|
|
||||||
{
|
|
||||||
$token = Str::random(64);
|
|
||||||
|
|
||||||
return $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBase64Encoded($strValue)
|
function isBase64Encoded($strValue)
|
||||||
{
|
{
|
||||||
return base64_encode(base64_decode($strValue, true)) === $strValue;
|
return base64_encode(base64_decode($strValue, true)) === $strValue;
|
||||||
|
@@ -18,7 +18,7 @@ return new class extends Migration
|
|||||||
$table->boolean('is_metrics_enabled')->default(false);
|
$table->boolean('is_metrics_enabled')->default(false);
|
||||||
$table->integer('metrics_refresh_rate_seconds')->default(5);
|
$table->integer('metrics_refresh_rate_seconds')->default(5);
|
||||||
$table->integer('metrics_history_days')->default(30);
|
$table->integer('metrics_history_days')->default(30);
|
||||||
$table->string('metrics_token')->default(generateSentinelToken());
|
$table->string('metrics_token')->nullable();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
<?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('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('metrics_token');
|
||||||
|
$table->dropColumn('metrics_refresh_rate_seconds');
|
||||||
|
$table->dropColumn('metrics_history_days');
|
||||||
|
$table->text('sentinel_token')->nullable();
|
||||||
|
$table->integer('sentinel_metrics_refresh_rate_seconds')->default(5);
|
||||||
|
$table->integer('sentinel_metrics_history_days')->default(30);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->string('metrics_token')->nullable();
|
||||||
|
$table->integer('metrics_refresh_rate_seconds')->default(5);
|
||||||
|
$table->integer('metrics_history_days')->default(30);
|
||||||
|
$table->dropColumn('sentinel_token');
|
||||||
|
$table->dropColumn('sentinel_metrics_refresh_rate_seconds');
|
||||||
|
$table->dropColumn('sentinel_metrics_history_days');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -4981,11 +4981,11 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
logdrain_newrelic_license_key:
|
logdrain_newrelic_license_key:
|
||||||
type: string
|
type: string
|
||||||
metrics_history_days:
|
sentinel_metrics_refresh_rate_seconds:
|
||||||
type: integer
|
type: integer
|
||||||
metrics_refresh_rate_seconds:
|
sentinel_metrics_history_days:
|
||||||
type: integer
|
type: integer
|
||||||
metrics_token:
|
sentinel_token:
|
||||||
type: string
|
type: string
|
||||||
docker_cleanup_frequency:
|
docker_cleanup_frequency:
|
||||||
type: string
|
type: string
|
||||||
|
@@ -68,7 +68,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 w-full lg:flex-row">
|
<div class="flex flex-col gap-2 w-full lg:flex-row">
|
||||||
<x-forms.input type="password" id="server.ip" label="IP Address/Domain"
|
<x-forms.input type="password" id="server.ip" label="IP Address/Domain"
|
||||||
helper="An IP Address (127.0.0.1) or domain (example.com). Make sure there is no protocol like http(s):// so you provide a FQDN not a URL." required />
|
helper="An IP Address (127.0.0.1) or domain (example.com). Make sure there is no protocol like http(s):// so you provide a FQDN not a URL."
|
||||||
|
required />
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input id="server.user" label="User" required />
|
<x-forms.input id="server.user" label="User" required />
|
||||||
<x-forms.input type="number" id="server.port" label="Port" required />
|
<x-forms.input type="number" id="server.port" label="Port" required />
|
||||||
@@ -94,7 +95,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="inline-flex relative items-center w-64">
|
<div class="inline-flex relative items-center w-64">
|
||||||
<input autocomplete="off" wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
<input autocomplete="off"
|
||||||
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
||||||
@focus="open = true" @click.away="open = false" @input="open = true" class="w-full input"
|
@focus="open = true" @click.away="open = false" @input="open = true" class="w-full input"
|
||||||
:placeholder="placeholder" wire:model.debounce.300ms="server.settings.server_timezone">
|
:placeholder="placeholder" wire:model.debounce.300ms="server.settings.server_timezone">
|
||||||
@@ -129,23 +131,32 @@
|
|||||||
</div>
|
</div>
|
||||||
@if ($server->settings->is_cloudflare_tunnel)
|
@if ($server->settings->is_cloudflare_tunnel)
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
|
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel"
|
||||||
|
label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
@elseif (!$server->isFunctional())
|
@elseif (!$server->isFunctional())
|
||||||
<div class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
|
<div
|
||||||
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please validate your server first.</span> Then you will need a Cloudflare token and an SSH domain configured.
|
class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
|
||||||
<br/>
|
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please
|
||||||
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please click <span wire:click="manualCloudflareConfig" class="underline cursor-pointer">here</span>, then you should validate the server.
|
validate your server first.</span> Then you will need a Cloudflare token and an SSH
|
||||||
<br/><br/>
|
domain configured.
|
||||||
For more information, please read our <a href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank" class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
|
<br />
|
||||||
|
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please
|
||||||
|
click <span wire:click="manualCloudflareConfig"
|
||||||
|
class="underline cursor-pointer">here</span>, then you should validate the server.
|
||||||
|
<br /><br />
|
||||||
|
For more information, please read our <a
|
||||||
|
href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank"
|
||||||
|
class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
|
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
|
||||||
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels" class="w-full" :closeOutside="false">
|
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels"
|
||||||
|
class="w-full" :closeOutside="false">
|
||||||
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
@endif
|
@endif
|
||||||
@if ($server->isFunctional() &&!$server->settings->is_cloudflare_tunnel)
|
@if ($server->isFunctional() && !$server->settings->is_cloudflare_tunnel)
|
||||||
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
|
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
|
||||||
I have configured Cloudflare Tunnels manually
|
I have configured Cloudflare Tunnels manually
|
||||||
</div>
|
</div>
|
||||||
@@ -201,57 +212,58 @@
|
|||||||
</ul>"
|
</ul>"
|
||||||
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
||||||
</div>
|
</div>
|
||||||
<x-modal-confirmation
|
<x-modal-confirmation title="Confirm Docker Cleanup?" buttonTitle="Trigger Docker Cleanup"
|
||||||
title="Confirm Docker Cleanup?"
|
submitAction="manualCleanup" :actions="[
|
||||||
buttonTitle="Trigger Docker Cleanup"
|
|
||||||
submitAction="manualCleanup"
|
|
||||||
:actions="[
|
|
||||||
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
|
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
|
||||||
'Permanently deletes all unused images',
|
'Permanently deletes all unused images',
|
||||||
'Clears build cache',
|
'Clears build cache',
|
||||||
'Removes old versions of the Coolify helper image',
|
'Removes old versions of the Coolify helper image',
|
||||||
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
|
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
|
||||||
'Optionally permanently deletes all unused networks (if enabled in advanced options).'
|
'Optionally permanently deletes all unused networks (if enabled in advanced options).',
|
||||||
]"
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
:confirmWithText="false"
|
step2ButtonText="Trigger Docker Cleanup" />
|
||||||
:confirmWithPassword="false"
|
|
||||||
step2ButtonText="Trigger Docker Cleanup"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
@if ($server->settings->force_docker_cleanup)
|
@if ($server->settings->force_docker_cleanup)
|
||||||
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
||||||
label="Docker cleanup frequency" required
|
label="Docker cleanup frequency" required
|
||||||
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
||||||
@else
|
@else
|
||||||
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
||||||
label="Docker cleanup threshold (%)" required
|
label="Docker cleanup threshold (%)" required
|
||||||
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
||||||
@endif
|
@endif
|
||||||
<div x-data="{ open: false }" class="mt-4 max-w-md">
|
<div x-data="{ open: false }" class="mt-4 max-w-md">
|
||||||
<button @click="open = !open" type="button" class="flex items-center justify-between w-full text-left text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
|
<button @click="open = !open" type="button"
|
||||||
|
class="flex items-center justify-between w-full text-left text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
|
||||||
<span>Advanced Options</span>
|
<span>Advanced Options</span>
|
||||||
<svg :class="{'rotate-180': open}" class="w-5 h-5 transition-transform duration-200" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
<svg :class="{ 'rotate-180': open }" class="w-5 h-5 transition-transform duration-200"
|
||||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div x-show="open" class="mt-2 space-y-2">
|
<div x-show="open" class="mt-2 space-y-2">
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2"><strong>Warning: Enable these options only if you fully understand their implications and consequences!</strong><br>Improper use will result in data loss and could cause functional issues.</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2"><strong>Warning: Enable these
|
||||||
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes" label="Delete Unused Volumes"
|
options only if you fully understand their implications and
|
||||||
|
consequences!</strong><br>Improper use will result in data loss and could cause
|
||||||
|
functional issues.</p>
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes"
|
||||||
|
label="Delete Unused Volumes"
|
||||||
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
|
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
|
||||||
<ul class='list-disc pl-4 mt-2'>
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
|
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
|
||||||
<li>Data from stopped containers volumes will be permanently lost.</li>
|
<li>Data from stopped containers volumes will be permanently lost.</li>
|
||||||
<li>No way to recover deleted volume data.</li>
|
<li>No way to recover deleted volume data.</li>
|
||||||
</ul>"
|
</ul>" />
|
||||||
/>
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks"
|
||||||
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks" label="Delete Unused Networks"
|
label="Delete Unused Networks"
|
||||||
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
|
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
|
||||||
<ul class='list-disc pl-4 mt-2'>
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
|
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
|
||||||
<li>Custom networks for stopped containers will be permanently deleted.</li>
|
<li>Custom networks for stopped containers will be permanently deleted.</li>
|
||||||
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
|
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
|
||||||
</ul>"
|
</ul>" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -269,21 +281,30 @@
|
|||||||
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
|
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
|
||||||
{{-- @endif --}}
|
{{-- @endif --}}
|
||||||
</div>
|
</div>
|
||||||
<div>Metrics are disabled until a few bugs are fixed.</div>
|
@if (isDev())
|
||||||
{{-- <div class="w-64">
|
<x-forms.button wire:click="getPushData">Get Push Data</x-forms.button>
|
||||||
|
{{-- <div class="w-64">
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" />
|
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" />
|
||||||
</div>
|
<x-forms.button>Start Sentinel</x-forms.button>
|
||||||
<div class="pt-4">
|
</div> --}}
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input type="password" id="server.settings.metrics_token" label="Metrics token" required
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap items-end">
|
||||||
helper="Token for collector (Sentinel)." />
|
<x-forms.input type="password" id="server.settings.sentinel_token" label="Metrics token"
|
||||||
<x-forms.input id="server.settings.metrics_refresh_rate_seconds" label="Metrics rate (seconds)"
|
required helper="Token for collector (Sentinel)." />
|
||||||
required
|
<x-forms.button wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
||||||
helper="The interval for gathering metrics. Lower means more disk space will be used." />
|
</div>
|
||||||
<x-forms.input id="server.settings.metrics_history_days" label="Metrics history (days)" required
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||||
helper="How many days should the metrics data should be reserved." />
|
<x-forms.input id="server.settings.sentinel_metrics_refresh_rate_seconds"
|
||||||
|
label="Metrics rate (seconds)" required
|
||||||
|
helper="The interval for gathering metrics. Lower means more disk space will be used." />
|
||||||
|
<x-forms.input id="server.settings.sentinel_metrics_history_days"
|
||||||
|
label="Metrics history (days)" required
|
||||||
|
helper="How many days should the metrics data should be reserved." />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> --}}
|
@else
|
||||||
|
<div>Metrics are disabled until a few bugs are fixed.</div>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -13,6 +13,9 @@ use App\Http\Controllers\Api\TeamController;
|
|||||||
use App\Http\Middleware\ApiAllowed;
|
use App\Http\Middleware\ApiAllowed;
|
||||||
use App\Http\Middleware\IgnoreReadOnlyApiToken;
|
use App\Http\Middleware\IgnoreReadOnlyApiToken;
|
||||||
use App\Http\Middleware\OnlyRootApiToken;
|
use App\Http\Middleware\OnlyRootApiToken;
|
||||||
|
use App\Jobs\PushServerUpdateJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/health', [OtherController::class, 'healthcheck']);
|
Route::get('/health', [OtherController::class, 'healthcheck']);
|
||||||
@@ -129,6 +132,28 @@ Route::group([
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'prefix' => 'v1',
|
||||||
|
], function () {
|
||||||
|
Route::post('/sentinel/push', function () {
|
||||||
|
$token = request()->header('Authorization');
|
||||||
|
if (!$token) {
|
||||||
|
return response()->json(['message' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
$naked_token = str_replace('Bearer ', '', $token);
|
||||||
|
$decrypted = decrypt($naked_token);
|
||||||
|
$decrypted_token = json_decode($decrypted, true);
|
||||||
|
$server_uuid = data_get($decrypted_token, 'server_uuid');
|
||||||
|
$server = Server::where('uuid', $server_uuid)->first();
|
||||||
|
if (!$server) {
|
||||||
|
return response()->json(['message' => 'Server not found'], 404);
|
||||||
|
}
|
||||||
|
$data = request()->all();
|
||||||
|
PushServerUpdateJob::dispatch($server, $data);
|
||||||
|
return response()->json(['message' => 'ok'], 200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Route::any('/{any}', function () {
|
Route::any('/{any}', function () {
|
||||||
return response()->json(['message' => 'Not found.', 'docs' => 'https://coolify.io/docs'], 404);
|
return response()->json(['message' => 'Not found.', 'docs' => 'https://coolify.io/docs'], 404);
|
||||||
})->where('any', '.*');
|
})->where('any', '.*');
|
||||||
|
@@ -13,7 +13,7 @@ DOCKER_VERSION="26.0"
|
|||||||
# TODO: Ask for a user
|
# TODO: Ask for a user
|
||||||
CURRENT_USER=$USER
|
CURRENT_USER=$USER
|
||||||
|
|
||||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,sentinel}
|
||||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||||
mkdir -p /data/coolify/proxy/dynamic
|
mkdir -p /data/coolify/proxy/dynamic
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user