Merge branch 'next' into main

This commit is contained in:
Andras Bacsai
2024-08-14 21:00:56 +02:00
committed by GitHub
33 changed files with 904 additions and 430 deletions

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
class OpenApi extends Command
{
protected $signature = 'openapi';
protected $description = 'Generate OpenApi file.';
public function handle()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
}
}

View File

@@ -201,7 +201,7 @@ class ApplicationsController extends Controller
#[OA\Post( #[OA\Post(
summary: 'Create (Private - GH App)', summary: 'Create (Private - GH App)',
description: 'Create new application based on a private repository through a Github App.', description: 'Create new application based on a private repository through a Github App.',
path: '/applications/private-gh-app', path: '/applications/private-github-app',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
if (! $found_app) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$env->delete();
return response()->json([
'message' => 'Environment variable deleted.',
]);
}
}

View File

@@ -157,7 +157,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private ?string $coolify_variables = null; private ?string $coolify_variables = null;
private bool $preserveRepository = true; private bool $preserveRepository = false;
public $tries = 1; public $tries = 1;
@@ -480,6 +480,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
// Start compose file // Start compose file
$server_workdir = $this->application->workdir();
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) { if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
@@ -488,7 +489,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
$this->docker_compose_location = '/docker-compose.yaml'; $this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
@@ -508,15 +508,26 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) { if ($this->preserveRepository) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}"; if ($this->env_filename) {
} $command .= " --env-file {$server_workdir}/{$this->env_filename}";
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; }
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
} else {
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
}
$this->write_deployment_configurations();
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
} }
} }
@@ -619,6 +630,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
], ],
); );
} }
$this->application->fileStorages()->each(function ($fileStorage) {
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
$fileStorage->saveStorageOnServer();
}
});
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->build_server; $this->server = $this->build_server;
} }
@@ -1708,13 +1724,17 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
} }
if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
$docker_compose['services'][$this->container_name]['volumes'] = [];
}
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; $docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages);
} }
if (count($persistent_file_volumes) > 0) { if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { $docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path"; return "$item->fs_path:$item->mount_path";
})->toArray(); })->toArray());
} }
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;

View File

@@ -336,7 +336,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$url = $this->database->internal_db_url; $url = $this->database->internal_db_url;
if ($databaseWithCollections === 'all') { if ($databaseWithCollections === 'all') {
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
@@ -351,13 +351,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} }
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if ($collectionsToExclude->count() === 0) { if ($collectionsToExclude->count() === 0) {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
} }
} else { } else {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";

View File

@@ -3,7 +3,6 @@
namespace App\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@@ -30,6 +29,8 @@ class General extends Component
public ?string $ports_exposes = null; public ?string $ports_exposes = null;
public bool $is_preserve_repository_enabled = false;
public bool $is_container_label_escape_enabled = true; public bool $is_container_label_escape_enabled = true;
public $customLabels; public $customLabels;
@@ -145,6 +146,7 @@ class General extends Component
} }
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels(); $this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
@@ -168,9 +170,21 @@ class General extends Component
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->application->refresh(); $this->application->refresh();
// If port_exposes changed, reset default labels
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
} }
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
if ($this->application->settings->is_preserve_repository_enabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
$storage->save();
});
}
}
} }
public function loadComposeFile($isInit = false) public function loadComposeFile($isInit = false)
@@ -179,6 +193,11 @@ class General extends Component
if ($isInit && $this->application->docker_compose_raw) { if ($isInit && $this->application->docker_compose_raw) {
return; return;
} }
// Must reload the application to get the latest database changes
// Why? Not sure, but it works.
$this->application->refresh();
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
if (is_null($this->parsedServices)) { if (is_null($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
@@ -186,32 +205,6 @@ class General extends Component
return; return;
} }
$compose = $this->application->parseCompose(); $compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded'); $this->dispatch('compose_loaded');
$this->dispatch('refreshStorages'); $this->dispatch('refreshStorages');

View File

@@ -5,6 +5,8 @@ namespace App\Livewire\Project\New;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Project; use App\Models\Project;
use App\Models\Service; use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -58,12 +60,26 @@ class DockerCompose extends Component
$project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (! $destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$service = Service::create([ $service = Service::create([
'name' => 'service'.Str::random(10), 'name' => 'service'.Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw, 'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
]); ]);
$variables = parseEnvFormatToArray($this->envFile); $variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) { foreach ($variables as $key => $variable) {
EnvironmentVariable::create([ EnvironmentVariable::create([

View File

@@ -33,6 +33,7 @@ class FileStorage extends Component
'fileStorage.fs_path' => 'required', 'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required', 'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable', 'fileStorage.content' => 'nullable',
'fileStorage.is_based_on_git' => 'required|boolean',
]; ];
public function mount() public function mount()
@@ -45,6 +46,7 @@ class FileStorage extends Component
$this->workdir = null; $this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path; $this->fs_path = $this->fileStorage->fs_path;
} }
$this->fileStorage->loadStorageOnServer();
} }
public function convertToDirectory() public function convertToDirectory()
@@ -68,6 +70,9 @@ class FileStorage extends Component
$this->fileStorage->deleteStorageOnServer(); $this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false; $this->fileStorage->is_directory = false;
$this->fileStorage->content = null; $this->fileStorage->content = null;
if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) {
$this->fileStorage->is_based_on_git = true;
}
$this->fileStorage->save(); $this->fileStorage->save();
$this->fileStorage->saveStorageOnServer(); $this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -24,7 +24,8 @@ class Show extends Component
public string $type; public string $type;
protected $listeners = [ protected $listeners = [
'refresh' => 'refresh', 'refreshEnvs' => 'refresh',
'refresh',
'compose_loaded' => '$refresh', 'compose_loaded' => '$refresh',
]; ];

View File

