mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-14 08:42:38 -04:00
The missed daily backups on Coolify Cloud came from Redis evicting queue keys (allkeys-lfu on a full instance), not from the scan of all schedules. The next_run_at design (452259aac,5c5740533) did not prevent that, and it added stored state that must be kept in sync on every frequency, timezone, and server change. It was not released, so remove it: - revert next_run_at, its model hooks, the "Next run" UI, and the diagnostics changes; restore scheduled_job_states and the queue overlap lock - a new migration removes the next_run_at columns and creates scheduled_job_states again where the removed migration already ran Keep the changes that fix real problems: - run the manager every minute from the scheduler with `scheduled:dispatch`, so a busy queue cannot delay it past the catch-up window - load the server chain of scheduled backups with each chunk, which removes about 7,000 queries per run on Coolify Cloud - catch Throwable for each schedule, so one broken schedule does not stop the others - publish database and volume backups again when their queued job was not started after 60 minutes (for example, lost in Redis); log lost tasks and Docker cleanups as missed. A lost backup is not run again when a newer occurrence of the same schedule exists.
208 lines
8.0 KiB
PHP
208 lines
8.0 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Project\Shared;
|
|
|
|
use App\Actions\Application\StopApplicationOneServer;
|
|
use App\Actions\Docker\GetContainersStatus;
|
|
use App\Events\ApplicationStatusChanged;
|
|
use App\Models\Application;
|
|
use App\Models\Server;
|
|
use App\Models\StandaloneDocker;
|
|
use App\Traits\ListensToTeamChannel;
|
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|
use Illuminate\Support\Collection;
|
|
use Livewire\Component;
|
|
|
|
class Destination extends Component
|
|
{
|
|
use AuthorizesRequests;
|
|
use ListensToTeamChannel;
|
|
|
|
public $resource;
|
|
|
|
public Collection $networks;
|
|
|
|
public function getListeners()
|
|
{
|
|
return [
|
|
'refresh' => 'mount',
|
|
...$this->teamChannelListeners([
|
|
'ApplicationStatusChanged' => 'loadData',
|
|
'ServiceStatusChanged' => 'mount',
|
|
]),
|
|
];
|
|
}
|
|
|
|
public function mount()
|
|
{
|
|
$this->networks = collect([]);
|
|
$this->loadData();
|
|
}
|
|
|
|
public function loadData()
|
|
{
|
|
$all_networks = collect([]);
|
|
$all_networks = $all_networks->push($this->resource->destination);
|
|
$all_networks = $all_networks->merge($this->resource->additional_networks);
|
|
|
|
$this->networks = Server::isUsable()->get()->map(function ($server) {
|
|
return $server->standaloneDockers;
|
|
})->flatten();
|
|
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
|
|
return $all_networks->pluck('id')->contains($network->id);
|
|
});
|
|
$this->networks = $this->networks->reject(function ($network) {
|
|
return $this->resource->destination->server->id == $network->server->id;
|
|
});
|
|
if ($this->resource?->additional_servers?->count() > 0) {
|
|
$this->networks = $this->networks->reject(function ($network) {
|
|
return $this->resource->additional_servers->pluck('id')->contains($network->server->id);
|
|
});
|
|
}
|
|
}
|
|
|
|
public function stop($serverId)
|
|
{
|
|
try {
|
|
$this->authorize('deploy', $this->resource);
|
|
$server = Server::ownedByCurrentTeam()->findOrFail($serverId);
|
|
StopApplicationOneServer::run($this->resource, $server);
|
|
auditLog('ui.application.destination_stopped', [
|
|
'team_id' => $this->resource->team()?->id,
|
|
'application_uuid' => $this->resource->uuid,
|
|
'application_name' => $this->resource->name,
|
|
'server_uuid' => $server->uuid,
|
|
'server_name' => $server->name,
|
|
]);
|
|
$this->refreshServers();
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function redeploy(int $network_id, int $server_id)
|
|
{
|
|
try {
|
|
$this->authorize('deploy', $this->resource);
|
|
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
|
|
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
|
|
|
|
return;
|
|
}
|
|
$deployment_uuid = new_public_id();
|
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
|
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
|
|
$result = queue_application_deployment(
|
|
deployment_uuid: $deployment_uuid,
|
|
application: $this->resource,
|
|
server: $server,
|
|
destination: $destination,
|
|
only_this_server: true,
|
|
no_questions_asked: true,
|
|
);
|
|
if ($result['status'] === 'queue_full') {
|
|
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
|
|
|
return;
|
|
}
|
|
if ($result['status'] === 'skipped') {
|
|
$this->dispatch('success', 'Deployment skipped', $result['message']);
|
|
|
|
return;
|
|
}
|
|
|
|
return redirectRoute($this, 'project.application.deployment.show', [
|
|
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
|
'application_uuid' => data_get($this->resource, 'uuid'),
|
|
'deployment_uuid' => $deployment_uuid,
|
|
'environment_uuid' => data_get($this->resource, 'environment.uuid'),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function promote(int $network_id, int $server_id)
|
|
{
|
|
try {
|
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
|
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
|
|
$this->authorize('update', $this->resource);
|
|
$this->resource->getConnection()->transaction(function () use ($network, $server) {
|
|
$mainDestination = $this->resource->destination;
|
|
$this->resource->update([
|
|
'destination_id' => $network->id,
|
|
'destination_type' => StandaloneDocker::class,
|
|
]);
|
|
$this->resource->additional_networks()
|
|
->wherePivot('server_id', $server->id)
|
|
->detach($network->id);
|
|
$this->resource->additional_networks()->attach($mainDestination->id, ['server_id' => $mainDestination->server->id]);
|
|
});
|
|
$this->resource->refresh();
|
|
$this->refreshServers();
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function refreshServers()
|
|
{
|
|
GetContainersStatus::run($this->resource->destination->server);
|
|
$this->loadData();
|
|
$this->dispatch('refresh');
|
|
}
|
|
|
|
public function addServer(int $network_id, int $server_id)
|
|
{
|
|
try {
|
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
|
$network = StandaloneDocker::ownedByCurrentTeam()->where('server_id', $server->id)->findOrFail($network_id);
|
|
$this->authorize('update', $this->resource);
|
|
$reason = $this->resource instanceof Application
|
|
? $this->resource->additionalServersUnavailableReason($server)
|
|
: 'Only applications can use multiple servers.';
|
|
if ($reason) {
|
|
$this->dispatch('error', 'Failed to add server.', $reason);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->resource->additional_networks()->syncWithoutDetaching([
|
|
$network->id => ['server_id' => $server->id],
|
|
]);
|
|
$this->dispatch('refresh');
|
|
} catch (\Throwable $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
|
|
public function removeServer(int $network_id, int $server_id, $password, $selectedActions = [])
|
|
{
|
|
try {
|
|
$this->authorize('update', $this->resource);
|
|
if (! verifyPasswordConfirmation($password, $this)) {
|
|
return 'The provided password is incorrect.';
|
|
}
|
|
|
|
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
|
|
$this->dispatch('error', 'You are trying to remove the main server.');
|
|
|
|
return;
|
|
}
|
|
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
|
|
StopApplicationOneServer::run($this->resource, $server);
|
|
$this->resource->additional_networks()
|
|
->wherePivot('server_id', $server_id)
|
|
->detach($network_id);
|
|
$this->loadData();
|
|
$this->dispatch('refresh');
|
|
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
|
|
|
return true;
|
|
} catch (\Exception $e) {
|
|
return handleError($e, $this);
|
|
}
|
|
}
|
|
}
|