mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-10 16:05:44 -04:00
- Proxy: list and delete Traefik ACME certificates from the server proxy page via new TraefikAcmeService and Get/DeleteTraefikCertificate actions - DNS: track ownership and cross-resource references for managed DNS records so records are only deleted when no longer referenced; release records asynchronously on resource deletion via ReleaseManagedDnsRecordsJob and ManagedDnsRecordCleanup; harden Cloudflare provider deletion results - Databases: fail closed on start when prerequisites or the CA certificate are missing (DatabaseStartException, Server::ensureCaCertificate) and clean up stale start activities via ResourceStartActivity - Webhooks: throttle repeated manual webhook signature failures for GitHub, GitLab, Gitea and Bitbucket - Deployments: improve compose build-context handling and compose file load error reporting - Install scripts: rework terminal UI output in install.sh (stable and nightly) - Misc: settings sidebar accordion fixes, log drain toggle rollback, add Serverside to README sponsors - Add migrations and tests covering the above
86 lines
3.0 KiB
PHP
86 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace App\Actions\Service;
|
|
|
|
use App\Models\Service;
|
|
use App\Models\ServiceApplication;
|
|
use App\Models\ServiceDatabase;
|
|
use RuntimeException;
|
|
|
|
class DeleteService
|
|
{
|
|
public function cleanupRemote(Service $service, bool $deleteVolumes, bool $deleteConnectedNetworks, bool $deleteConfigurations): void
|
|
{
|
|
$server = data_get($service, 'server');
|
|
if (! $server?->isFunctional()) {
|
|
throw new RuntimeException('Server is not functional.');
|
|
}
|
|
|
|
$this->removeContainers($service);
|
|
|
|
if ($deleteVolumes) {
|
|
$commands = [];
|
|
foreach ($service->applications()->get() as $application) {
|
|
foreach ($application->persistentStorages()->get() as $storage) {
|
|
$commands[] = 'docker volume rm -f '.escapeshellarg($storage->name);
|
|
}
|
|
}
|
|
foreach ($service->databases()->get() as $database) {
|
|
foreach ($database->persistentStorages()->get() as $storage) {
|
|
$commands[] = 'docker volume rm -f '.escapeshellarg($storage->name);
|
|
}
|
|
}
|
|
foreach ($commands as $command) {
|
|
instant_remote_process([$command], $server);
|
|
}
|
|
}
|
|
|
|
if ($deleteConnectedNetworks) {
|
|
$service->deleteConnectedNetworks();
|
|
}
|
|
if ($deleteConfigurations) {
|
|
$service->deleteConfigurations();
|
|
}
|
|
}
|
|
|
|
public function removeSubresourceContainer(ServiceApplication|ServiceDatabase $resource): void
|
|
{
|
|
$service = $resource->service;
|
|
$server = $service?->server;
|
|
if (! $server?->isFunctional()) {
|
|
throw new RuntimeException('Server is not functional.');
|
|
}
|
|
|
|
$this->removeContainers($service, $resource);
|
|
}
|
|
|
|
private function removeContainers(Service $service, ServiceApplication|ServiceDatabase|null $subresource = null): void
|
|
{
|
|
$filters = "--filter 'label=coolify.serviceId={$service->id}'";
|
|
if ($subresource !== null) {
|
|
// Applications and databases are separate tables, so an id alone can match the other type.
|
|
$subType = $subresource instanceof ServiceDatabase ? 'database' : 'application';
|
|
$filters .= " --filter 'label=coolify.service.subId={$subresource->id}' --filter 'label=coolify.service.subType={$subType}'";
|
|
}
|
|
|
|
$command = "container_ids=\$(docker ps -aq {$filters}); [ -z \"\$container_ids\" ] || docker rm -f \$container_ids";
|
|
instant_remote_process([$command], $service->server);
|
|
}
|
|
|
|
public function deleteLocal(Service $service): void
|
|
{
|
|
foreach ($service->applications()->get() as $application) {
|
|
$application->forceDelete();
|
|
}
|
|
foreach ($service->databases()->get() as $database) {
|
|
$database->forceDelete();
|
|
}
|
|
foreach ($service->scheduled_tasks as $task) {
|
|
$task->delete();
|
|
}
|
|
$service->environment_variables()->delete();
|
|
$service->tags()->detach();
|
|
$service->forceDelete();
|
|
}
|
|
}
|