Replace hard validation error with self-healing token logic. Tokens that are null, empty, or fail decryption are now regenerated automatically rather than crashing sentinel startup or metrics reads. Token format changed from encrypted JSON payload to a plain 64-char random string (Str::random), eliminating double-encryption issues and simplifying the validation regex to cover the new character set. New `ensureValidSentinelToken()` method on ServerSetting centralises the get-or-regenerate contract; both StartSentinel and HasMetrics now delegate to it. HasMetrics logs a warning when regeneration occurs so operators know a sentinel container restart is required. `isValidSentinelToken()` now accepts `?string` (null → false). Adds feature tests covering: null/empty/undecryptable stored values, idempotent return of valid tokens, RuntimeException only when regeneration itself produces an invalid token, no double-encryption of newly generated tokens, and cast round-trip consistency.
71 lines
3.1 KiB
PHP
71 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Server;
|
|
|
|
use App\Events\SentinelRestarted;
|
|
use App\Models\Server;
|
|
use Lorisleiva\Actions\Concerns\AsAction;
|
|
|
|
class StartSentinel
|
|
{
|
|
use AsAction;
|
|
|
|
public function handle(Server $server, bool $restart = false, ?string $latestVersion = null, ?string $customImage = null)
|
|
{
|
|
if ($server->isSwarm() || $server->isBuildServer()) {
|
|
return;
|
|
}
|
|
if ($restart) {
|
|
StopSentinel::run($server);
|
|
}
|
|
$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 = $server->settings->ensureValidSentinelToken();
|
|
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
|
$debug = data_get($server, 'settings.is_sentinel_debug_enabled');
|
|
$mountDir = '/data/coolify/sentinel';
|
|
$image = config('constants.coolify.registry_url').'/coollabsio/sentinel:'.$version;
|
|
if (! $endpoint) {
|
|
throw new \RuntimeException('You should set FQDN in Instance Settings.');
|
|
}
|
|
$environments = [
|
|
'TOKEN' => $token,
|
|
'DEBUG' => $debug ? 'true' : 'false',
|
|
'PUSH_ENDPOINT' => $endpoint,
|
|
'PUSH_INTERVAL_SECONDS' => $pushInterval,
|
|
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
|
|
'COLLECTOR_REFRESH_RATE_SECONDS' => $refreshRate,
|
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metricsHistory,
|
|
];
|
|
$labels = [
|
|
'coolify.managed' => 'true',
|
|
];
|
|
if (isDev()) {
|
|
// data_set($environments, 'DEBUG', 'true');
|
|
if ($customImage && ! empty($customImage)) {
|
|
$image = $customImage;
|
|
}
|
|
$mountDir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
|
}
|
|
$dockerEnvironments = implode(' ', array_map(fn ($key, $value) => '-e '.escapeshellarg("$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 $mountDir",
|
|
$dockerCommand,
|
|
"chown -R 9999:root $mountDir",
|
|
"chmod -R 700 $mountDir",
|
|
], $server);
|
|
|
|
$server->settings->is_sentinel_enabled = true;
|
|
$server->settings->save();
|
|
$server->sentinelHeartbeat();
|
|
|
|
// Dispatch event to notify UI components
|
|
SentinelRestarted::dispatch($server, $version);
|
|
}
|
|
}
|