Merge pull request #1806 from coollabsio/next

v4.0.0-beta.233
This commit is contained in:
Andras Bacsai
2024-03-04 11:18:56 +01:00
committed by GitHub
39 changed files with 305 additions and 52 deletions

View File

@@ -15,6 +15,9 @@ class StartProxy
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
if ($proxyType === 'NONE') {
return 'OK';
}
$commands = collect([]); $commands = collect([]);
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$configuration = CheckConfiguration::run($server); $configuration = CheckConfiguration::run($server);

View File

@@ -77,6 +77,9 @@ class Handler extends ExceptionHandler
); );
} }
); );
if (str($e->getMessage())->contains('No space left on device')) {
return;
}
ray('reporting to sentry'); ray('reporting to sentry');
Integration::captureUnhandledException($e); Integration::captureUnhandledException($e);
}); });

View File

@@ -374,6 +374,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->cleanup_git(); $this->cleanup_git();
$this->application->loadComposeFile(isInit: false); $this->application->loadComposeFile(isInit: false);
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
$yaml = $composeFile = $this->application->docker_compose_raw; $yaml = $composeFile = $this->application->docker_compose_raw;
} else { } else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
@@ -413,16 +414,33 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
]); ]);
} }
$this->write_deployment_configurations(); $this->write_deployment_configurations();
// Start compose file // Start compose file
if ($this->docker_compose_custom_start_command) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->execute_remote_command( if ($this->docker_compose_custom_start_command) {
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true], $this->execute_remote_command(
); ["cd {$this->basedir} && {$this->docker_compose_custom_start_command}", "hidden" => true],
);
} else {
$server_workdir = $this->application->workdir();
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command(
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
);
}
} else { } else {
$this->execute_remote_command( if ($this->docker_compose_custom_start_command) {
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true], $this->execute_remote_command(
); [executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
}
} }
$this->application_deployment_queue->addLogEntry("New container started."); $this->application_deployment_queue->addLogEntry("New container started.");
} }
private function deploy_dockerfile_buildpack() private function deploy_dockerfile_buildpack()

View File

@@ -51,6 +51,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
{ {
$this->backup = $backup; $this->backup = $backup;
$this->team = Team::find($backup->team_id); $this->team = Team::find($backup->team_id);
if (is_null($this->team)) {
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') { if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
$this->database = data_get($this->backup, 'database'); $this->database = data_get($this->backup, 'database');
$this->server = $this->database->service->server; $this->server = $this->database->service->server;
@@ -316,7 +319,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
private function backup_standalone_mongodb(string $databaseWithCollections): void private function backup_standalone_mongodb(string $databaseWithCollections): void
{ {
try { try {
$url = $this->database->getDbUrl(useInternal: true); $url = $this->database->get_db_url(useInternal: true);
if ($databaseWithCollections === 'all') { if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir; $commands[] = "mkdir -p " . $this->backup_dir;
$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";

View File

@@ -9,6 +9,8 @@ class Advanced extends Component
{ {
public Application $application; public Application $application;
public bool $is_force_https_enabled; public bool $is_force_https_enabled;
public bool $is_gzip_enabled;
public bool $is_stripprefix_enabled;
protected $rules = [ protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required', 'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required', 'application.settings.is_git_lfs_enabled' => 'boolean|required',
@@ -19,13 +21,17 @@ class Advanced extends Component
'application.settings.is_gpu_enabled' => 'boolean|required', 'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required', 'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.is_gzip_enabled' => 'boolean|required',
'application.settings.is_stripprefix_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required', 'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required', 'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required', 'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required', 'application.settings.gpu_options' => 'string|required',
]; ];
public function mount() { public function mount() {
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; $this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
$this->is_gzip_enabled = $this->application->isGzipEnabled();
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
} }
public function instantSave() public function instantSave()
{ {
@@ -40,6 +46,14 @@ class Advanced extends Component
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled; $this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->dispatch('resetDefaultLabels', false); $this->dispatch('resetDefaultLabels', false);
} }
if ($this->application->settings->is_gzip_enabled !== $this->is_gzip_enabled) {
$this->application->settings->is_gzip_enabled = $this->is_gzip_enabled;
$this->dispatch('resetDefaultLabels', false);
}
if ($this->application->settings->is_stripprefix_enabled !== $this->is_stripprefix_enabled) {
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
$this->dispatch('resetDefaultLabels', false);
}
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
} }

View File

@@ -263,7 +263,11 @@ class General extends Component
} }
if ($this->application->build_pack === 'dockercompose') { if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains); $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$this->parsedServices = $this->application->parseCompose(); if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose();
} else {
$this->parsedServices = $this->application->parseCompose();
}
} }
$this->application->custom_labels = base64_encode($this->customLabels); $this->application->custom_labels = base64_encode($this->customLabels);
$this->application->save(); $this->application->save();

View File

@@ -46,9 +46,9 @@ class General extends Component
public function mount() public function mount()
{ {
$this->db_url = $this->database->getDbUrl(true); $this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
} }
} }
public function instantSaveAdvanced() { public function instantSaveAdvanced() {
@@ -93,7 +93,7 @@ class General extends Component
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);

View File

@@ -44,9 +44,9 @@ class General extends Component
public function mount() public function mount()
{ {
$this->db_url = $this->database->getDbUrl(true); $this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
} }
} }
public function instantSaveAdvanced() public function instantSaveAdvanced()
@@ -95,7 +95,7 @@ class General extends Component
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);

