diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 965162037..74bdd81cb 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -16,17 +16,17 @@ class StartService $commands[] = "cd " . $service->workdir(); $commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'"; $commands[] = "echo '####### Creating Docker network.'"; - $commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true"; + $commands[] = "docker network create --attachable '{$service->uuid}' >/dev/null || true"; $commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'"; $commands[] = "echo '####### Pulling images.'"; $commands[] = "docker compose pull"; $commands[] = "echo '####### Starting containers.'"; $commands[] = "docker compose up -d --remove-orphans --force-recreate"; - $commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true"; + $commands[] = "docker network connect $service->uuid coolify-proxy || true"; $compose = data_get($service,'docker_compose',[]); $serviceNames = data_get(Yaml::parse($compose),'services',[]); foreach($serviceNames as $serviceName => $serviceConfig){ - $commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true"; + $commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true"; } $activity = remote_process($commands, $service->server); return $activity; diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index 703c85b92..bc853108f 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -55,6 +55,7 @@ class General extends Component 'application.docker_registry_image_tag' => 'nullable', 'application.dockerfile_location' => 'nullable', 'application.custom_labels' => 'nullable', + 'application.dockerfile_target_build' => 'nullable', ]; protected $validationAttributes = [ 'application.name' => 'name', @@ -77,6 +78,7 @@ class General extends Component 'application.docker_registry_image_tag' => 'Docker registry image tag', 'application.dockerfile_location' => 'Dockerfile location', 'application.custom_labels' => 'Custom labels', + 'application.dockerfile_target_build' => 'Dockerfile target build', ]; public function mount() diff --git a/app/Http/Livewire/Project/CloneProject.php b/app/Http/Livewire/Project/CloneProject.php index 735bbc0da..215f494a7 100644 --- a/app/Http/Livewire/Project/CloneProject.php +++ b/app/Http/Livewire/Project/CloneProject.php @@ -104,6 +104,7 @@ class CloneProject extends Component $uuid = (string)new Cuid2(7); $newDatabase = $database->replicate()->fill([ 'uuid' => $uuid, + 'status' => 'exited', 'environment_id' => $newEnvironment->id, 'destination_id' => $this->selectedServer, ]); @@ -111,15 +112,15 @@ class CloneProject extends Component $environmentVaribles = $database->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $payload = []; - if ($database->type() === 'standalone-postgres') { + if ($database->type() === 'standalone-postgresql') { $payload['standalone_postgresql_id'] = $newDatabase->id; - } else if ($database->type() === 'standalone_redis') { + } else if ($database->type() === 'standalone-redis') { $payload['standalone_redis_id'] = $newDatabase->id; - } else if ($database->type() === 'standalone_mongodb') { + } else if ($database->type() === 'standalone-mongodb') { $payload['standalone_mongodb_id'] = $newDatabase->id; - } else if ($database->type() === 'standalone_mysql') { + } else if ($database->type() === 'standalone-mysql') { $payload['standalone_mysql_id'] = $newDatabase->id; - }else if ($database->type() === 'standalone_mariadb') { + } else if ($database->type() === 'standalone-mariadb') { $payload['standalone_mariadb_id'] = $newDatabase->id; } $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); @@ -134,6 +135,16 @@ class CloneProject extends Component 'destination_id' => $this->selectedServer, ]); $newService->save(); + foreach ($newService->applications() as $application) { + $application->update([ + 'status' => 'exited', + ]); + } + foreach ($newService->databases() as $database) { + $database->update([ + 'status' => 'exited', + ]); + } $newService->parse(); } return redirect()->route('project.resources', [ diff --git a/app/Http/Livewire/Project/Database/BackupEdit.php b/app/Http/Livewire/Project/Database/BackupEdit.php index 951f95468..813016dba 100644 --- a/app/Http/Livewire/Project/Database/BackupEdit.php +++ b/app/Http/Livewire/Project/Database/BackupEdit.php @@ -3,6 +3,7 @@ namespace App\Http\Livewire\Project\Database; use Livewire\Component; +use Spatie\Url\Url; class BackupEdit extends Component { @@ -43,14 +44,23 @@ class BackupEdit extends Component { // TODO: Delete backup from server and add a confirmation modal $this->backup->delete(); - redirect()->route('project.database.backups.all', $this->parameters); + if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') { + $previousUrl = url()->previous(); + $url = Url::fromString($previousUrl); + $url = $url->withoutQueryParameter('selectedBackupId'); + $url = $url->withFragment('backups'); + $url = $url->getPath() . "#{$url->getFragment()}"; + return redirect()->to($url); + } else { + redirect()->route('project.database.backups.all', $this->parameters); + } + } public function instantSave() { try { $this->custom_validate(); - $this->backup->save(); $this->backup->refresh(); $this->emit('success', 'Backup updated successfully'); diff --git a/app/Http/Livewire/Project/Database/BackupExecutions.php b/app/Http/Livewire/Project/Database/BackupExecutions.php index f8ec4efbe..41a1cfbd6 100644 --- a/app/Http/Livewire/Project/Database/BackupExecutions.php +++ b/app/Http/Livewire/Project/Database/BackupExecutions.php @@ -19,7 +19,11 @@ class BackupExecutions extends Component $this->emit('error', 'Backup execution not found.'); return; } - delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); + if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') { + delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server); + } else { + delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); + } $execution->delete(); $this->emit('success', 'Backup deleted successfully.'); $this->emit('refreshBackupExecutions'); @@ -33,7 +37,11 @@ class BackupExecutions extends Component return; } $filename = data_get($execution, 'filename'); - $server = $execution->scheduledDatabaseBackup->database->destination->server; + if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') { + $server = $execution->scheduledDatabaseBackup->database->service->destination->server; + } else { + $server = $execution->scheduledDatabaseBackup->database->destination->server; + } $privateKeyLocation = savePrivateKeyToFs($server); $disk = Storage::build([ 'driver' => 'sftp', diff --git a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php index f804c389d..a36266a6c 100644 --- a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php @@ -22,7 +22,8 @@ class CreateScheduledBackup extends Component 'frequency' => 'Backup Frequency', 'save_s3' => 'Save to S3', ]; - public function mount() { + public function mount() + { if ($this->s3s->count() > 0) { $this->s3_storage_id = $this->s3s->first()->id; } @@ -50,11 +51,16 @@ class CreateScheduledBackup extends Component $payload['databases_to_backup'] = $this->database->postgres_db; } else if ($this->database->type() === 'standalone-mysql') { $payload['databases_to_backup'] = $this->database->mysql_database; - }else if ($this->database->type() === 'standalone-mariadb') { + } else if ($this->database->type() === 'standalone-mariadb') { $payload['databases_to_backup'] = $this->database->mariadb_database; } - ScheduledDatabaseBackup::create($payload); - $this->emit('refreshScheduledBackups'); + + $databaseBackup = ScheduledDatabaseBackup::create($payload); + if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') { + $this->emit('refreshScheduledBackups', $databaseBackup->id); + } else { + $this->emit('refreshScheduledBackups'); + } } catch (\Throwable $e) { handleError($e, $this); } finally { diff --git a/app/Http/Livewire/Project/Database/ScheduledBackups.php b/app/Http/Livewire/Project/Database/ScheduledBackups.php index f1abbb86d..ec20b1adb 100644 --- a/app/Http/Livewire/Project/Database/ScheduledBackups.php +++ b/app/Http/Livewire/Project/Database/ScheduledBackups.php @@ -8,13 +8,33 @@ class ScheduledBackups extends Component { public $database; public $parameters; + public $type; + public $selectedBackup; + public $selectedBackupId; + public $s3s; protected $listeners = ['refreshScheduledBackups']; + protected $queryString = ['selectedBackupId']; public function mount(): void { + if ($this->selectedBackupId) { + $this->setSelectedBackup($this->selectedBackupId); + } $this->parameters = get_route_parameters(); + if ($this->database->getMorphClass() === 'App\Models\ServiceDatabase') { + $this->type = 'service-database'; + } else { + $this->type = 'database'; + } + $this->s3s = currentTeam()->s3s; + } + public function setSelectedBackup($backupId) { + $this->selectedBackupId = $backupId; + $this->selectedBackup = $this->database->scheduledBackups->find($this->selectedBackupId); + if (is_null($this->selectedBackup)) { + $this->selectedBackupId = null; + } } - public function delete($scheduled_backup_id): void { $this->database->scheduledBackups->find($scheduled_backup_id)->delete(); @@ -22,9 +42,11 @@ class ScheduledBackups extends Component $this->refreshScheduledBackups(); } - public function refreshScheduledBackups(): void + public function refreshScheduledBackups(?int $id = null): void { - ray('refreshScheduledBackups'); $this->database->refresh(); + if ($id) { + $this->setSelectedBackup($id); + } } } diff --git a/app/Http/Livewire/Project/Service/Navbar.php b/app/Http/Livewire/Project/Service/Navbar.php index 23ad062a5..1953605e6 100644 --- a/app/Http/Livewire/Project/Service/Navbar.php +++ b/app/Http/Livewire/Project/Service/Navbar.php @@ -27,11 +27,15 @@ class Navbar extends Component $activity = StartService::run($this->service); $this->emit('newMonitorActivity', $activity->id); } - public function stop() + public function stop(bool $forceCleanup = false) { StopService::run($this->service); $this->service->refresh(); - $this->emit('success', 'Service stopped successfully.'); + if ($forceCleanup) { + $this->emit('success', 'Force cleanup service successfully.'); + } else { + $this->emit('success', 'Service stopped successfully.'); + } $this->emit('checkStatus'); } } diff --git a/app/Http/Livewire/Project/Service/Show.php b/app/Http/Livewire/Project/Service/Show.php index 958fd595a..53f5033f4 100644 --- a/app/Http/Livewire/Project/Service/Show.php +++ b/app/Http/Livewire/Project/Service/Show.php @@ -16,6 +16,8 @@ class Show extends Component public array $parameters; public array $query; public Collection $services; + public $s3s; + protected $listeners = ['generateDockerCompose']; public function mount() @@ -33,6 +35,7 @@ class Show extends Component $this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first(); $this->serviceDatabase->getFilesFromServer(); } + $this->s3s = currentTeam()->s3s; } catch(\Throwable $e) { return handleError($e, $this); } diff --git a/app/Http/Livewire/Project/Shared/GetLogs.php b/app/Http/Livewire/Project/Shared/GetLogs.php index b693793cf..4244bed12 100644 --- a/app/Http/Livewire/Project/Shared/GetLogs.php +++ b/app/Http/Livewire/Project/Shared/GetLogs.php @@ -17,7 +17,7 @@ class GetLogs extends Component public int $numberOfLines = 100; public function doSomethingWithThisChunkOfOutput($output) { - $this->outputs .= $output; + $this->outputs .= removeAnsiColors($output); } public function instantSave() { diff --git a/app/Http/Livewire/Settings/Backup.php b/app/Http/Livewire/Settings/Backup.php index 916344ddc..7a5e2f0ed 100644 --- a/app/Http/Livewire/Settings/Backup.php +++ b/app/Http/Livewire/Settings/Backup.php @@ -69,7 +69,6 @@ class Backup extends Component ]); $this->database->refresh(); $this->backup->refresh(); - ray($this->backup); $this->s3s = S3Storage::whereTeamId(0)->get(); } diff --git a/app/Http/Livewire/Source/Github/Change.php b/app/Http/Livewire/Source/Github/Change.php index d0b717730..c9ef89c02 100644 --- a/app/Http/Livewire/Source/Github/Change.php +++ b/app/Http/Livewire/Source/Github/Change.php @@ -3,17 +3,18 @@ namespace App\Http\Livewire\Source\Github; use App\Models\GithubApp; +use App\Models\InstanceSettings; use Livewire\Component; class Change extends Component { public string $webhook_endpoint; - public string|null $ipv4; - public string|null $ipv6; - public string|null $fqdn; + public ?string $ipv4; + public ?string $ipv6; + public ?string $fqdn; - public bool|null $default_permissions = true; - public bool|null $preview_deployment_permissions = true; + public ?bool $default_permissions = true; + public ?bool $preview_deployment_permissions = true; public $parameters; public GithubApp $github_app; @@ -28,29 +29,68 @@ class Change extends Component 'github_app.custom_user' => 'required|string', 'github_app.custom_port' => 'required|int', 'github_app.app_id' => 'required|int', - 'github_app.installation_id' => 'nullable', - 'github_app.client_id' => 'nullable', - 'github_app.client_secret' => 'nullable', - 'github_app.webhook_secret' => 'nullable', + 'github_app.installation_id' => 'required|int', + 'github_app.client_id' => 'required|string', + 'github_app.client_secret' => 'required|string', + 'github_app.webhook_secret' => 'required|string', 'github_app.is_system_wide' => 'required|bool', ]; public function mount() { + $github_app_uuid = request()->github_app_uuid; + $this->github_app = GithubApp::where('uuid', $github_app_uuid)->first(); + if (!$this->github_app) { + return redirect()->route('source.all'); + } + $settings = InstanceSettings::get(); + $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); + + $this->name = str($this->github_app->name)->kebab(); + $this->fqdn = $settings->fqdn; + + if ($settings->public_ipv4) { + $this->ipv4 = 'http://' . $settings->public_ipv4 . ':' . config('app.port'); + } + if ($settings->public_ipv6) { + $this->ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port'); + } + if ($this->github_app->installation_id && session('from')) { + $source_id = data_get(session('from'), 'source_id'); + if (!$source_id || $this->github_app->id !== $source_id) { + session()->forget('from'); + } else { + $parameters = data_get(session('from'), 'parameters'); + $back = data_get(session('from'), 'back'); + $environment_name = data_get($parameters, 'environment_name'); + $project_uuid = data_get($parameters, 'project_uuid'); + $type = data_get($parameters, 'type'); + $destination = data_get($parameters, 'destination'); + session()->forget('from'); + return redirect()->route($back, [ + 'environment_name' => $environment_name, + 'project_uuid' => $project_uuid, + 'type' => $type, + 'destination' => $destination, + ]); + } + } + $this->parameters = get_route_parameters(); if (isCloud() && !isDev()) { $this->webhook_endpoint = config('app.url'); } else { $this->webhook_endpoint = $this->ipv4; $this->is_system_wide = $this->github_app->is_system_wide; } - $this->parameters = get_route_parameters(); } public function submit() { try { + $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->validate(); $this->github_app->save(); + $this->emit('success', 'Github App updated successfully.'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -58,6 +98,7 @@ class Change extends Component public function instantSave() { + $this->submit(); } public function delete() diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 4ffc1f6b6..d91334745 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -69,6 +69,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private $docker_compose_base64; private string $dockerfile_location = '/Dockerfile'; private ?string $addHosts = null; + private ?string $buildTarget = null; private $log_model; private Collection $saved_outputs; @@ -178,6 +179,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted return "--add-host $name:$ip"; })->implode(' '); + if ($this->application->dockerfile_target_build) { + $this->buildTarget = " --target {$this->application->dockerfile_target_build} "; + } + // Get user home directory $this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server); $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); @@ -921,7 +926,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if ($this->application->settings->is_static) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true + executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true ]); $dockerfile = base64_encode("FROM {$this->application->static_image} @@ -959,7 +964,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); ); } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true + executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true ]); } } diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 804233de9..94e907834 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -7,6 +7,7 @@ use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackupExecution; use App\Models\Server; +use App\Models\ServiceDatabase; use App\Models\StandaloneMariadb; use App\Models\StandaloneMongodb; use App\Models\StandaloneMysql; @@ -32,7 +33,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted public ?Team $team = null; public Server $server; public ScheduledDatabaseBackup $backup; - public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database; + public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; public ?string $container_name = null; public ?ScheduledDatabaseBackupExecution $backup_log = null; @@ -48,9 +49,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted { $this->backup = $backup; $this->team = Team::find($backup->team_id); - $this->database = data_get($this->backup, 'database'); - $this->server = $this->database->destination->server; - $this->s3 = $this->backup->s3; + 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 middleware(): array @@ -73,14 +80,38 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $this->database->delete(); return; } - $status = Str::of(data_get($this->database, 'status')); if (!$status->startsWith('running') && $this->database->id !== 0) { ray('database not running'); return; } - $databaseType = $this->database->type(); - $databasesToBackup = data_get($this->backup, 'databases_to_backup'); + if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') { + $databaseType = $this->database->databaseType(); + $serviceUuid = $this->database->service->uuid; + if ($databaseType === 'standalone-postgresql') { + $this->container_name = "postgresql-$serviceUuid"; + $commands[] = "docker exec $this->container_name env | grep POSTGRES_"; + $envs = instant_remote_process($commands, $this->server); + $databasesToBackup = Str::of($envs)->after('POSTGRES_DB=')->before("\n")->value(); + $this->database->postgres_user = Str::of($envs)->after('POSTGRES_USER=')->before("\n")->value(); + } else if ($databaseType === 'standalone-mysql') { + $this->container_name = "mysql-$serviceUuid"; + $commands[] = "docker exec $this->container_name env | grep MYSQL_"; + $envs = instant_remote_process($commands, $this->server); + $databasesToBackup = Str::of($envs)->after('MYSQL_DATABASE=')->before("\n")->value(); + $this->database->mysql_root_password = Str::of($envs)->after('MYSQL_ROOT_PASSWORD=')->before("\n")->value(); + } else if ($databaseType === 'standalone-mariadb') { + $this->container_name = "mariadb-$serviceUuid"; + $commands[] = "docker exec $this->container_name env | grep MARIADB_"; + $envs = instant_remote_process($commands, $this->server); + $databasesToBackup = Str::of($envs)->after('MARIADB_DATABASE=')->before("\n")->value(); + $this->database->mysql_root_password = Str::of($envs)->after('MARIADB_ROOT_PASSWORD=')->before("\n")->value(); + } + } else { + $this->container_name = $this->database->uuid; + $databaseType = $this->database->type(); + $databasesToBackup = data_get($this->backup, 'databases_to_backup'); + } if (is_null($databasesToBackup)) { if ($databaseType === 'standalone-postgresql') { @@ -116,7 +147,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted return; } } - $this->container_name = $this->database->uuid; $this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name; if ($this->database->name === 'coolify-db') { @@ -314,7 +344,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted if ($this->backup->number_of_backups_locally === 0) { $deletable = $this->backup->executions()->where('status', 'success'); } else { - $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally); + $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1); } foreach ($deletable->get() as $execution) { delete_backup_locally($execution->filename, $this->server); @@ -334,8 +364,12 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $bucket = $this->s3->bucket; $endpoint = $this->s3->endpoint; $this->s3->testConnection(); - $commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1"; - + if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') { + $network = $this->database->service->destination->network; + } else { + $network = $this->database->destination->network; + } + $commands[] = "docker run --pull=always -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper"; $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); diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 411a0f464..0813c1218 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -2,7 +2,6 @@ namespace App\Jobs; -use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -22,7 +21,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted public function middleware(): array { - return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))]; } public function uniqueId(): string diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 900cdc3b4..d5ecbd2a6 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -20,6 +20,14 @@ class ServiceDatabase extends BaseModel { return 'service'; } + public function databaseType() + { + $image = str($this->image)->before(':'); + if ($image->value() === 'postgres') { + $image = 'postgresql'; + } + return "standalone-$image"; + } public function service() { return $this->belongsTo(Service::class); @@ -36,4 +44,8 @@ class ServiceDatabase extends BaseModel { getFilesystemVolumesFromServer($this, $isInit); } + public function scheduledBackups() + { + return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); + } } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 8e8b2ec3e..013821bd7 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -501,3 +501,6 @@ function generateDeployWebhook($resource) { $url = $api . $endpoint . "?uuid=$uuid&force=false"; return $url; } +function removeAnsiColors($text) { + return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text); +} diff --git a/config/sentry.php b/config/sentry.php index 8899bddd2..8ffa05932 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.111', + 'release' => '4.0.0-beta.112', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index f02cc33b3..b2220ad08 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ string('dockerfile_target_build')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('dockerfile_target_build'); + }); + } +}; diff --git a/resources/views/components/services/navbar.blade.php b/resources/views/components/services/navbar.blade.php index 43e16c3df..1d0cf03a5 100644 --- a/resources/views/components/services/navbar.blade.php +++ b/resources/views/components/services/navbar.blade.php @@ -38,13 +38,13 @@ @endif @if (serviceStatus($service) === 'exited') - General + @click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''" + href="#">General Storages + @click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''" + href="#">Storages + Backups @if (data_get($parameters, 'service_name')) @@ -43,8 +47,15 @@
Persistent storage to preserve data between deployments.
Please modify storage layout in your Docker Compose file. - + + +
+
+