@@ -16,7 +16,7 @@ class Show extends Component
public array $parameters; public array $parameters;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey'];
public function saveKey($data) public function saveKey($data)
{ {

View File

@@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
#[OA\Schema( #[OA\Schema(
@@ -97,8 +96,22 @@ class EnvironmentVariable extends Model
$resource = Application::find($this->application_id); $resource = Application::find($this->application_id);
} elseif ($this->service_id) { } elseif ($this->service_id) {
$resource = Service::find($this->service_id); $resource = Service::find($this->service_id);
} elseif ($this->database_id) { } elseif ($this->standalone_postgresql_id) {
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id')); $resource = StandalonePostgresql::find($this->standalone_postgresql_id);
} elseif ($this->standalone_redis_id) {
$resource = StandaloneRedis::find($this->standalone_redis_id);
} elseif ($this->standalone_mongodb_id) {
$resource = StandaloneMongodb::find($this->standalone_mongodb_id);
} elseif ($this->standalone_mysql_id) {
$resource = StandaloneMysql::find($this->standalone_mysql_id);
} elseif ($this->standalone_mariadb_id) {
$resource = StandaloneMariadb::find($this->standalone_mariadb_id);
} elseif ($this->standalone_keydb_id) {
$resource = StandaloneKeydb::find($this->standalone_keydb_id);
} elseif ($this->standalone_dragonfly_id) {
$resource = StandaloneDragonfly::find($this->standalone_dragonfly_id);
} elseif ($this->standalone_clickhouse_id) {
$resource = StandaloneClickhouse::find($this->standalone_clickhouse_id);
} }
return $resource; return $resource;
@@ -122,63 +135,6 @@ class EnvironmentVariable extends Model
); );
} }
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (! $this->application_id) {
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (! $compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose || $found_in_args;
}
);
}
protected function isShared(): Attribute protected function isShared(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -201,8 +157,10 @@ class EnvironmentVariable extends Model
$environment_variable = trim($environment_variable); $environment_variable = trim($environment_variable);
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/'); $sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
if ($sharedEnvsFound->isEmpty()) { if ($sharedEnvsFound->isEmpty()) {
return $environment_variable; return $environment_variable;
} }
foreach ($sharedEnvsFound as $sharedEnv) { foreach ($sharedEnvsFound as $sharedEnv) {
$type = str($sharedEnv)->match('/(.*?)\./'); $type = str($sharedEnv)->match('/(.*?)\./');
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {

View File

@@ -24,6 +24,32 @@ class LocalFileVolume extends BaseModel
return $this->morphTo('resource'); return $this->morphTo('resource');
} }
public function loadStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([]);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
if ($isFile === 'OK') {
$content = instant_remote_process(["cat $path"], $server, false);
$this->content = $content;
$this->is_directory = false;
$this->save();
}
}
public function deleteStorageOnServer() public function deleteStorageOnServer()
{ {
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
@@ -35,17 +61,20 @@ class LocalFileVolume extends BaseModel
$server = $this->resource->destination->server; $server = $this->resource->destination->server;
} }
$commands = collect([]); $commands = collect([]);
$fs_path = data_get($this, 'fs_path'); $path = data_get_str($this, 'fs_path');
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server); if ($path->startsWith('.')) {
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server); $path = $path->after('.');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') { $path = $workdir.$path;
ray($isFile, $isDir); }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') { if ($isFile === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $commands->push("rm -rf $path > /dev/null 2>&1 || true");
} elseif ($isDir === 'OK') { } elseif ($isDir === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true"); $commands->push("rmdir $path > /dev/null 2>&1 || true");
} }
} }
if ($commands->count() > 0) { if ($commands->count() > 0) {
@@ -55,6 +84,7 @@ class LocalFileVolume extends BaseModel
public function saveStorageOnServer() public function saveStorageOnServer()
{ {
$this->load(['service']);
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
if ($isService) { if ($isService) {
$workdir = $this->resource->service->workdir(); $workdir = $this->resource->service->workdir();
@@ -74,30 +104,36 @@ class LocalFileVolume extends BaseModel
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true"); $commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
} }
} }
$fileVolume = $this; $path = data_get_str($this, 'fs_path');
$path = str(data_get($fileVolume, 'fs_path')); $content = data_get($this, 'content');
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) { if ($path->startsWith('.')) {
$path = $path->after('.'); $path = $path->after('.');
$path = $workdir.$path; $path = $workdir.$path;
} }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) { if ($isFile == 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false); $content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false; $this->is_directory = false;
$fileVolume->content = $content; $this->content = $content;
$fileVolume->save(); $this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id')); FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.'); throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) { } elseif ($isDir == 'OK' && ! $this->is_directory) {
$fileVolume->is_directory = true; if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
$fileVolume->save(); $this->is_directory = true;
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.'); $this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
}
instant_remote_process([
"rm -fr $path",
"touch $path",
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
} }
if ($isDir == 'NOK' && ! $fileVolume->is_directory) { if ($isDir == 'NOK' && ! $this->is_directory) {
$chmod = data_get($fileVolume, 'chmod'); $chmod = data_get($this, 'chmod');
$chown = data_get($fileVolume, 'chown'); $chown = data_get($this, 'chown');
if ($content) { if ($content) {
$content = base64_encode($content); $content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null"); $commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
@@ -111,7 +147,7 @@ class LocalFileVolume extends BaseModel
if ($chmod) { if ($chmod) {
$commands->push("chmod $chmod $path"); $commands->push("chmod $chmod $path");
} }
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) { } elseif ($isDir == 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true"); $commands->push("mkdir -p $path > /dev/null 2>&1 || true");
} }

View File

@@ -23,6 +23,7 @@ use Symfony\Component\Yaml\Yaml;
'description' => ['type' => 'string', 'description' => 'The description of the service.'], 'description' => ['type' => 'string', 'description' => 'The description of the service.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'], 'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
'destination_type' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'], 'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
@@ -205,6 +206,41 @@ class Service extends BaseModel
foreach ($applications as $application) { foreach ($applications as $application) {
$image = str($application->image)->before(':')->value(); $image = str($application->image)->before(':')->value();
switch ($image) { switch ($image) {
case str($image)?->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first();
if ($host_port) {
$data = $data->merge([
'Host Port Binding' => [
'key' => data_get($host_port, 'key'),
'value' => data_get($host_port, 'value'),
'rules' => 'required',
],
]);
}
if ($username) {
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('RabbitMQ', $data->toArray());
break;
case str($image)?->contains('tolgee'): case str($image)?->contains('tolgee'):
$data = collect([]); $data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
@@ -504,6 +540,9 @@ class Service extends BaseModel
default: default:
$data = collect([]); $data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) { if ($admin_user) {
$data = $data->merge([ $data = $data->merge([
@@ -525,6 +564,15 @@ class Service extends BaseModel
], ],
]); ]);
} }
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray()); $fields->put('Admin', $data->toArray());
break; break;
case str($image)?->contains('vaultwarden'): case str($image)?->contains('vaultwarden'):

View File

@@ -794,7 +794,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
$topLevelVolumes = collect($tempTopLevelVolumes); $topLevelVolumes = collect($tempTopLevelVolumes);
} }
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) { $services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices, $topLevelVolumes) {
// Workarounds for beta users. // Workarounds for beta users.
if ($serviceName === 'registry') { if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry'; $tempServiceName = 'docker-registry';
@@ -963,102 +963,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update volumes // Collect/create/update volumes
if ($serviceVolumes->count() > 0) { if ($serviceVolumes->count() > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) { ['serviceVolumes' => $serviceVolumes, 'topLevelVolumes' => $topLevelVolumes] = parseServiceVolumes($serviceVolumes, $savedService, $topLevelVolumes);
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = str('bind');
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull = data_get($foundConfig, 'content');
if ($contentNotNull) {
$content = $contentNotNull;
}
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
}
if (is_null($isDirectory) && is_null($content)) {
// if isDirectory is not set & content is also not set, we assume it is a directory
ray('setting isDirectory to true');
$isDirectory = true;
}
}
if ($type?->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]
);
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevelVolumes->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]
);
}
dispatch(new ServerFilesFromServerJob($savedService));
return $volume;
});
data_set($service, 'volumes', $serviceVolumes->toArray()); data_set($service, 'volumes', $serviceVolumes->toArray());
} }
@@ -1645,131 +1550,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} elseif ($resource->compose_parsing_version === '2') { } elseif ($resource->compose_parsing_version === '2') {
if (count($serviceVolumes) > 0) { if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { ['serviceVolumes' => $serviceVolumes, 'topLevelVolumes' => $topLevelVolumes] = parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id);
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$uuid = $resource->uuid;
$name = $uuid."-$name-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $name);
data_set($topLevelVolumes, $name, $v);
}
}
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
$uuid = $resource->uuid;
$name = str($uuid."-$name");
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
}
} elseif (is_array($volume)) {
$source = data_get($volume, 'source');
$target = data_get($volume, 'target');
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
$uuid = $resource->uuid;
if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
} else {
if ($pull_request_id === 0) {
$source = $uuid."-$source";
} else {
$source = $uuid."-$source-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
if (! str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
}
}
} else {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
}
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
dispatch(new ServerFilesFromServerJob($resource));
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray()); data_set($service, 'volumes', $serviceVolumes->toArray());
} }
} }
@@ -2662,3 +2443,127 @@ function customApiValidator(Collection|array $item, array $rules)
'required' => 'This field is required.', 'required' => 'This field is required.',
]); ]);
} }
function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id = 0)
{
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = str('bind');
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $resource->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull = data_get($foundConfig, 'content');
if ($contentNotNull) {
$content = $contentNotNull;
}
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
}
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
ray('setting isDirectory to true');
$isDirectory = true;
}
}
if ($type?->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
if (get_class($resource) === "App\Models\Application") {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
} else {
$dir = base_configuration_dir().'/services/'.$resource->service->uuid;
}
if ($source->startsWith('.')) {
$source = $source->replaceFirst('.', $dir);
}
if ($source->startsWith('~')) {
$source = $source->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
}
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
]
);
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
if (get_class($resource) === "App\Models\Application") {
$name = "{$resource->uuid}_{$slugWithoutUuid}";
} else {
$name = "{$resource->service->uuid}_{$slugWithoutUuid}";
}
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevelVolumes->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
]
);
}
dispatch(new ServerFilesFromServerJob($resource));
return $volume;
});
return [
'serviceVolumes' => $serviceVolumes,
'topLevelVolumes' => $topLevelVolumes,
];
}

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.323', 'release' => '4.0.0-beta.324',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.323'; return '4.0.0-beta.324';

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->boolean('is_based_on_git')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->dropColumn('is_based_on_git');
});
}
};