View File

@@ -46,9 +46,9 @@ class General extends Component
public function mount() public function mount()
{ {
$this->db_url = $this->database->getDbUrl(true); $this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
} }
} }
public function instantSaveAdvanced() public function instantSaveAdvanced()
@@ -94,7 +94,7 @@ class General extends Component
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);

View File

@@ -53,9 +53,9 @@ class General extends Component
]; ];
public function mount() public function mount()
{ {
$this->db_url = $this->database->getDbUrl(true); $this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
} }
} }
public function instantSaveAdvanced() { public function instantSaveAdvanced() {
@@ -87,7 +87,7 @@ class General extends Component
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);

View File

@@ -39,9 +39,9 @@ class General extends Component
]; ];
public function mount() public function mount()
{ {
$this->db_url = $this->database->getDbUrl(true); $this->db_url = $this->database->get_db_url(true);
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
} }
} }
public function instantSaveAdvanced() { public function instantSaveAdvanced() {
@@ -86,7 +86,7 @@ class General extends Component
return; return;
} }
StartDatabaseProxy::run($this->database); StartDatabaseProxy::run($this->database);
$this->db_url_public = $this->database->getDbUrl(); $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.'); $this->dispatch('success', 'Database is now publicly accessible.');
} else { } else {
StopDatabaseProxy::run($this->database); StopDatabaseProxy::run($this->database);

View File

@@ -18,6 +18,7 @@ class Configuration extends Component
$userId = auth()->user()->id; $userId = auth()->user()->id;
return [ return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
"check_status"
]; ];
} }
public function render() public function render()

View File

@@ -6,7 +6,6 @@ use App\Actions\Shared\PullImage;
use App\Actions\Service\StartService; use App\Actions\Service\StartService;
use App\Actions\Service\StopService; use App\Actions\Service\StopService;
use App\Events\ServiceStatusChanged; use App\Events\ServiceStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Models\Service; use App\Models\Service;
use Livewire\Component; use Livewire\Component;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
@@ -27,6 +26,10 @@ class Navbar extends Component
{ {
$this->dispatch('refresh')->self(); $this->dispatch('refresh')->self();
} }
public function check_status() {
$this->dispatch('check_status');
$this->dispatch('success', 'Service status updated.');
}
public function render() public function render()
{ {
return view('livewire.project.service.navbar'); return view('livewire.project.service.navbar');

View File

@@ -18,6 +18,7 @@ class ServiceApplicationView extends Component
'application.required_fqdn' => 'required|boolean', 'application.required_fqdn' => 'required|boolean',
'application.is_log_drain_enabled' => 'nullable|boolean', 'application.is_log_drain_enabled' => 'nullable|boolean',
'application.is_gzip_enabled' => 'nullable|boolean', 'application.is_gzip_enabled' => 'nullable|boolean',
'application.is_stripprefix_enabled' => 'nullable|boolean',
]; ];
public function render() public function render()
{ {

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Server;
use App\Actions\Proxy\CheckConfiguration; use App\Actions\Proxy\CheckConfiguration;
use App\Actions\Proxy\SaveConfiguration; use App\Actions\Proxy\SaveConfiguration;
use App\Actions\Proxy\StartProxy;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -26,7 +27,7 @@ class Proxy extends Component
public function proxyStatusUpdated() public function proxyStatusUpdated()
{ {
$this->server->refresh(); $this->dispatch('refresh')->self();
} }
public function change_proxy() public function change_proxy()
@@ -41,6 +42,9 @@ class Proxy extends Component
$this->server->proxy->set('type', $proxy_type); $this->server->proxy->set('type', $proxy_type);
$this->server->save(); $this->server->save();
$this->selectedProxy = $this->server->proxy->type; $this->selectedProxy = $this->server->proxy->type;
if ($this->selectedProxy !== 'NONE') {
StartProxy::run($this->server, false);
}
$this->dispatch('proxyStatusUpdated'); $this->dispatch('proxyStatusUpdated');
} }