Scheduled Backups

+ + Add +
+ +
@endisset diff --git a/resources/views/livewire/source/github/change.blade.php b/resources/views/livewire/source/github/change.blade.php index ea3b1e82f..1501685ca 100644 --- a/resources/views/livewire/source/github/change.blade.php +++ b/resources/views/livewire/source/github/change.blade.php @@ -4,32 +4,26 @@

This source will be deleted. It is not reversible.
Please think again.

-
-
-

GitHub App

-
-
Your Private GitHub App for private repositories.
- @if (data_get($github_app, 'app_id')) +
Your Private GitHub App for private repositories.
@if (!data_get($github_app, 'installation_id'))
You must complete this step before you can use this source!
- + Install Repositories on GitHub @else @@ -47,7 +41,7 @@
+ instantSave id="github_app.is_system_wide" />
@endif
@@ -78,110 +72,118 @@
@endif - @else -
- - - - You must complete this step before you can use this source! + + @else +
+

GitHub App

+
+ + Delete +
-
-

Register a GitHub App

-
You need to register a GitHub App before using this source.
-
- @if (!isCloud() || isDev()) -
- - @if ($ipv4) - - @endif - @if ($ipv6) - - @endif - @if ($fqdn) - - @endif - @if (config('app.url')) - - @endif - - - Register - -
- @else +
+
+ + + + You must complete this step before you can use this source! +
+
+