View File

@@ -247,13 +247,13 @@ paths:
security: security:
- -
bearerAuth: [] bearerAuth: []
/applications/private-gh-app: /applications/private-github-app:
post: post:
tags: tags:
- Applications - Applications
summary: 'Create (Private - GH App)' summary: 'Create (Private - GH App)'
description: 'Create new application based on a private repository through a Github App.' description: 'Create new application based on a private repository through a Github App.'
operationId: 4d46c84bda4f1a411f6dda15fce4061f operationId: 8b7af9c9a509385963bf3e72eeeea786
requestBody: requestBody:
description: 'Application object that needs to be created.' description: 'Application object that needs to be created.'
required: true required: true

21
public/svgs/budibase.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg width="1200" height="265" viewBox="0 0 1200 265" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_17)">
<path d="M456.1 140.1C451.8 135.6 446.3 132.2 439.5 130C459.2 121.8 463.3 97.1 452.7 80.7C443.4 67.5 427.4 64.2 410.3 64C395 64 370.2 64 355.1 64V204C361.1 204 375.9 204 381.7 204C397.9 203.6 428.5 206 442.5 199.5C466.7 191.5 473.4 158 456.1 140.1ZM408.1 86.8C414.8 86.8 420.2 88 424.4 90.4C434.4 96.1 432.6 117.1 419.9 119.5C412.7 122.1 389.7 120.7 381.7 121V86.8H408.1ZM431.7 176.7C427.3 179.7 421.4 181.2 413.9 181.2H381.7V142.4H413.9C421.4 142.4 427.3 143.9 431.7 146.8C440.5 151.7 440.5 171.6 431.7 176.7Z" fill="black"/>
<path d="M527.6 206.3C502.3 207 481.5 188.6 482.4 162.5V101.1H507.6V161C507.3 175 514.6 183.8 527.6 184C540.6 183.8 547.8 175 547.6 161V101.2H572.6V162.6C573.5 188.5 552.7 207 527.6 206.3Z" fill="black"/>
<path d="M666.6 69.3V116.3C656.2 97.1 628.2 93.7 610.7 105.7C595.5 115.9 588.1 133.5 588.2 152.5C588.1 171.3 595.6 189.2 610.7 199.3C628.1 211.4 656.1 207.9 666.6 188.8V204H691.6V63L666.6 69.3ZM659.7 175.8C650.5 186.7 630.5 186.7 621.3 175.8C611.4 165.9 611.4 139.3 621.3 129.4C630.5 118.5 650.5 118.5 659.7 129.4C669.5 139.2 669.5 165.9 659.7 175.8Z" fill="black"/>
<path d="M730.9 88.8C709 89.3 709.1 58 730.9 58.4C752.4 58.3 752.5 89 730.9 88.8ZM743.1 101.2V204H718.1V101.2H743.1Z" fill="black"/>
<path d="M867 124.9C854.6 96.2 811.3 88 794.6 116.4V63L769.6 69.3V204H794.6V188.9C805 208 833.3 211.5 850.6 199.4C873.6 184.5 878.7 148.4 867 124.9ZM840 175.8C830.8 186.7 810.8 186.7 801.6 175.8C791.7 165.9 791.7 139.3 801.6 129.4C810.8 118.5 830.8 118.5 840 129.4C849.9 139.2 849.9 165.9 840 175.8Z" fill="black"/>
<path d="M973.4 116.4C954.1 90.2 902.3 92.8 890.1 125.1L911.2 134C917.7 118.9 943.3 115.9 952.1 130C955.5 134.8 954.8 145.2 954.8 151.4C935.4 133 885 141.8 886 174.3C884.7 208.5 938.3 216.1 956.4 194V203.9H979.8V139.3C979.8 130 977.6 122.4 973.4 116.4ZM949.2 184.3C941.7 189.3 925.4 189.2 917.9 184.4C909.8 180.1 909.8 167.9 917.9 163.6C928.4 157.6 954.7 157.6 955.2 174C955.2 178.4 953.2 181.8 949.2 184.3Z" fill="black"/>
<path d="M1043.7 206.3C1022.2 206.7 1001.5 194.6 996.9 172.3L1017.1 165.7C1019.8 179.2 1030.7 185.9 1043.7 185.9C1052.2 185.7 1062.1 184.1 1062.9 175.7C1062.2 166.9 1053.4 166.5 1044.3 163.5C1032.2 161.1 1014.7 155.9 1008.5 149C996.7 138.1 998 116.4 1011.7 107.1C1034.3 90.3 1082.5 98.3 1085.7 131.9L1064.9 137.3C1062.8 124.5 1053.5 119.2 1041.5 119.1C1033.7 119.2 1024.9 121.1 1024.3 128.9C1025.3 138.1 1036.6 138.2 1044.3 140.9C1056 143.2 1072.3 147.9 1078.8 155.1C1088.6 164 1089.5 180.8 1081.5 191.1C1072.9 202.3 1058.6 206.3 1043.7 206.3Z" fill="black"/>
<path d="M1193.8 124.2C1182.3 99.2 1148.3 91.9 1125.7 105.5C1109.3 115.3 1101.5 132.8 1101.6 152.6C1101.5 172.3 1109.6 190.1 1126.2 199.7C1150.3 213.7 1187 205.7 1198 177.9L1176.5 170.2C1170 184.2 1151.7 188.7 1138.8 181.4C1131.3 176.8 1127.8 167.6 1127 158.2H1199.7C1200.7 146.3 1198.7 133.5 1193.8 124.2ZM1151.6 119C1164.3 119 1172.5 128.2 1174.5 140.8H1127.8C1130 127.9 1138 118.9 1151.6 119Z" fill="black"/>
<path d="M158.2 8.6V116.6C158.2 121.3 162 125.2 166.8 125.2H213.8C218 125.2 222 123.2 224.6 119.8L262.9 68.9C265.7 65.2 265.7 60.1 262.9 56.4L224.6 5.4C222 2 218 0 213.8 0H166.8C162 0 158.2 3.8 158.2 8.6Z" fill="#FF4E4E"/>
<path d="M158.2 148.4V256.4C158.2 261.1 162 265 166.8 265H213.8C218 265 222 263 224.6 259.6L262.9 208.7C265.7 205 265.7 199.9 262.9 196.2L224.6 145.3C222.1 141.9 218.1 139.9 213.8 139.9H166.8C162 139.8 158.2 143.7 158.2 148.4Z" fill="#6E56FF"/>
<path d="M0 8.6V116.6C0 121.3 3.8 125.2 8.6 125.2H109.6C113.8 125.2 117.8 123.2 120.4 119.8L155.9 72.5C160.3 66.6 160.3 58.5 155.9 52.6L120.3 5.4C117.8 2 113.8 0 109.5 0H8.6C3.8 0 0 3.8 0 8.6Z" fill="#F97777"/>
<path d="M0 148.4V256.4C0 261.1 3.8 265 8.6 265H109.6C113.8 265 117.8 263 120.4 259.6L155.9 212.3C160.3 206.4 160.3 198.3 155.9 192.4L120.4 145.1C117.9 141.7 113.9 139.7 109.6 139.7H8.6C3.8 139.8 0 143.7 0 148.4Z" fill="#9F8FFF"/>
</g>
<defs>
<clipPath id="clip0_1_17">
<rect width="1200" height="265" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/svgs/chaskiq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