View File

@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use RuntimeException; use RuntimeException;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class Application extends BaseModel class Application extends BaseModel
@@ -79,6 +80,18 @@ class Application extends BaseModel
} }
return false; return false;
} }
public function isForceHttpsEnabled()
{
return data_get($this, 'settings.is_force_https_enabled', false);
}
public function isStripprefixEnabled()
{
return data_get($this, 'settings.is_stripprefix_enabled', true);
}
public function isGzipEnabled()
{
return data_get($this, 'settings.is_gzip_enabled', true);
}
public function link() public function link()
{ {
if (data_get($this, 'environment.project.uuid')) { if (data_get($this, 'environment.project.uuid')) {
@@ -476,6 +489,10 @@ class Application extends BaseModel
} }
return false; return false;
} }
public function workdir()
{
return application_configuration_dir() . "/{$this->uuid}";
}
public function isLogDrainEnabled() public function isLogDrainEnabled()
{ {
return data_get($this, 'settings.is_log_drain_enabled', false); return data_get($this, 'settings.is_log_drain_enabled', false);
@@ -710,6 +727,64 @@ class Application extends BaseModel
]; ];
} }
} }
function parseRawCompose()
{
try {
$yaml = Yaml::parse($this->docker_compose_raw);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
$services = data_get($yaml, 'services');
$commands = collect([]);
$services = collect($services)->map(function ($service) use ($commands) {
$serviceVolumes = collect(data_get($service, 'volumes', []));
if ($serviceVolumes->count() > 0) {
foreach ($serviceVolumes as $volume) {
$workdir = $this->workdir();
$type = null;
$source = null;
if (is_string($volume)) {
$source = Str::of($volume)->before(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = Str::of('bind');
}
} else if (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
}
if ($type->value() === 'bind') {
if ($source->value() === "/var/run/docker.sock") {
continue;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
continue;
}
if ($source->startsWith('.')) {
$source = $source->after('.');
$source = $workdir . $source;
}
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
}
}
}
$labels = collect(data_get($service, 'labels', []));
if (!$labels->contains('coolify.managed')) {
$labels->push('coolify.managed=true');
}
if (!$labels->contains('coolify.applicationId')) {
$labels->push('coolify.applicationId=' . $this->id);
}
if (!$labels->contains('coolify.type')) {
$labels->push('coolify.type=application');
}
data_set($service, 'labels', $labels->toArray());
return $service;
});
data_set($yaml, 'services', $services->toArray());
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
instant_remote_process($commands, $this->destination->server, false);
}
function parseCompose(int $pull_request_id = 0) function parseCompose(int $pull_request_id = 0)
{ {
if ($this->docker_compose_raw) { if ($this->docker_compose_raw) {

View File

@@ -3,7 +3,6 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Str;
class LocalFileVolume extends BaseModel class LocalFileVolume extends BaseModel
{ {

View File

@@ -23,6 +23,10 @@ class ServiceApplication extends BaseModel
{ {
return data_get($this, 'is_log_drain_enabled', false); return data_get($this, 'is_log_drain_enabled', false);
} }
public function isStripprefixEnabled()
{
return data_get($this, 'is_stripprefix_enabled', true);
}
public function isGzipEnabled() public function isGzipEnabled()
{ {
return data_get($this, 'is_gzip_enabled', true); return data_get($this, 'is_gzip_enabled', true);

View File

@@ -21,9 +21,13 @@ class ServiceDatabase extends BaseModel
{ {
return data_get($this, 'is_log_drain_enabled', false); return data_get($this, 'is_log_drain_enabled', false);
} }
public function isStripprefixEnabled()
{
return data_get($this, 'is_stripprefix_enabled', true);
}
public function isGzipEnabled() public function isGzipEnabled()
{ {
return true; return data_get($this, 'is_gzip_enabled', true);
} }
public function type() public function type()
{ {

View File

@@ -126,7 +126,7 @@ class StandaloneMariadb extends BaseModel
); );
} }
public function getDbUrl(bool $useInternal = false): string public function get_db_url(bool $useInternal = false): string
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}"; return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";

View File

@@ -142,7 +142,7 @@ class StandaloneMongodb extends BaseModel
{ {
return 'standalone-mongodb'; return 'standalone-mongodb';
} }
public function getDbUrl(bool $useInternal = false) public function get_db_url(bool $useInternal = false)
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true"; return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";

View File

@@ -127,7 +127,7 @@ class StandaloneMysql extends BaseModel
); );
} }
public function getDbUrl(bool $useInternal = false): string public function get_db_url(bool $useInternal = false): string
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";

View File

@@ -126,7 +126,7 @@ class StandalonePostgresql extends BaseModel
{ {
return 'standalone-postgresql'; return 'standalone-postgresql';
} }
public function getDbUrl(bool $useInternal = false): string public function get_db_url(bool $useInternal = false): string
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";

View File

@@ -122,7 +122,7 @@ class StandaloneRedis extends BaseModel
{ {
return 'standalone-redis'; return 'standalone-redis';
} }
public function getDbUrl(bool $useInternal = false): string public function get_db_url(bool $useInternal = false): string
{ {
if ($this->is_public && !$useInternal) { if ($this->is_public && !$useInternal) {
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";

View File

@@ -215,7 +215,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
} }
return $payload; return $payload;
} }
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?string $service_name = null) function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
{ {
$labels = collect([]); $labels = collect([]);
$labels->push('traefik.enable=true'); $labels->push('traefik.enable=true');
@@ -281,8 +281,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port"); $labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
} }
if ($path !== '/') { if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}"); if ($is_stripprefix_enabled) {
$middlewares = collect(["{$https_label}-stripprefix"]); $labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$https_label}-stripprefix"]);
}
if ($is_gzip_enabled) { if ($is_gzip_enabled) {
$middlewares->push('gzip'); $middlewares->push('gzip');
} }
@@ -334,8 +336,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}"); $labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
} }
if ($path !== '/') { if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}"); if ($is_stripprefix_enabled) {
$middlewares = collect(["{$http_label}-stripprefix"]); $labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$middlewares = collect(["{$http_label}-stripprefix"]);
}
if ($is_gzip_enabled) { if ($is_gzip_enabled) {
$middlewares->push('gzip'); $middlewares->push('gzip');
} }
@@ -392,7 +396,14 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$domains = Str::of(data_get($application, 'fqdn'))->explode(','); $domains = Str::of(data_get($application, 'fqdn'))->explode(',');
} }
// Add Traefik labels no matter which proxy is selected // Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($appUuid, $domains, $application->settings->is_force_https_enabled, $onlyPort)); $labels = $labels->merge(fqdnLabelsForTraefik(
uuid: $appUuid,
domains: $domains,
onlyPort: $onlyPort,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled()
));
} }
return $labels->all(); return $labels->all();
} }