Register a GitHub App

+
You need to register a GitHub App before using this source.
+
+ @if (!isCloud() || isDev()) +
+ + @if ($ipv4) + + @endif + @if ($ipv6) + + @endif + @if ($fqdn) + + @endif + @if (config('app.url')) + + @endif + - Register Now + Register - @endif -
- -
+ @else + + Register Now + + @endif +
+ +
- - - @endif - + const webhookBaseUrl = `${baseUrl}/webhooks`; + const path = organization ? `organizations/${organization}/settings/apps/new` : 'settings/apps/new'; + const default_permissions = { + contents: 'read', + metadata: 'read', + emails: 'read' + }; + if (preview_deployment_permissions) { + default_permissions.pull_requests = 'write'; + } + const data = { + name, + url: baseUrl, + hook_attributes: { + url: `${webhookBaseUrl}/source/github/events`, + active: true, + }, + redirect_url: `${webhookBaseUrl}/source/github/redirect`, + callback_urls: [`${baseUrl}/login/github/app`], + public: false, + request_oauth_on_install: false, + setup_url: `${webhookBaseUrl}/source/github/install?source=${uuid}`, + setup_on_update: true, + default_permissions, + default_events: ['pull_request', 'push'] + }; + const form = document.createElement('form'); + form.setAttribute('method', 'post'); + form.setAttribute('action', `${html_url}/${path}?state=${uuid}`); + const input = document.createElement('input'); + input.setAttribute('id', 'manifest'); + input.setAttribute('name', 'manifest'); + input.setAttribute('type', 'hidden'); + input.setAttribute('value', JSON.stringify(data)); + form.appendChild(input); + document.getElementsByTagName('body')[0].appendChild(form); + form.submit(); + } + + @endif
diff --git a/routes/web.php b/routes/web.php index 83e119aee..54226ca89 100644 --- a/routes/web.php +++ b/routes/web.php @@ -20,11 +20,10 @@ use App\Http\Livewire\Server\PrivateKey\Show as PrivateKeyShow; use App\Http\Livewire\Server\Proxy\Show as ProxyShow; use App\Http\Livewire\Server\Proxy\Logs as ProxyLogs; use App\Http\Livewire\Server\Show; +use App\Http\Livewire\Source\Github\Change as GitHubChange; use App\Http\Livewire\Subscription\Show as SubscriptionShow; use App\Http\Livewire\Waitlist\Index as WaitlistIndex; -use App\Models\GithubApp; use App\Models\GitlabApp; -use App\Models\InstanceSettings; use App\Models\PrivateKey; use App\Models\Server; use App\Models\StandaloneDocker; @@ -178,49 +177,7 @@ Route::middleware(['auth'])->group(function () { 'sources' => $sources, ]); })->name('source.all'); - Route::get('/source/github/{github_app_uuid}', function (Request $request) { - $github_app = GithubApp::where('uuid', request()->github_app_uuid)->first(); - if (!$github_app) { - abort(404); - } - $github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); - $settings = InstanceSettings::get(); - $name = Str::of(Str::kebab($github_app->name)); - if ($settings->public_ipv4) { - $ipv4 = 'http://' . $settings->public_ipv4 . ':' . config('app.port'); - } - if ($settings->public_ipv6) { - $ipv6 = 'http://' . $settings->public_ipv6 . ':' . config('app.port'); - } - if ($github_app->installation_id && session('from')) { - $source_id = data_get(session('from'), 'source_id'); - if (!$source_id || $github_app->id !== $source_id) { - session()->forget('from'); - } else { - $parameters = data_get(session('from'), 'parameters'); - $back = data_get(session('from'), 'back'); - $environment_name = data_get($parameters, 'environment_name'); - $project_uuid = data_get($parameters, 'project_uuid'); - $type = data_get($parameters, 'type'); - $destination = data_get($parameters, 'destination'); - session()->forget('from'); - return redirect()->route($back, [ - 'environment_name' => $environment_name, - 'project_uuid' => $project_uuid, - 'type' => $type, - 'destination' => $destination, - ]); - } - } - return view('source.github.show', [ - 'github_app' => $github_app, - 'name' => $name, - 'ipv4' => $ipv4 ?? null, - 'ipv6' => $ipv6 ?? null, - 'fqdn' => $settings->fqdn, - ]); - })->name('source.github.show'); - + Route::get('/source/github/{github_app_uuid}', GitHubChange::class)->name('source.github.show'); Route::get('/source/gitlab/{gitlab_app_uuid}', function (Request $request) { $gitlab_app = GitlabApp::where('uuid', request()->gitlab_app_uuid)->first(); return view('source.gitlab.show', [ diff --git a/scripts/install.sh b/scripts/install.sh index 0d759681a..26ada577f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,7 +5,7 @@ ## Always run "php artisan app:sync-to-bunny-cdn --env=secrets" or "scripts/run sync-bunny" if you update this file. ########### -VERSION="1.0.2" +VERSION="1.0.3" DOCKER_VERSION="24.0" CDN="https://cdn.coollabs.io/coolify" @@ -46,7 +46,14 @@ apt install -y curl wget git jq jc >/dev/null 2>&1 if ! [ -x "$(command -v docker)" ]; then echo "Docker is not installed. Installing Docker..." curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh - echo "Docker installed successfully" + if [ -x "$(command -v docker)" ]; then + echo "Docker installed successfully." + else + echo "Docker installation failed." + echo "Maybe your OS is not supported." + echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." + exit 1 + fi fi echo -e "-------------" echo -e "Check Docker Configuration..." diff --git a/templates/compose/gitea-with-mariadb.yaml b/templates/compose/gitea-with-mariadb.yaml index e5a2e0ef1..f00f73ded 100644 --- a/templates/compose/gitea-with-mariadb.yaml +++ b/templates/compose/gitea-with-mariadb.yaml @@ -6,7 +6,7 @@ services: gitea: image: gitea/gitea:latest environment: - - SERVICE_FQDN_GITEA + - SERVICE_FQDN_GITEA_3000 - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=mysql @@ -18,8 +18,8 @@ services: - gitea-data:/var/lib/gitea - gitea-timezone:/etc/timezone:ro - gitea-localtime:/etc/localtime:ro - labels: - - "traefik.http.services.gitea-websecure.loadbalancer.server.port=3000" + ports: + - 22222:22 depends_on: mariadb: condition: service_healthy diff --git a/templates/compose/gitea-with-mysql.yaml b/templates/compose/gitea-with-mysql.yaml index d7cdd8a4f..d25d4952e 100644 --- a/templates/compose/gitea-with-mysql.yaml +++ b/templates/compose/gitea-with-mysql.yaml @@ -6,7 +6,7 @@ services: gitea: image: gitea/gitea:latest environment: - - SERVICE_FQDN_GITEA + - SERVICE_FQDN_GITEA_3000 - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=mysql @@ -18,8 +18,8 @@ services: - gitea-data:/var/lib/gitea - gitea-timezone:/etc/timezone:ro - gitea-localtime:/etc/localtime:ro - labels: - - "traefik.http.services.gitea-websecure.loadbalancer.server.port=3000" + ports: + - 22222:22 depends_on: mysql: condition: service_healthy diff --git a/templates/compose/gitea-with-postgresql.yaml b/templates/compose/gitea-with-postgresql.yaml index 2de0dcabb..f213a3bac 100644 --- a/templates/compose/gitea-with-postgresql.yaml +++ b/templates/compose/gitea-with-postgresql.yaml @@ -6,7 +6,7 @@ services: gitea: image: gitea/gitea:latest environment: - - SERVICE_FQDN_GITEA + - SERVICE_FQDN_GITEA_3000 - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=postgres @@ -18,8 +18,8 @@ services: - gitea-data:/var/lib/gitea - gitea-timezone:/etc/timezone:ro - gitea-localtime:/etc/localtime:ro - labels: - - "traefik.http.services.gitea-websecure.loadbalancer.server.port=3000" + ports: + - 22222:22 depends_on: postgresql: condition: service_healthy diff --git a/templates/compose/gitea.yaml b/templates/compose/gitea.yaml index aed7686a3..2d75675fa 100644 --- a/templates/compose/gitea.yaml +++ b/templates/compose/gitea.yaml @@ -9,6 +9,8 @@ services: - SERVICE_FQDN_GITEA_3000 - USER_UID=1000 - USER_GID=1000 + ports: + - 22222:22 volumes: - gitea-data:/var/lib/gitea - gitea-timezone:/etc/timezone:ro diff --git a/templates/compose/n8n-with-postgresql.yaml b/templates/compose/n8n-with-postgresql.yaml index cd96c294d..ef9208b02 100644 --- a/templates/compose/n8n-with-postgresql.yaml +++ b/templates/compose/n8n-with-postgresql.yaml @@ -8,11 +8,12 @@ services: environment: - SERVICE_FQDN_N8N - N8N_EDITOR_BASE_URL=${SERVICE_FQDN_N8N} - - N8N_HOST=${SERVICE_FQDN_N8N} + - WEBHOOK_URL=${SERVICE_FQDN_N8N} + - N8N_HOST=${SERVICE_URL_N8N} - GENERIC_TIMEZONE="Europe/Berlin" - TZ="Europe/Berlin" - DB_TYPE=postgresdb - - DB_POSTGRESDB_DATABASE=${POSTGRES_DB:-umami} + - DB_POSTGRESDB_DATABASE=${POSTGRES_DB:-n8n} - DB_POSTGRESDB_HOST=postgresql - DB_POSTGRESDB_PORT=5432 - DB_POSTGRESDB_USER=$SERVICE_USER_POSTGRES @@ -29,7 +30,7 @@ services: environment: - POSTGRES_USER=$SERVICE_USER_POSTGRES - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES - - POSTGRES_DB=${POSTGRES_DB:-umami} + - POSTGRES_DB=${POSTGRES_DB:-n8n} healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s diff --git a/templates/compose/n8n.yaml b/templates/compose/n8n.yaml index c8613cf03..99a795cf8 100644 --- a/templates/compose/n8n.yaml +++ b/templates/compose/n8n.yaml @@ -8,7 +8,8 @@ services: environment: - SERVICE_FQDN_N8N - N8N_EDITOR_BASE_URL=${SERVICE_FQDN_N8N} - - N8N_HOST=${SERVICE_FQDN_N8N} + - WEBHOOK_URL=${SERVICE_FQDN_N8N} + - N8N_HOST=${SERVICE_URL_N8N} - GENERIC_TIMEZONE="Europe/Berlin" - TZ="Europe/Berlin" volumes: diff --git a/templates/service-templates.json b/templates/service-templates.json index ff1bd0556..53add41cd 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -145,7 +145,7 @@ "gitea-with-mariadb": { "documentation": "https:\/\/docs.gitea.com", "slogan": "Gitea (with MariaDB) is a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.", - "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQQogICAgICAtIFVTRVJfVUlEPTEwMDAKICAgICAgLSBVU0VSX0dJRD0xMDAwCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19EQl9UWVBFPW15c3FsCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19IT1NUPW1hcmlhZGIKICAgICAgLSAnR0lURUFfX2RhdGFiYXNlX19OQU1FPSR7TVlTUUxfREFUQUJBU0UtZ2l0ZWF9JwogICAgICAtIEdJVEVBX19kYXRhYmFzZV9fVVNFUj0kU0VSVklDRV9VU0VSX01ZU1FMCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19QQVNTV0Q9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLWRhdGE6L3Zhci9saWIvZ2l0ZWEnCiAgICAgIC0gJ2dpdGVhLXRpbWV6b25lOi9ldGMvdGltZXpvbmU6cm8nCiAgICAgIC0gJ2dpdGVhLWxvY2FsdGltZTovZXRjL2xvY2FsdGltZTpybycKICAgIGxhYmVsczoKICAgICAgLSB0cmFlZmlrLmh0dHAuc2VydmljZXMuZ2l0ZWEtd2Vic2VjdXJlLmxvYWRiYWxhbmNlci5zZXJ2ZXIucG9ydD0zMDAwCiAgICBkZXBlbmRzX29uOgogICAgICBtYXJpYWRiOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIG1hcmlhZGI6CiAgICBpbWFnZTogJ21hcmlhZGI6MTEnCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1tYXJpYWRiLWRhdGE6L3Zhci9saWIvbXlzcWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0V9JwogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTFJPT1R9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGhlYWx0aGNoZWNrLnNoCiAgICAgICAgLSAnLS1jb25uZWN0JwogICAgICAgIC0gJy0taW5ub2RiX2luaXRpYWxpemVkJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0RCX1RZUEU9bXlzcWwKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0hPU1Q9bWFyaWFkYgogICAgICAtICdHSVRFQV9fZGF0YWJhc2VfX05BTUU9JHtNWVNRTF9EQVRBQkFTRS1naXRlYX0nCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19VU0VSPSRTRVJWSUNFX1VTRVJfTVlTUUwKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX1BBU1NXRD0kU0VSVklDRV9QQVNTV09SRF9NWVNRTAogICAgdm9sdW1lczoKICAgICAgLSAnZ2l0ZWEtZGF0YTovdmFyL2xpYi9naXRlYScKICAgICAgLSAnZ2l0ZWEtdGltZXpvbmU6L2V0Yy90aW1lem9uZTpybycKICAgICAgLSAnZ2l0ZWEtbG9jYWx0aW1lOi9ldGMvbG9jYWx0aW1lOnJvJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjIyOjIyJwogICAgZGVwZW5kc19vbjoKICAgICAgbWFyaWFkYjoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBtYXJpYWRiOgogICAgaW1hZ2U6ICdtYXJpYWRiOjExJwogICAgdm9sdW1lczoKICAgICAgLSAnZ2l0ZWEtbWFyaWFkYi1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBoZWFsdGhjaGVjay5zaAogICAgICAgIC0gJy0tY29ubmVjdCcKICAgICAgICAtICctLWlubm9kYl9pbml0aWFsaXplZCcKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "version control", "collaboration", @@ -158,7 +158,7 @@ "gitea-with-mysql": { "documentation": "https:\/\/docs.gitea.com", "slogan": "Gitea (with MySQL) is a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.", - "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQQogICAgICAtIFVTRVJfVUlEPTEwMDAKICAgICAgLSBVU0VSX0dJRD0xMDAwCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19EQl9UWVBFPW15c3FsCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19IT1NUPW15c3FsCiAgICAgIC0gJ0dJVEVBX19kYXRhYmFzZV9fTkFNRT0ke01ZU1FMX0RBVEFCQVNFLWdpdGVhfScKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX1VTRVI9JFNFUlZJQ0VfVVNFUl9NWVNRTAogICAgICAtIEdJVEVBX19kYXRhYmFzZV9fUEFTU1dEPSRTRVJWSUNFX1BBU1NXT1JEX01ZU1FMCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1kYXRhOi92YXIvbGliL2dpdGVhJwogICAgICAtICdnaXRlYS10aW1lem9uZTovZXRjL3RpbWV6b25lOnJvJwogICAgICAtICdnaXRlYS1sb2NhbHRpbWU6L2V0Yy9sb2NhbHRpbWU6cm8nCiAgICBsYWJlbHM6CiAgICAgIC0gdHJhZWZpay5odHRwLnNlcnZpY2VzLmdpdGVhLXdlYnNlY3VyZS5sb2FkYmFsYW5jZXIuc2VydmVyLnBvcnQ9MzAwMAogICAgZGVwZW5kc19vbjoKICAgICAgbXlzcWw6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgbXlzcWw6CiAgICBpbWFnZTogJ215c3FsOjguMCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLW15c3FsLWRhdGE6L3Zhci9saWIvbXlzcWwnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnTVlTUUxfVVNFUj0ke1NFUlZJQ0VfVVNFUl9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTH0nCiAgICAgIC0gJ01ZU1FMX0RBVEFCQVNFPSR7TVlTUUxfREFUQUJBU0V9JwogICAgICAtICdNWVNRTF9ST09UX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9NWVNRTFJPT1R9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIG15c3FsYWRtaW4KICAgICAgICAtIHBpbmcKICAgICAgICAtICctaCcKICAgICAgICAtIGxvY2FsaG9zdAogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0RCX1RZUEU9bXlzcWwKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0hPU1Q9bXlzcWwKICAgICAgLSAnR0lURUFfX2RhdGFiYXNlX19OQU1FPSR7TVlTUUxfREFUQUJBU0UtZ2l0ZWF9JwogICAgICAtIEdJVEVBX19kYXRhYmFzZV9fVVNFUj0kU0VSVklDRV9VU0VSX01ZU1FMCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19QQVNTV0Q9JFNFUlZJQ0VfUEFTU1dPUkRfTVlTUUwKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLWRhdGE6L3Zhci9saWIvZ2l0ZWEnCiAgICAgIC0gJ2dpdGVhLXRpbWV6b25lOi9ldGMvdGltZXpvbmU6cm8nCiAgICAgIC0gJ2dpdGVhLWxvY2FsdGltZTovZXRjL2xvY2FsdGltZTpybycKICAgIHBvcnRzOgogICAgICAtICcyMjIyMjoyMicKICAgIGRlcGVuZHNfb246CiAgICAgIG15c3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIG15c3FsOgogICAgaW1hZ2U6ICdteXNxbDo4LjAnCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1teXNxbC1kYXRhOi92YXIvbGliL215c3FsJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ01ZU1FMX1VTRVI9JHtTRVJWSUNFX1VTRVJfTVlTUUx9JwogICAgICAtICdNWVNRTF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUx9JwogICAgICAtICdNWVNRTF9EQVRBQkFTRT0ke01ZU1FMX0RBVEFCQVNFfScKICAgICAgLSAnTVlTUUxfUk9PVF9QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfTVlTUUxST09UfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBteXNxbGFkbWluCiAgICAgICAgLSBwaW5nCiAgICAgICAgLSAnLWgnCiAgICAgICAgLSBsb2NhbGhvc3QKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", "tags": [ "version control", "collaboration", @@ -171,7 +171,7 @@ "gitea-with-postgresql": { "documentation": "https:\/\/docs.gitea.com", "slogan": "Gitea (with PostgreSQL)vis a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.", - "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQQogICAgICAtIFVTRVJfVUlEPTEwMDAKICAgICAgLSBVU0VSX0dJRD0xMDAwCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19EQl9UWVBFPXBvc3RncmVzCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19IT1NUPXBvc3RncmVzcWwKICAgICAgLSAnR0lURUFfX2RhdGFiYXNlX19OQU1FPSR7UE9TVEdSRVNRTF9EQVRBQkFTRS1naXRlYX0nCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19VU0VSPSRTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTAogICAgICAtIEdJVEVBX19kYXRhYmFzZV9fUEFTU1dEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUwKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLWRhdGE6L3Zhci9saWIvZ2l0ZWEnCiAgICAgIC0gJ2dpdGVhLXRpbWV6b25lOi9ldGMvdGltZXpvbmU6cm8nCiAgICAgIC0gJ2dpdGVhLWxvY2FsdGltZTovZXRjL2xvY2FsdGltZTpybycKICAgIGxhYmVsczoKICAgICAgLSB0cmFlZmlrLmh0dHAuc2VydmljZXMuZ2l0ZWEtd2Vic2VjdXJlLmxvYWRiYWxhbmNlci5zZXJ2ZXIucG9ydD0zMDAwCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3Jlc3FsOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE1LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0V9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAo=", + "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0RCX1RZUEU9cG9zdGdyZXMKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX0hPU1Q9cG9zdGdyZXNxbAogICAgICAtICdHSVRFQV9fZGF0YWJhc2VfX05BTUU9JHtQT1NUR1JFU1FMX0RBVEFCQVNFLWdpdGVhfScKICAgICAgLSBHSVRFQV9fZGF0YWJhc2VfX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gR0lURUFfX2RhdGFiYXNlX19QQVNTV0Q9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTAogICAgdm9sdW1lczoKICAgICAgLSAnZ2l0ZWEtZGF0YTovdmFyL2xpYi9naXRlYScKICAgICAgLSAnZ2l0ZWEtdGltZXpvbmU6L2V0Yy90aW1lem9uZTpybycKICAgICAgLSAnZ2l0ZWEtbG9jYWx0aW1lOi9ldGMvbG9jYWx0aW1lOnJvJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMjIyOjIyJwogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXNxbDoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjMwMDAnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUKICBwb3N0Z3Jlc3FsOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNS1hbHBpbmUnCiAgICB2b2x1bWVzOgogICAgICAtICdnaXRlYS1wb3N0Z3Jlc3FsLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU1FMX0RBVEFCQVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", "tags": [ "version control", "collaboration", @@ -184,7 +184,7 @@ "gitea": { "documentation": "https:\/\/docs.gitea.com", "slogan": "Gitea is a self-hosted, lightweight Git service, offering version control, collaboration, and code hosting.", - "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLWRhdGE6L3Zhci9saWIvZ2l0ZWEnCiAgICAgIC0gJ2dpdGVhLXRpbWV6b25lOi9ldGMvdGltZXpvbmU6cm8nCiAgICAgIC0gJ2dpdGVhLWxvY2FsdGltZTovZXRjL2xvY2FsdGltZTpybycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", + "compose": "c2VydmljZXM6CiAgZ2l0ZWE6CiAgICBpbWFnZTogJ2dpdGVhL2dpdGVhOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9HSVRFQV8zMDAwCiAgICAgIC0gVVNFUl9VSUQ9MTAwMAogICAgICAtIFVTRVJfR0lEPTEwMDAKICAgIHBvcnRzOgogICAgICAtICcyMjIyMjoyMicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2dpdGVhLWRhdGE6L3Zhci9saWIvZ2l0ZWEnCiAgICAgIC0gJ2dpdGVhLXRpbWV6b25lOi9ldGMvdGltZXpvbmU6cm8nCiAgICAgIC0gJ2dpdGVhLWxvY2FsdGltZTovZXRjL2xvY2FsdGltZTpybycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDozMDAwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==", "tags": [ "version control", "collaboration", @@ -309,7 +309,7 @@ "n8n-with-postgresql": { "documentation": "https:\/\/docs.n8n.io\/hosting\/", "slogan": "n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.", - "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnR0VORVJJQ19USU1FWk9ORT0iRXVyb3BlL0JlcmxpbiInCiAgICAgIC0gJ1RaPSJFdXJvcGUvQmVybGluIicKICAgICAgLSBEQl9UWVBFPXBvc3RncmVzZGIKICAgICAgLSAnREJfUE9TVEdSRVNEQl9EQVRBQkFTRT0ke1BPU1RHUkVTX0RCOi11bWFtaX0nCiAgICAgIC0gREJfUE9TVEdSRVNEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1NUR1JFU0RCX1BPUlQ9NTQzMgogICAgICAtIERCX1BPU1RHUkVTREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gREJfUE9TVEdSRVNEQl9TQ0hFTUE9cHVibGljCiAgICAgIC0gREJfUE9TVEdSRVNEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnbjhuLWRhdGE6L2hvbWUvbm9kZS8ubjhuJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LXVtYW1pfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK", + "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnV0VCSE9PS19VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX1VSTF9OOE59JwogICAgICAtICdHRU5FUklDX1RJTUVaT05FPSJFdXJvcGUvQmVybGluIicKICAgICAgLSAnVFo9IkV1cm9wZS9CZXJsaW4iJwogICAgICAtIERCX1RZUEU9cG9zdGdyZXNkYgogICAgICAtICdEQl9QT1NUR1JFU0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICAgIC0gREJfUE9TVEdSRVNEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1NUR1JFU0RCX1BPUlQ9NTQzMgogICAgICAtIERCX1BPU1RHUkVTREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTCiAgICAgIC0gREJfUE9TVEdSRVNEQl9TQ0hFTUE9cHVibGljCiAgICAgIC0gREJfUE9TVEdSRVNEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgdm9sdW1lczoKICAgICAgLSAnbjhuLWRhdGE6L2hvbWUvbm9kZS8ubjhuJwogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3Jlc3FsCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFUwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNfREI6LW44bn0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", "tags": [ "n8n", "workflow", @@ -323,7 +323,7 @@ "n8n": { "documentation": "https:\/\/docs.n8n.io\/hosting\/", "slogan": "n8n is an extendable workflow automation tool which enables you to connect anything to everything via its open, fair-code model.", - "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnR0VORVJJQ19USU1FWk9ORT0iRXVyb3BlL0JlcmxpbiInCiAgICAgIC0gJ1RaPSJFdXJvcGUvQmVybGluIicKICAgIHZvbHVtZXM6CiAgICAgIC0gJ244bi1kYXRhOi9ob21lL25vZGUvLm44bicK", + "compose": "c2VydmljZXM6CiAgbjhuOgogICAgaW1hZ2U6IGRvY2tlci5uOG4uaW8vbjhuaW8vbjhuCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fTjhOCiAgICAgIC0gJ044Tl9FRElUT1JfQkFTRV9VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnV0VCSE9PS19VUkw9JHtTRVJWSUNFX0ZRRE5fTjhOfScKICAgICAgLSAnTjhOX0hPU1Q9JHtTRVJWSUNFX1VSTF9OOE59JwogICAgICAtICdHRU5FUklDX1RJTUVaT05FPSJFdXJvcGUvQmVybGluIicKICAgICAgLSAnVFo9IkV1cm9wZS9CZXJsaW4iJwogICAgdm9sdW1lczoKICAgICAgLSAnbjhuLWRhdGE6L2hvbWUvbm9kZS8ubjhuJwo=", "tags": [ "n8n", "workflow", diff --git a/versions.json b/versions.json index 4ebbef973..cff2caaf6 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.111" + "version": "4.0.0-beta.112" } } }