Files
coolify/app/Actions/Database/RestartDatabase.php
T
Andras BacsaiandClaude Opus 5.5 9bdbbeef7a fix(secret-manager): use remote secret values exactly and check them before a restart
- Write remote secret values as exact dotenv literals in the service .env
  file and escape "$" where they go directly into a generated compose file
  (environment, start commands, healthchecks).
- Resolve remote secrets before a service or database restart stops the
  running resource, and show the secret manager error instead of a generic
  start failure (new RemoteSecretException).
- Cache fetched key names for the autocomplete and rate limit how often a
  user can contact the secret manager.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:00:41 +02:00

74 lines
3.3 KiB
PHP

<?php
namespace App\Actions\Database;
use App\Exceptions\RemoteSecretException;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\StandaloneSqlite;
use App\Support\DatabaseOperationReservation;
use App\Support\ResourceStartActivity;
use Lorisleiva\Actions\Concerns\AsAction;
class RestartDatabase
{
use AsAction;
/**
* @param string|null $reservation The token from StartDatabase::reserveOperation(). The request
* that queued this action holds the reservation; this action
* keeps it during the stop and releases it after the start.
*/
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $database, ?string $reservation = null)
{
if ($reservation === null) {
$reservation = DatabaseOperationReservation::acquire($database->uuid);
if ($reservation === null) {
return ResourceStartActivity::DATABASE_OPERATION_IN_PROGRESS_MESSAGE;
}
} elseif (! DatabaseOperationReservation::renew($database->uuid, $reservation)) {
// Do not stop the database for a request that expired in the queue.
return StartDatabase::operationInProgressError($database) ?? DatabaseOperationReservation::EXPIRED_MESSAGE;
}
try {
$server = $database->destination->server;
if (! $server->isFunctional()) {
return 'Server is not functional';
}
$busyError = StartDatabase::operationInProgressError($database, $reservation);
if ($busyError !== null) {
return $busyError;
}
$prerequisiteError = StartDatabase::prerequisiteError($database);
if ($prerequisiteError !== null) {
return $prerequisiteError;
}
// Check remote secrets before stopping: if the secret manager fails, the database keeps running.
try {
$database->ensureRemoteSecretsResolvable($database->runtime_environment_variables()->get());
} catch (RemoteSecretException $e) {
return $e->getMessage();
}
StopDatabase::run($database, dockerCleanup: false);
// The stop can take a while; keep the reservation for the start that follows.
DatabaseOperationReservation::renew($database->uuid, $reservation);
return StartDatabase::run($database, $reservation);
} finally {
DatabaseOperationReservation::release($database->uuid, $reservation);
}
}
public function asJob(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|StandaloneSqlite $database, ?string $reservation = null): mixed
{
return StartDatabase::runQueued($database, 'Database restart failed.', fn (): mixed => $this->handle($database, $reservation));
}
}