1
public/svgs/rabbitmq.svg Normal file
View File

@@ -0,0 +1 @@
<svg height="271" preserveAspectRatio="xMidYMid" viewBox="0 0 256 271" width="256" xmlns="http://www.w3.org/2000/svg"><path d="m245.44 108.307692h-85.090462c-4.268307 0-7.734153-3.465846-7.734153-7.734154v-88.6793842c0-6.56738457-5.32677-11.8941538-11.889231-11.8941538h-30.375385c-6.567384 0-11.8892305 5.32676923-11.8892305 11.8941538v88.1427692c0 4.573539-3.6972308 8.290462-8.2707693 8.310154l-27.8843077.132923c-4.612923.024615-8.3593846-3.716923-8.3495384-8.324923l.1723077-88.2412308c.0147692-6.57723082-5.312-11.9138462-11.8892308-11.9138462h-30.3507692c-6.56738465 0-11.8892308 5.32676923-11.8892308 11.8941538v248.3150772c0 5.833846 4.72615385 10.56 10.5550769 10.56h234.8849231c5.833846 0 10.56-4.726154 10.56-10.56v-141.341539c0-5.833846-4.726154-10.56-10.56-10.56zm-39.901538 93.233231c0 7.645539-6.198154 13.843692-13.843693 13.843692h-24.004923c-7.645538 0-13.843692-6.198153-13.843692-13.843692v-24.004923c0-7.645538 6.198154-13.843692 13.843692-13.843692h24.004923c7.645539 0 13.843693 6.198154 13.843693 13.843692z" fill="#f60"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

