Skip to content

Commit

Permalink
Merge pull request #3663 from coollabsio/next
Browse files Browse the repository at this point in the history
v4.0.0-beta.350
  • Loading branch information
andrasbacsai authored Oct 3, 2024
2 parents 5354561 + a6a3abc commit 436a1d9
Show file tree
Hide file tree
Showing 33 changed files with 320 additions and 171 deletions.
50 changes: 50 additions & 0 deletions app/Console/Commands/CheckApplicationDeploymentQueue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace App\Console\Commands;

use App\Enums\ApplicationDeploymentStatus;
use App\Models\ApplicationDeploymentQueue;
use Illuminate\Console\Command;

class CheckApplicationDeploymentQueue extends Command
{
protected $signature = 'check:deployment-queue {--force} {--seconds=3600}';

protected $description = 'Check application deployment queue.';

public function handle()
{
$seconds = $this->option('seconds');
$deployments = ApplicationDeploymentQueue::whereIn('status', [
ApplicationDeploymentStatus::IN_PROGRESS,
ApplicationDeploymentStatus::QUEUED,
])->where('created_at', '>=', now()->subSeconds($seconds))->get();
if ($deployments->isEmpty()) {
$this->info('No deployments found in the last '.$seconds.' seconds.');

return;
}

$this->info('Found '.$deployments->count().' deployments created in the last '.$seconds.' seconds.');

foreach ($deployments as $deployment) {
if ($this->option('force')) {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
$this->cancelDeployment($deployment);
} else {
$this->info('Deployment '.$deployment->id.' created at '.$deployment->created_at.' is older than '.$seconds.' seconds. Setting status to failed.');
if ($this->confirm('Do you want to cancel this deployment?', true)) {
$this->cancelDeployment($deployment);
}
}
}
}

private function cancelDeployment(ApplicationDeploymentQueue $deployment)
{
$deployment->update(['status' => ApplicationDeploymentStatus::FAILED]);
if ($deployment->server?->isFunctional()) {
remote_process(['docker rm -f '.$deployment->deployment_uuid], $deployment->server, false);
}
}
}
4 changes: 2 additions & 2 deletions app/Console/Commands/CleanupApplicationDeploymentQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $signature = 'cleanup:deployment-queue {--team-id=}';

protected $description = 'CleanupApplicationDeploymentQueue';
protected $description = 'Cleanup application deployment queue.';

public function handle()
{
Expand Down
12 changes: 12 additions & 0 deletions app/Console/Commands/CleanupStuckedResources.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Jobs\CleanupHelperContainersJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
Expand Down Expand Up @@ -47,6 +48,17 @@ private function cleanup_stucked_resources()
} catch (\Throwable $e) {
echo "Error in cleaning stucked resources: {$e->getMessage()}\n";
}
try {
$applicationsDeploymentQueue = ApplicationDeploymentQueue::get();
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
if (is_null($applicationDeploymentQueue->application)) {
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
$applicationDeploymentQueue->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application deployment queue: {$e->getMessage()}\n";
}
try {
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($applications as $application) {
Expand Down
6 changes: 4 additions & 2 deletions app/Helpers/SshMultiplexingHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ public static function generateScpCommand(Server $server, string $source, string
$muxPersistTime = config('constants.ssh.mux_persist_time');

$scp_command = "timeout $timeout scp ";

if ($server->isIpv6()) {
$scp_command .= '-6 ';
}
if (self::isMultiplexingEnabled()) {
$scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} ";
self::ensureMultiplexedConnection($server);
Expand Down Expand Up @@ -136,8 +138,8 @@ public static function generateSshCommand(Server $server, string $command)

$ssh_command .= self::getCommonSshOptions($server, $sshKeyLocation, config('constants.ssh.connection_timeout'), config('constants.ssh.server_interval'));

$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$delimiter = Hash::make($command);
$delimiter = base64_encode($delimiter);
$command = str_replace($delimiter, '', $command);

$ssh_command .= "{$server->user}@{$server->ip} 'bash -se' << \\$delimiter".PHP_EOL
Expand Down
90 changes: 37 additions & 53 deletions app/Jobs/DatabaseBackupJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Jobs;

use App\Actions\Database\StopDatabase;
use App\Events\BackupCreated;
use App\Models\S3Storage;
use App\Models\ScheduledDatabaseBackup;
Expand All @@ -24,7 +23,6 @@
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;

class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
Expand Down Expand Up @@ -63,30 +61,26 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
}

public function handle(): void
{
try {
// Check if team is exists
if (is_null($this->team)) {
StopDatabase::run($this->database);
$this->database->delete();

return;
$this->team = Team::findOrFail($this->backup->team_id);
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server;
$this->s3 = $this->backup->s3;
} else {
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->s3 = $this->backup->s3;
}
if (is_null($this->server)) {
throw new \Exception('Server not found?!');
}
if (is_null($this->database)) {
throw new \Exception('Database not found?!');
}

BackupCreated::dispatch($this->team->id);
Expand Down Expand Up @@ -237,7 +231,6 @@ public function handle(): void
}
}
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;

if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = 'coolify-db';
Expand Down Expand Up @@ -325,7 +318,9 @@ public function handle(): void
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
throw $e;
} finally {
BackupCreated::dispatch($this->team->id);
if ($this->team) {
BackupCreated::dispatch($this->team->id);
}
}
}

Expand Down Expand Up @@ -466,34 +461,6 @@ private function remove_old_backups(): void
}
}