View File

@@ -1047,7 +1047,15 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels = $serviceLabels->merge($defaultLabels); $serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) { if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) { if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true, serviceLabels: $serviceLabels, is_gzip_enabled: $savedService->isGzipEnabled(), service_name: $serviceName)); $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $resource->uuid,
domains: $fqdns,
is_force_https_enabled: true,
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName));
} }
} }
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) { if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
@@ -1249,7 +1257,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update networks // Collect/create/update networks
if ($serviceNetworks->count() > 0) { if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) { foreach ($serviceNetworks as $networkName => $networkDetails) {
ray($networkDetails);
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName; return $value == $networkName || $key == $networkName;
}); });
@@ -1405,7 +1412,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]); ]);
} }
} else { } else {
$generatedValue = generateEnvValue($command, $service); $generatedValue = generateEnvValue($command);
if (!$foundEnv) { if (!$foundEnv) {
EnvironmentVariable::create([ EnvironmentVariable::create([
'key' => $key, 'key' => $key,
@@ -1581,7 +1588,7 @@ function parseEnvVariable(Str|string $value)
'port' => $port, 'port' => $port,
]; ];
} }
function generateEnvValue(string $command, Service $service) function generateEnvValue(string $command, ?Service $service = null)
{ {
switch ($command) { switch ($command) {
case 'PASSWORD': case 'PASSWORD':

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.232', 'release' => '4.0.0-beta.233',
// 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.232'; return '4.0.0-beta.233';

View File

@@ -0,0 +1,44 @@
<?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('application_settings', function (Blueprint $table) {
$table->boolean('is_gzip_enabled')->default(true);
$table->boolean('is_stripprefix_enabled')->default(true);
});
Schema::table('service_applications', function (Blueprint $table) {
$table->boolean('is_stripprefix_enabled')->default(true);
});
Schema::table('service_databases', function (Blueprint $table) {
$table->boolean('is_gzip_enabled')->default(true);
$table->boolean('is_stripprefix_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_gzip_enabled');
$table->dropColumn('is_stripprefix_enabled');
});
Schema::table('service_applications', function (Blueprint $table) {
$table->dropColumn('is_stripprefix_enabled');
});
Schema::table('service_databases', function (Blueprint $table) {
$table->dropColumn('is_gzip_enabled');
$table->dropColumn('is_stripprefix_enabled');
});
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,8 +1,10 @@
<div class="group"> <div class="group">
@if (data_get($application, 'fqdn') || @if (
(data_get($application, 'fqdn') ||
collect(json_decode($this->application->docker_compose_domains))->count() > 0 || collect(json_decode($this->application->docker_compose_domains))->count() > 0 ||
data_get($application, 'previews', collect([]))->count() > 0 || data_get($application, 'previews', collect([]))->count() > 0 ||
data_get($application, 'ports_mappings_array')) data_get($application, 'ports_mappings_array')) &&
data_get($application, 'settings.is_raw_compose_deployment_enabled') !== true)
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application <label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
<x-chevron-down /> <x-chevron-down />
</label> </label>

View File

@@ -20,6 +20,11 @@
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold text-warning'>You will lose the rolling update feature!</span>" helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled" instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" /> label="Consistent Container Names" />
<x-forms.checkbox label="Enable gzip compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
instantSave id="is_gzip_enabled" />
<x-forms.checkbox helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api."
instantSave id="is_stripprefix_enabled" label="Strip Prefixes" />
<h3>Logs</h3> <h3>Logs</h3>
@if (!$application->settings->is_raw_compose_deployment_enabled) @if (!$application->settings->is_raw_compose_deployment_enabled)
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings." <x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."

View File

@@ -45,9 +45,11 @@
</div> </div>
@endif @endif
@if ($application->build_pack === 'dockercompose') @if ($application->build_pack === 'dockercompose')
<div class="w-96">
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled" <x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
label="Raw Compose Deployment" label="Raw Compose Deployment"
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" /> helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a href='https://coolify.io/docs/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
</div>
@if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled) @if (count($parsedServices) > 0 && !$application->settings->is_raw_compose_deployment_enabled)
@foreach (data_get($parsedServices, 'services') as $serviceName => $service) @foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image'))) @if (!isDatabaseImage(data_get($service, 'image')))

View File

@@ -34,7 +34,9 @@
<h3 class="pt-2">Advanced</h3> <h3 class="pt-2">Advanced</h3>
<div class="w-96"> <div class="w-96">
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression" <x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." /> helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." />
<x-forms.checkbox instantSave id="application.is_stripprefix_enabled" label="Strip Prefixes"
helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api." />
<x-forms.checkbox instantSave label="Exclude from service status" <x-forms.checkbox instantSave label="Exclude from service status"
helper="If you do not need to monitor this resource, enable. Useful if this service is optional." helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
id="application.exclude_from_status"></x-forms.checkbox> id="application.exclude_from_status"></x-forms.checkbox>

View File

@@ -1,7 +1,7 @@
<div> <div>
<div x-init="$wire.getLogs" id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }"> <div x-init="$wire.getLogs" id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@if ($resource->type() === 'application') @if ($resource?->type() === 'application')
<h3>{{ $container }}</h3> <h3>{{ $container }}</h3>
@else @else
<h3>{{ str($container)->beforeLast('-')->headline() }}</h3> <h3>{{ str($container)->beforeLast('-')->headline() }}</h3>

View File

@@ -25,7 +25,7 @@
</div> </div>
<div wire:loading.remove wire:target="loadProxyConfiguration"> <div wire:loading.remove wire:target="loadProxyConfiguration">
@if ($proxy_settings) @if ($proxy_settings)
<div class="flex flex-col gap-2 pt-2"> <div class="flex flex-col gap-2 pt-4">
<x-forms.textarea label="Configuration file: traefik.conf" name="proxy_settings" <x-forms.textarea label="Configuration file: traefik.conf" name="proxy_settings"
wire:model="proxy_settings" rows="30" /> wire:model="proxy_settings" rows="30" />
<x-forms.button wire:click.prevent="reset_proxy_configuration"> <x-forms.button wire:click.prevent="reset_proxy_configuration">

View File

@@ -0,0 +1,32 @@
# documentation: https://github.com/dgtlmoon/changedetection.io/
# slogan: Website change detection monitor and notifications.
# tags: web, alert, monitor
# logo: svgs/changedetection.png
services:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io
volumes:
- changedetection-data:/datastore
environment:
- SERVICE_FQDN_CHANGEDETECTION
- PUID=1000
- PGID=1000
- BASE_URL=$SERVICE_FQDN_CHANGEDETECTION
- PLAYWRIGHT_DRIVER_URL=ws://playwright-chrome:3000/?stealth=1&--disable-web-security=true
# Hides the `Referer` header so that monitored websites can't see the changedetection.io hostname.
- HIDE_REFERER=true
depends_on:
playwright-chrome:
condition: service_started
playwright-chrome:
image: dgtlmoon/sockpuppetbrowser:latest
# cap_add:
# - SYS_ADMIN
# SYS_ADMIN might be too much, but it can be needed on your platform https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-on-gitlabci
restart: unless-stopped
environment:
- SCREEN_WIDTH=1920
- SCREEN_HEIGHT=1024
- SCREEN_DEPTH=16
- MAX_CONCURRENT_CHROME_PROCESSES=10

View File

@@ -51,6 +51,18 @@
"logo": "svgs\/unknown.svg", "logo": "svgs\/unknown.svg",
"minversion": "0.0.0" "minversion": "0.0.0"
}, },
"changedetection": {
"documentation": "https:\/\/github.com\/dgtlmoon\/changedetection.io\/",
"slogan": "Website change detection monitor and notifications.",
"compose": "c2VydmljZXM6CiAgY2hhbmdlZGV0ZWN0aW9uOgogICAgaW1hZ2U6IGdoY3IuaW8vZGd0bG1vb24vY2hhbmdlZGV0ZWN0aW9uLmlvCiAgICB2b2x1bWVzOgogICAgICAtICdjaGFuZ2VkZXRlY3Rpb24tZGF0YTovZGF0YXN0b3JlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0NIQU5HRURFVEVDVElPTgogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtIEJBU0VfVVJMPSRTRVJWSUNFX0ZRRE5fQ0hBTkdFREVURUNUSU9OCiAgICAgIC0gJ1BMQVlXUklHSFRfRFJJVkVSX1VSTD13czovL3BsYXl3cmlnaHQtY2hyb21lOjMwMDAvP3N0ZWFsdGg9MSYtLWRpc2FibGUtd2ViLXNlY3VyaXR5PXRydWUnCiAgICAgIC0gSElERV9SRUZFUkVSPXRydWUKICAgIGRlcGVuZHNfb246CiAgICAgIHBsYXl3cmlnaHQtY2hyb21lOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9zdGFydGVkCiAgcGxheXdyaWdodC1jaHJvbWU6CiAgICBpbWFnZTogJ2RndGxtb29uL3NvY2twdXBwZXRicm93c2VyOmxhdGVzdCcKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTQ1JFRU5fV0lEVEg9MTkyMAogICAgICAtIFNDUkVFTl9IRUlHSFQ9MTAyNAogICAgICAtIFNDUkVFTl9ERVBUSD0xNgogICAgICAtIE1BWF9DT05DVVJSRU5UX0NIUk9NRV9QUk9DRVNTRVM9MTAK",
"tags": [
"web",
"alert",
"monitor"
],
"logo": "svgs\/changedetection.png",
"minversion": "0.0.0"
},
"code-server": { "code-server": {
"documentation": "https:\/\/coder.com\/docs\/code-server\/latest", "documentation": "https:\/\/coder.com\/docs\/code-server\/latest",
"slogan": "Code-Server is a web-based code editor that enables remote coding and collaboration from any device, anywhere.", "slogan": "Code-Server is a web-based code editor that enables remote coding and collaboration from any device, anywhere.",

View File

@@ -4,7 +4,7 @@
"version": "3.12.36" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.232" "version": "4.0.0-beta.233"
} }
} }
} }