29
public/svgs/windmill.svg Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{opacity:0.4;fill:#FFFFFF;}
.st2{fill:#BCD4FC;}
.st3{fill:#3B82F6;}
.st4{fill:#B3B3B3;}
.st5{fill:url(#SVGID_1_);}
.st6{fill:url(#SVGID_00000021089067129159788970000008246765442136188072_);}
.st7{fill:url(#SVGID_00000117639240116366130650000015074833605515028638_);}
.st8{opacity:0.4;fill:url(#SVGID_00000101781798616409025840000016567063639337360777_);}
.st9{opacity:0.4;fill:url(#SVGID_00000052086836598721292040000002033117744178971046_);}
.st10{opacity:0.4;fill:url(#SVGID_00000159460939004760751800000002448009281983951536_);}
.st11{opacity:0.4;fill:url(#SVGID_00000013177830667419993080000017721442101626521532_);}
.st12{opacity:0.4;fill:url(#SVGID_00000152235521444854938490000006526001119318383285_);}
.st13{opacity:0.4;fill:url(#SVGID_00000119823135212293698520000012774889010992664993_);}
</style>
<g>
<polygon class="st2" points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "/>
<polygon class="st3" points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "/>
<polygon class="st3" points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
"/>
<polygon class="st3" points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "/>
<polygon class="st2" points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "/>
<polygon class="st2" points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -10,6 +10,7 @@
@endif @endif
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div> <div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
</div> </div>
<form wire:submit='submit' class="flex flex-col gap-2"> <form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
@if ($fileStorage->is_directory) @if ($fileStorage->is_directory)
@@ -26,25 +27,40 @@
</div> </div>
</x-modal-confirmation> </x-modal-confirmation>
@endif @endif
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong @if (!$fileStorage->is_based_on_git)
class="text-error">Please <x-modal-confirmation isErrorButton buttonTitle="Delete">
think <div class="px-2">This storage will be deleted. It is not reversible. <strong
again.</strong><br><br></div> class="text-error">Please
<h4>Actions</h4> think
@if ($fileStorage->is_directory) again.</strong><br><br></div>
<x-forms.checkbox id="permanently_delete" <h4>Actions</h4>
label="Permanently delete directory from the server?"></x-forms.checkbox> @if ($fileStorage->is_directory)
@else <x-forms.checkbox id="permanently_delete"
<x-forms.checkbox id="permanently_delete" label="Permanently delete directory from the server?"></x-forms.checkbox>
label="Permanently delete file from the server?"></x-forms.checkbox> @else
@endif <x-forms.checkbox id="permanently_delete"
</x-modal-confirmation> label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
@endif
</div> </div>
@if (!$fileStorage->is_directory) @if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea> @if (data_get($resource, 'settings.is_preserve_repository_enabled'))
<x-forms.button class="w-full" type="submit">Save</x-forms.button> <div class="w-96">
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
id="fileStorage.is_based_on_git"></x-forms.checkbox>
</div>
@endif
<x-forms.textarea
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
rows="20" id="fileStorage.content"
readonly="{{ $fileStorage->is_based_on_git }}"></x-forms.textarea>
@if (!$fileStorage->is_based_on_git)
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@endif
@endif @endif
</form> </form>
</div> </div>

View File

@@ -1,15 +1,6 @@
<div> <div>
<form wire:submit='submit' <form wire:submit='submit'
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300"> class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
{{-- @if (!$env->isFoundInCompose && !$isSharedVariable)
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg>This variable is not found in the compose file, so it won't be used.</div>
@endif --}}
@if ($isLocked) @if ($isLocked)
<div class="flex flex-1 w-full gap-2"> <div class="flex flex-1 w-full gap-2">
<x-forms.input disabled id="env.key" /> <x-forms.input disabled id="env.key" />

View File

@@ -120,8 +120,6 @@ Route::group([
Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/services/{uuid}/restart', [ServicesController::class, 'action_restart'])->middleware([IgnoreReadOnlyApiToken::class]);
Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]); Route::match(['get', 'post'], '/services/{uuid}/stop', [ServicesController::class, 'action_stop'])->middleware([IgnoreReadOnlyApiToken::class]);
// Route::delete('/envs/{env_uuid}', [EnvironmentVariablesController::class, 'delete_env_by_uuid'])->middleware([IgnoreReadOnlyApiToken::class]);
}); });
Route::any('/{any}', function () { Route::any('/{any}', function () {

View File

@@ -83,8 +83,8 @@ arch)
pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true
;; ;;
ubuntu | debian | raspbian) ubuntu | debian | raspbian)
apt update -y >/dev/null apt-get update -y >/dev/null
apt install -y curl wget git jq >/dev/null apt-get install -y curl wget git jq >/dev/null
;; ;;
centos | fedora | rhel | ol | rocky | almalinux | amzn) centos | fedora | rhel | ol | rocky | almalinux | amzn)
if [ "$OS_TYPE" = "amzn" ]; then if [ "$OS_TYPE" = "amzn" ]; then

View File

@@ -0,0 +1,150 @@
# documentation: https://docs.budibase.com/docs/docker-compose
# slogan: Low code platform for building business apps and workflows in minutes. Supports PostgreSQL, MySQL, MSSQL, MongoDB, Rest API, Docker, K8s, and more.
# tags: budibase,low-code,business-apps,workflow,automation,postgresql,mysql,mssql,mongodb,docker,kubernetes
# logo: svgs/budibase.svg
# port: 10000
services:
app-service:
image: budibase.docker.scarf.sh/budibase/apps
environment:
- SELF_HOSTED=1
- COUCH_DB_URL=http://$SERVICE_USER_BUDIBASE_COUCHDB:$SERVICE_PASSWORD_BUDIBASE_COUCHDB@couchdb-service:5984
- WORKER_URL=http://worker-service:4003
- MINIO_URL=http://minio-service:9000
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
- INTERNAL_API_KEY=$SERVICE_BASE64_128_BUDIBASE
- BUDIBASE_ENVIRONMENT=${BUDIBASE_ENVIRONMENT:-PRODUCTION}
- PORT=4002
- API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE
- JWT_SECRET=$SERVICE_BASE64_64_BUDIBASE
- LOG_LEVEL=info
- ENABLE_ANALYTICS=${ENABLE_ANALYTICS:-true}
- REDIS_URL=redis-service:6379
- REDIS_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_REDIS
- BB_ADMIN_USER_EMAIL=${BB_ADMIN_USER_EMAIL}
- BB_ADMIN_USER_PASSWORD=${BB_ADMIN_USER_PASSWORD}
depends_on:
- worker-service
- redis-service
healthcheck:
test: ["CMD", "curl", "-f", "http://app-service:4002"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s
worker-service:
image: budibase.docker.scarf.sh/budibase/worker
environment:
- SELF_HOSTED=1
- PORT=4003
- CLUSTER_PORT=10000
- API_ENCRYPTION_KEY=$SERVICE_BASE64_64_BUDIBASE
- JWT_SECRET=$SERVICE_BASE64_64_BUDIBASE
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
- MINIO_URL=http://minio-service:9000
- APPS_URL=http://app-service:4002
- COUCH_DB_USERNAME=$SERVICE_USER_BUDIBASE_COUCHDB
- COUCH_DB_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_COUCHDB
- COUCH_DB_URL=http://$SERVICE_USER_BUDIBASE_COUCHDB:$SERVICE_PASSWORD_BUDIBASE_COUCHDB@couchdb-service:5984
- INTERNAL_API_KEY=$SERVICE_BASE64_128_BUDIBASE
- REDIS_URL=redis-service:6379
- REDIS_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_REDIS
depends_on:
- redis-service
- minio-service
healthcheck:
test: ["CMD", "curl", "-f", "http://worker-service:4003"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s
minio-service:
image: minio/minio
volumes:
- minio_data:/data
environment:
- MINIO_ACCESS_KEY=$SERVICE_USER_BUDIBASE_MINIO
- MINIO_SECRET_KEY=$SERVICE_PASSWORD_BUDIBASE_MINIO
- MINIO_BROWSER=off
command: server /data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
proxy-service:
image: budibase/proxy
environment:
- SERVICE_FQDN_BUDIBASE_10000
- PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
- PROXY_RATE_LIMIT_API_PER_SECOND=20
- APPS_UPSTREAM_URL=http://app-service:4002
- WORKER_UPSTREAM_URL=http://worker-service:4003
- MINIO_UPSTREAM_URL=http://minio-service:9000
- COUCHDB_UPSTREAM_URL=http://couchdb-service:5984
- WATCHTOWER_UPSTREAM_URL=http://watchtower-service:8080
- RESOLVER=127.0.0.11
depends_on:
- minio-service
- worker-service
- app-service
- couchdb-service
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:10000/"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s
couchdb-service:
image: budibase/couchdb
environment:
- COUCHDB_PASSWORD=$SERVICE_PASSWORD_BUDIBASE_COUCHDB
- COUCHDB_USER=$SERVICE_USER_BUDIBASE_COUCHDB
- TARGETBUILD=docker-compose
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5984/"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s
volumes:
- couchdb3_data:/opt/couchdb/data
redis-service:
image: redis
command: redis-server --requirepass "$SERVICE_PASSWORD_BUDIBASE_REDIS"
volumes:
- redis_data:/data
healthcheck:
test:
["CMD", "redis-cli", "-a", "$SERVICE_PASSWORD_BUDIBASE_REDIS", "ping"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s
watchtower-service:
restart: always
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --debug --http-api-update bbapps bbworker bbproxy
environment:
- WATCHTOWER_HTTP_API=true
- WATCHTOWER_HTTP_API_TOKEN=$SERVICE_PASSWORD_BUDIBASE_WATCHTOWER
- WATCHTOWER_CLEANUP=true
labels:
- "com.centurylinklabs.watchtower.enable=false"
healthcheck:
test: ["CMD", "curl", "-f", "http://watchtower-service:8080"]
interval: 15s
timeout: 15s
retries: 5
start_period: 10s

View File

@@ -0,0 +1,152 @@
# documentation: https://chaskiq.io
# slogan: Chaskiq is an messaging platform for marketing, support & sales
# tags: chaskiq,messaging,chat,marketing,support,sales,open,source,rails,redis,postgresql,sidekiq
# logo: svgs/chaskiq.png
# port: 3000
services:
chaskiq:
image: chaskiq/chaskiq:latest
environment:
- SERVICE_FQDN_CHASKIQ_3000
- REDIS_URL=redis://redis:6379/
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgresql:5432/${POSTGRES_DB:-chaskiq}
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- SERVICE_URL=${SERVICE_URL_CHASKIQ}
- HOST=${SERVICE_FQDN_CHASKIQ_3000}
- ASSET_HOST=${SERVICE_FQDN_CHASKIQ_3000}
- WS=wss://${SERVICE_URL_CHASKIQ}/cable
- SNS_CONFIGURATION_SET=metrics
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
- AWS_S3_BUCKET=${AWS_S3_BUCKET:-}
- AWS_S3_REGION=${AWS_S3_REGION:-}
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example}
- ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}
- DEFAULT_SENDER_EMAIL=${DEFAULT_SENDER_EMAIL:-admin@example}
- LOCAL_STORAGE_PATH=/data/storage
- ACTIVE_STORAGE_SERVICE=${ACTIVE_STORAGE_SERVICE:-local}
- SMTP_DELIVERY_METHOD=${SMTP_DELIVERY_METHOD:-}
- SMTP_ADDRESS=${SMTP_ADDRESS:-}
- SMTP_USERNAME=${SMTP_USERNAME:-}
- SMTP_PASSWORD=${SMTP_PASSWORD:-}
- CHASKIQ_APPSTORE_TOKEN=${CHASKIQ_APPSTORE_TOKEN:-}
- APP_ENV=production
- RAILS_ENV=production
- RACK_ENV=production
- RAILS_SERVE_STATIC_FILES=true
- SECRET_KEY_BASE=$SERVICE_PASSWORD_64_SECRET
- RAILS_LOG_TO_STDOUT=true
- ENABLED_AUDITS=true
- TZ=Europe/Madrid
entrypoint: ["/entrypoint.sh"]
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- chaskiq-storage:/data/storage
- type: bind
source: ./entrypoint.sh
target: /entrypoint.sh
content: |
#!/bin/sh
set -e
rm -f /usr/src/app/tmp/pids/server.pid
exec "$@"
echo "Running database migrations..."
bundle exec rails db:setup || true
bundle exec rails db:migrate
echo "Finished running database migrations."
echo "Running packages update..."
bundle exec rails packages:update
echo "Finished packages update."
if [ ! -f /usr/src/app/admin_generated ]; then
echo "/usr/src/app/admin_generated not found, executing admin generation.."
bundle exec rake admin_generator
touch /usr/src/app/admin_generated
echo "Admin generation finished !"
fi
bundle exec rails s -b 0.0.0.0 -p 3000
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000"]
interval: 5s
timeout: 20s
retries: 15
sidekiq:
image: chaskiq/chaskiq:latest
environment:
- REDIS_URL=redis://redis:6379/
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgresql:5432/${POSTGRES_DB:-chaskiq}
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- HOST=${SERVICE_FQDN_CHASKIQ_3000}
- ASSET_HOST=${SERVICE_FQDN_CHASKIQ_3000}
- WS=wss://${SERVICE_URL_CHASKIQ}/cable
- SNS_CONFIGURATION_SET=metrics
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
- AWS_S3_BUCKET=${AWS_S3_BUCKET:-}
- AWS_S3_REGION=${AWS_S3_REGION:-}
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example}
- ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}
- DEFAULT_SENDER_EMAIL=${DEFAULT_SENDER_EMAIL:-admin@example}
- LOCAL_STORAGE_PATH=/data/storage
- ACTIVE_STORAGE_SERVICE=${ACTIVE_STORAGE_SERVICE:-local}
- SMTP_DELIVERY_METHOD=${SMTP_DELIVERY_METHOD:-}
- SMTP_ADDRESS=${SMTP_ADDRESS:-}
- SMTP_USERNAME=${SMTP_USERNAME:-}
- SMTP_PASSWORD=${SMTP_PASSWORD:-}
- CHASKIQ_APPSTORE_TOKEN=${CHASKIQ_APPSTORE_TOKEN:-}
- APP_ENV=production
- RAILS_ENV=production
- RACK_ENV=production
- RAILS_SERVE_STATIC_FILES=true
- SECRET_KEY_BASE=$SERVICE_PASSWORD_64_SECRET
- RAILS_LOG_TO_STDOUT=true
- ENABLED_AUDITS=true
- TZ=Europe/Madrid
volumes:
- chaskiq-storage:/data/storage
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
chaskiq:
condition: service_healthy
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
healthcheck:
test:
[
"CMD-SHELL",
"bundle exec rails runner 'puts Sidekiq.redis(&:info)' > /dev/null 2>&1",
]
interval: 5s
timeout: 10s
retries: 15
postgresql:
image: postgres:14-alpine
volumes:
- postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=${POSTGRES_DB:-chaskiq}
- POSTGRES_INITDB_ARGS= --data-checksums
- PSQL_HISTFILE=/root/log/.psql_history
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:6-alpine
restart: always
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10

View File

@@ -1,15 +1,19 @@
# ignore: true
# documentation: https://www.rabbitmq.com/documentation.html # documentation: https://www.rabbitmq.com/documentation.html
# slogan: With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers. # slogan: With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers.
# tags: message broker, message queue, message-oriented middleware, MOM, AMQP, MQTT, STOMP, messaging # tags: message broker, message queue, message-oriented middleware, MOM, AMQP, MQTT, STOMP, messaging
# logo: svgs/rabbitmq.svg
# port: 15672
services: services:
rabbitmq: rabbitmq:
image: rabbitmq:3 image: rabbitmq:3-management
environment: environment:
- SERVICE_FQDN_RABBITMQ_5672 - SERVICE_FQDN_RABBITMQ_15672
- RABBITMQ_DEFAULT_USER=$SERVICE_USER_RABBITMQ - RABBITMQ_DEFAULT_USER=$SERVICE_USER_RABBITMQ
- RABBITMQ_DEFAULT_PASS=$SERVICE_PASSWORD_RABBITMQ - RABBITMQ_DEFAULT_PASS=$SERVICE_PASSWORD_RABBITMQ
- PORT=${PORT:-5672}
ports:
- ${PORT}:5672
healthcheck: healthcheck:
test: rabbitmq-diagnostics -q ping test: rabbitmq-diagnostics -q ping
interval: 30s interval: 30s

View File

@@ -278,7 +278,7 @@ services:
config: config:
hide_credentials: true hide_credentials: true
supabase-studio: supabase-studio:
image: supabase/studio:20240514-6f5cabd image: supabase/studio:20240729-ce42139
healthcheck: healthcheck:
test: test:
[ [
@@ -315,7 +315,7 @@ services:
# Uncomment to use Big Query backend for analytics # Uncomment to use Big Query backend for analytics
# NEXT_ANALYTICS_BACKEND_PROVIDER=bigquery # NEXT_ANALYTICS_BACKEND_PROVIDER=bigquery
supabase-db: supabase-db:
image: supabase/postgres:15.1.1.41 image: supabase/postgres:15.1.1.78
healthcheck: healthcheck:
test: pg_isready -U postgres -h 127.0.0.1 test: pg_isready -U postgres -h 127.0.0.1
interval: 5s interval: 5s
@@ -895,7 +895,7 @@ services:
command: ["--config", "etc/vector/vector.yml"] command: ["--config", "etc/vector/vector.yml"]
supabase-rest: supabase-rest:
image: postgrest/postgrest:v12.0.1 image: postgrest/postgrest:v12.2.0
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database
@@ -941,8 +941,9 @@ services:
- GOTRUE_DB_DRIVER=postgres - GOTRUE_DB_DRIVER=postgres
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} - GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# The base URL your site is located at. Currently used in combination with other settings to construct URLs used in emails.
- GOTRUE_SITE_URL=${SERVICE_FQDN_SUPABASEKONG} - GOTRUE_SITE_URL=${SERVICE_FQDN_SUPABASEKONG}
# A comma separated list of URIs (e.g. "https://foo.example.com,https://*.foo.example.com,https://bar.example.com") which are permitted as valid redirect_to destinations.
- GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS} - GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS}
- GOTRUE_DISABLE_SIGNUP=${DISABLE_SIGNUP:-false} - GOTRUE_DISABLE_SIGNUP=${DISABLE_SIGNUP:-false}
@@ -992,9 +993,19 @@ services:
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true" # GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED="true"
# GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt" # GOTRUE_HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI="pg-functions://postgres/public/password_verification_attempt"
# Uncomment to enable common OAuth Variables
#- 'GOTRUE_EXTERNAL_GITHUB_CLIENT_ID=${GOTRUE_EXTERNAL_GITHUB_CLIENT_ID}'
#- 'GOTRUE_EXTERNAL_GITHUB_ENABLED=${GOTRUE_EXTERNAL_GITHUB_ENABLED}'
#- 'GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI=${GOTRUE_EXTERNAL_GITHUB_REDIRECT_URI}'
#- 'GOTRUE_EXTERNAL_GITHUB_SECRET=${GOTRUE_EXTERNAL_GITHUB_SECRET}'
#- 'GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=${GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID}'
#- 'GOTRUE_EXTERNAL_GOOGLE_ENABLED=${GOTRUE_EXTERNAL_GOOGLE_ENABLED}'
#- 'GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=${GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI}'
#- 'GOTRUE_EXTERNAL_GOOGLE_SECRET=${GOTRUE_EXTERNAL_GOOGLE_SECRET}'
realtime-dev: realtime-dev:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain # This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
image: supabase/realtime:v2.28.32 image: supabase/realtime:v2.30.23
container_name: realtime-dev.supabase-realtime container_name: realtime-dev.supabase-realtime
depends_on: depends_on:
supabase-db: supabase-db:
@@ -1034,6 +1045,9 @@ services:
- ERL_AFLAGS=-proto_dist inet_tcp - ERL_AFLAGS=-proto_dist inet_tcp
- ENABLE_TAILSCALE=false - ENABLE_TAILSCALE=false
- DNS_NODES='' - DNS_NODES=''
- RLIMIT_NOFILE=10000
- APP_NAME=realtime
- SEED_SELF_HOST=true
command: > command: >
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server" sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
supabase-minio: supabase-minio:
@@ -1155,7 +1169,7 @@ services:
- ./volumes/storage:/var/lib/storage - ./volumes/storage:/var/lib/storage
supabase-meta: supabase-meta:
image: supabase/postgres-meta:v0.80.0 image: supabase/postgres-meta:v0.83.2
depends_on: depends_on:
supabase-db: supabase-db:
# Disable this if you are using an external Postgres database # Disable this if you are using an external Postgres database

View File

@@ -0,0 +1,97 @@
# documentation: https://www.windmill.dev/docs/
# slogan: Windmill is a developer platform to build production-grade multi-steps automations and internal apps.\
# info: Login as admin@windmill.dev / changeme to setup the instance & accounts and give yourself super-admin privileges.
# tags: windmill,workflow,automation,developer,platform
# logo: svgs/windmill.svg
# port: 8000
version: "3.7"
services:
db:
image: postgres:16
shm_size: 1g
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: $SERVICE_PASSWORD_WINDMILL_POSTGRES
POSTGRES_DB: windmill
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
windmill_server:
image: ghcr.io/windmill-labs/windmill:main
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_WINDMILL_POSTGRES@db/windmill
- MODE=server
- BASE_URL=$SERVICE_FQDN_WINDMILL
depends_on:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
windmill_worker_1:
image: ghcr.io/windmill-labs/windmill:main
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_WINDMILL_POSTGRES@db/windmill
- MODE=worker
- WORKER_GROUP=default
depends_on:
db:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
windmill_worker_2:
image: ghcr.io/windmill-labs/windmill:main
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_WINDMILL_POSTGRES@db/windmill
- MODE=worker
- WORKER_GROUP=default
depends_on:
db:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
windmill_worker_3:
image: ghcr.io/windmill-labs/windmill:main
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_WINDMILL_POSTGRES@db/windmill
- MODE=worker
- WORKER_GROUP=default
depends_on:
db:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
windmill_worker_native:
image: ghcr.io/windmill-labs/windmill:main
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_WINDMILL_POSTGRES@db/windmill
- MODE=worker
- WORKER_GROUP=native
- NUM_WORKERS=8
- SLEEP_QUEUE=200
depends_on:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
lsp:
image: ghcr.io/windmill-labs/windmill-lsp:latest
volumes:
- lsp_cache:/root/.cache

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
{ {
"coolify": { "coolify": {
"v4": { "v4": {
"version": "4.0.0-beta.323" "version": "4.0.0-beta.324"
} }
} }
} }