// private function upload_to_s3(): void
// {
// try {
// if (is_null($this->s3)) {
// return;
// }
// $key = $this->s3->key;
// $secret = $this->s3->secret;
// // $region = $this->s3->region;
// $bucket = $this->s3->bucket;
// $endpoint = $this->s3->endpoint;
// $this->s3->testConnection(shouldSave: true);
// $configName = new Cuid2;

// $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/');
// $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'";
// $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'";
// instant_remote_process($commands, $this->server);
// $this->add_to_backup_output('Uploaded to S3.');
// } catch (\Throwable $e) {
// $this->add_to_backup_output($e->getMessage());
// throw $e;
// } finally {
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'";
// $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'";
// instant_remote_process($removeConfigCommands, $this->server, false);
// }
// }
private function upload_to_s3(): void
{
try {
Expand All @@ -515,10 +482,27 @@ private function upload_to_s3(): void
$this->ensureHelperImageAvailable();

$fullImageName = $this->getFullImageName();
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";

if (isDev()) {
if ($this->database->name === 'coolify-db') {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
} else {
$backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name.$this->backup_file;
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $backup_location_from:$this->backup_location:ro {$fullImageName}";
}
} else {
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}";
}
if ($this->s3->isHetzner()) {
$endpointWithoutBucket = 'https://'.str($endpoint)->after('https://')->after('.')->value();
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc alias set --path=off --api=S3v4 temporary {$endpointWithoutBucket} $key $secret";
} else {
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
}
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
instant_remote_process($commands, $this->server);

$this->add_to_backup_output('Uploaded to S3.');
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
Expand Down
2 changes: 1 addition & 1 deletion app/Livewire/Dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function mount()

public function cleanup_queue()
{
Artisan::queue('cleanup:application-deployment-queue', [
Artisan::queue('cleanup:deployment-queue', [
'--team-id' => currentTeam()->id,
]);
}
Expand Down
4 changes: 2 additions & 2 deletions app/Livewire/Project/Shared/Terminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
if ($status !== 'running') {
return;
}
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, "docker exec -it {$identifier} sh -c 'PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
} else {
$command = SshMultiplexingHelper::generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'");
$command = SshMultiplexingHelper::generateSshCommand($server, 'PATH=$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin && if [ -f ~/.profile ]; then . ~/.profile; fi && if [ -n "$SHELL" ]; then exec $SHELL; else sh; fi');
}

// ssh command is sent back to frontend then to websocket
Expand Down
34 changes: 24 additions & 10 deletions app/Livewire/Security/ApiTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class ApiTokens extends Component

public bool $readOnly = true;

public bool $rootAccess = false;

public array $permissions = ['read-only'];

public $isApiEnabled;
Expand All @@ -35,24 +37,42 @@ public function updatedViewSensitiveData()
if ($this->viewSensitiveData) {
$this->permissions[] = 'view:sensitive';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
}
if (count($this->permissions) == 0) {
$this->permissions = ['*'];
}
$this->makeSureOneIsSelected();
}

public function updatedReadOnly()
{
if ($this->readOnly) {
$this->permissions[] = 'read-only';
$this->permissions = array_diff($this->permissions, ['*']);
$this->rootAccess = false;
} else {
$this->permissions = array_diff($this->permissions, ['read-only']);
}
if (count($this->permissions) == 0) {
$this->makeSureOneIsSelected();
}

public function updatedRootAccess()
{
if ($this->rootAccess) {
$this->permissions = ['*'];
$this->readOnly = false;
$this->viewSensitiveData = false;
} else {
$this->readOnly = true;
$this->permissions = ['read-only'];
}
}

public function makeSureOneIsSelected()
{
if (count($this->permissions) == 0) {
$this->permissions = ['read-only'];
$this->readOnly = true;
}
}

Expand All @@ -62,12 +82,6 @@ public function addNewToken()
$this->validate([
'description' => 'required|min:3|max:255',
]);
// if ($this->viewSensitiveData) {
// $this->permissions[] = 'view:sensitive';
// }
// if ($this->readOnly) {
// $this->permissions[] = 'read-only';
// }
$token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
Expand Down
5 changes: 5 additions & 0 deletions app/Livewire/Server/ConfigureCloudflareTunnels.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public function alreadyConfigured()
public function submit()
{
try {
if (str($this->ssh_domain)->contains('https://')) {
$this->ssh_domain = str($this->ssh_domain)->replace('https://', '')->replace('http://', '')->trim();
// remove / from the end
$this->ssh_domain = str($this->ssh_domain)->replace('/', '');
}
$server = Server::ownedByCurrentTeam()->where('id', $this->server_id)->firstOrFail();
ConfigureCloudflared::dispatch($server, $this->cloudflare_token);
$server->settings->is_cloudflare_tunnel = true;
Expand Down
18 changes: 10 additions & 8 deletions app/Livewire/Storage/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ class Create extends Component
'endpoint' => 'Endpoint',
];

public function mount()
public function updatedEndpoint($value)
{
if (isDev()) {
$this->name = 'Local MinIO';
$this->description = 'Local MinIO';
$this->key = 'minioadmin';
$this->secret = 'minioadmin';
$this->bucket = 'local';
$this->endpoint = 'http://coolify-minio:9000';
if (! str($value)->startsWith('https://') && ! str($value)->startsWith('http://')) {
$this->endpoint = 'https://'.$value;
$value = $this->endpoint;
}

if (str($value)->contains('your-objectstorage.com') && ! isset($this->bucket)) {
$this->bucket = str($value)->after('//')->before('.');
} elseif (str($value)->contains('your-objectstorage.com')) {
$this->bucket = $this->bucket ?: str($value)->after('//')->before('.');
}
}

Expand Down
Loading

0 comments on commit 436a1d9

Please sign in to comment.