diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 0531899fe..ee9f67ad7 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -27,14 +27,23 @@ class Kernel extends ConsoleKernel // $this->instance_auto_update($schedule); // $this->check_scheduled_backups($schedule); $this->check_resources($schedule); + $this->cleanup_servers($schedule); } else { $schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); - $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer(); + // $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer(); $this->instance_auto_update($schedule); $this->check_scheduled_backups($schedule); $this->check_resources($schedule); + $this->cleanup_servers($schedule); + } + } + private function cleanup_servers($schedule) + { + $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true); + foreach ($servers as $server) { + $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer(); } } private function check_resources($schedule) diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index b4e122ec8..b87f82bd7 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -22,9 +22,6 @@ class General extends Component public string $git_branch; public string|null $git_commit_sha; public string $build_pack; - public string|null $wildcard_domain = null; - public string|null $server_wildcard_domain = null; - public string|null $global_wildcard_domain = null; public bool $is_static; public bool $is_git_submodules_enabled; @@ -91,18 +88,20 @@ class General extends Component $this->application->settings->save(); $this->application->save(); $this->application->refresh(); - $this->checkWildCardDomain(); $this->emit('success', 'Application settings updated!'); } - protected function checkWildCardDomain() - { - $coolify_instance_settings = InstanceSettings::get(); - $this->server_wildcard_domain = data_get($this->application, 'destination.server.settings.wildcard_domain'); - $this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain'); - $this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null; - } + public function getWildcardDomain() { + $server = data_get($this->application, 'destination.server'); + if ($server) { + $fqdn = generateFqdn($server, $this->application->uuid); + ray($fqdn); + $this->application->fqdn = $fqdn; + $this->application->save(); + $this->emit('success', 'Application settings updated!'); + } + } public function mount() { $this->is_static = $this->application->settings->is_static; @@ -112,31 +111,6 @@ class General extends Component $this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled; $this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled; $this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; - $this->checkWildCardDomain(); - } - - public function generateGlobalRandomDomain() - { - // Set wildcard domain based on Global wildcard domain - $url = Url::fromString($this->global_wildcard_domain); - $host = $url->getHost(); - $path = $url->getPath() === '/' ? '' : $url->getPath(); - $scheme = $url->getScheme(); - $this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path; - $this->application->save(); - $this->emit('success', 'Application settings updated!'); - } - - public function generateServerRandomDomain() - { - // Set wildcard domain based on Server wildcard domain - $url = Url::fromString($this->server_wildcard_domain); - $host = $url->getHost(); - $path = $url->getPath() === '/' ? '' : $url->getPath(); - $scheme = $url->getScheme(); - $this->application->fqdn = $scheme . '://' . $this->application->uuid . '.' . $host . $path; - $this->application->save(); - $this->emit('success', 'Application settings updated!'); } public function submit() diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepository.php b/app/Http/Livewire/Project/New/GithubPrivateRepository.php index 15f137bc2..9cf0ee11c 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepository.php @@ -11,6 +11,7 @@ use App\Traits\SaveFromRedirect; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Livewire\Component; +use Spatie\Url\Url; class GithubPrivateRepository extends Component { @@ -95,6 +96,7 @@ class GithubPrivateRepository extends Component $this->loadBranchByPage(); } } + $this->selected_branch_name = data_get($this->branches,'0.name'); } protected function loadBranchByPage() @@ -144,8 +146,9 @@ class GithubPrivateRepository extends Component $application->settings->is_static = $this->is_static; $application->settings->save(); - $sslip = sslip($destination->server); - $application->fqdn = "http://{$application->uuid}.$sslip"; + $fqdn = generateFqdn($destination->server, $application->uuid); + $application->fqdn = $fqdn; + $application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid); $application->save(); diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index db6f4ad89..cf8f56642 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -46,7 +46,6 @@ class GithubPrivateRepositoryDeployKey extends Component private GithubApp|GitlabApp|null $git_source = null; private string $git_host; private string $git_repository; - private string $git_branch; public function mount() { @@ -96,7 +95,7 @@ class GithubPrivateRepositoryDeployKey extends Component $application_init = [ 'name' => generate_random_name(), 'git_repository' => $this->git_repository, - 'git_branch' => $this->git_branch, + 'git_branch' => $this->branch, 'git_full_url' => "git@$this->git_host:$this->git_repository.git", 'build_pack' => 'nixpacks', 'ports_exposes' => $this->port, @@ -112,8 +111,8 @@ class GithubPrivateRepositoryDeployKey extends Component $application->settings->is_static = $this->is_static; $application->settings->save(); - $sslip = sslip($destination->server); - $application->fqdn = "http://{$application->uuid}.$sslip"; + $fqdn = generateFqdn($destination->server, $application->uuid); + $application->fqdn = $fqdn; $application->name = generate_random_name($application->uuid); $application->save(); @@ -132,11 +131,6 @@ class GithubPrivateRepositoryDeployKey extends Component $this->repository_url_parsed = Url::fromString($this->repository_url); $this->git_host = $this->repository_url_parsed->getHost(); $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); - if ($this->branch) { - $this->git_branch = $this->branch; - } else { - $this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main'; - } if ($this->git_host == 'github.com') { $this->git_source = GithubApp::where('name', 'Public GitHub')->first(); diff --git a/app/Http/Livewire/Project/New/PublicGitRepository.php b/app/Http/Livewire/Project/New/PublicGitRepository.php index 8c8b25746..4aee4c3bf 100644 --- a/app/Http/Livewire/Project/New/PublicGitRepository.php +++ b/app/Http/Livewire/Project/New/PublicGitRepository.php @@ -76,13 +76,14 @@ class PublicGitRepository extends Component $this->get_branch(); $this->selected_branch = $this->git_branch; } catch (\Throwable $e) { - return handleError($e, $this); - } - if (!$this->branch_found && $this->git_branch == 'main') { - try { - $this->git_branch = 'master'; - $this->get_branch(); - } catch (\Throwable $e) { + if (!$this->branch_found && $this->git_branch == 'main') { + try { + $this->git_branch = 'master'; + $this->get_branch(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } else { return handleError($e, $this); } } @@ -156,8 +157,8 @@ class PublicGitRepository extends Component $application->settings->is_static = $this->is_static; $application->settings->save(); - $sslip = sslip($destination->server); - $application->fqdn = "http://{$application->uuid}.$sslip"; + $fqdn = generateFqdn($destination->server, $application->uuid); + $application->fqdn = $fqdn; $application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid); $application->save(); diff --git a/app/Http/Livewire/Project/New/SimpleDockerfile.php b/app/Http/Livewire/Project/New/SimpleDockerfile.php index 6a5d40b68..be65475a3 100644 --- a/app/Http/Livewire/Project/New/SimpleDockerfile.php +++ b/app/Http/Livewire/Project/New/SimpleDockerfile.php @@ -60,10 +60,10 @@ CMD ["nginx", "-g", "daemon off;"] 'source_type' => GithubApp::class ]); - $sslip = sslip($destination->server); + $fqdn = generateFqdn($destination->server, $application->uuid); $application->update([ 'name' => 'dockerfile-' . $application->uuid, - 'fqdn' => "http://{$application->uuid}.$sslip" + 'fqdn' => $fqdn ]); redirect()->route('project.application.configuration', [ diff --git a/app/Http/Livewire/Project/Service/ComposeModal.php b/app/Http/Livewire/Project/Service/ComposeModal.php new file mode 100644 index 000000000..0c9f5e98f --- /dev/null +++ b/app/Http/Livewire/Project/Service/ComposeModal.php @@ -0,0 +1,19 @@ +emit('warning', "Saving new docker compose..."); + $this->emit('saveCompose', $this->raw); + } +} diff --git a/app/Http/Livewire/Project/Service/FileStorage.php b/app/Http/Livewire/Project/Service/FileStorage.php index 4341c6f51..91ab8e659 100644 --- a/app/Http/Livewire/Project/Service/FileStorage.php +++ b/app/Http/Livewire/Project/Service/FileStorage.php @@ -13,6 +13,7 @@ class FileStorage extends Component public LocalFileVolume $fileStorage; public ServiceApplication|ServiceDatabase $service; public string $fs_path; + public ?string $workdir = null; protected $rules = [ 'fileStorage.is_directory' => 'required', @@ -23,22 +24,28 @@ class FileStorage extends Component public function mount() { $this->service = $this->fileStorage->service; - $this->fs_path = Str::of($this->fileStorage->fs_path)->beforeLast('/'); - $file = Str::of($this->fileStorage->fs_path)->afterLast('/'); - if (Str::of($this->fs_path)->startsWith('.')) { - $this->fs_path = Str::of($this->fs_path)->after('.'); - $this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" . $file; - } - + if (Str::of($this->fileStorage->fs_path)->startsWith('.')) { + $this->workdir = $this->service->service->workdir(); + $this->fs_path = Str::of($this->fileStorage->fs_path)->after('.'); + } else { + $this->workdir = null; + $this->fs_path = $this->fileStorage->fs_path; + } } public function submit() { + $original = $this->fileStorage->getOriginal(); try { $this->validate(); + if ($this->fileStorage->is_directory) { + $this->fileStorage->content = null; + } $this->fileStorage->save(); - $this->service->saveFileVolumes(); + $this->fileStorage->saveStorageOnServer($this->service); $this->emit('success', 'File updated successfully.'); } catch (\Throwable $e) { + $this->fileStorage->setRawAttributes($original); + $this->fileStorage->save(); return handleError($e, $this); } } diff --git a/app/Http/Livewire/Project/Service/Index.php b/app/Http/Livewire/Project/Service/Index.php index c3c6c57c8..18029fb73 100644 --- a/app/Http/Livewire/Project/Service/Index.php +++ b/app/Http/Livewire/Project/Service/Index.php @@ -4,7 +4,6 @@ namespace App\Http\Livewire\Project\Service; use App\Jobs\ContainerStatusJob; use App\Models\Service; -use DanHarrin\LivewireRateLimiting\WithRateLimiting; use Livewire\Component; class Index extends Component @@ -20,7 +19,25 @@ class Index extends Component 'service.name' => 'required', 'service.description' => 'nullable', ]; - public function checkStatus() { + protected $listeners = ["saveCompose"]; + public function render() + { + return view('livewire.project.service.index'); + } + public function mount() + { + $this->parameters = get_route_parameters(); + $this->query = request()->query(); + $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); + $this->refreshStack(); + } + public function saveCompose($raw) + { + $this->service->docker_compose_raw = $raw; + $this->submit(); + } + public function checkStatus() + { dispatch_sync(new ContainerStatusJob($this->service->server)); $this->refreshStack(); } @@ -35,17 +52,8 @@ class Index extends Component $database->refresh(); }); } - public function mount() - { - $this->parameters = get_route_parameters(); - $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); - $this->refreshStack(); - } - public function render() - { - return view('livewire.project.service.index'); - } + + public function submit() { try { diff --git a/app/Http/Livewire/Project/Service/Show.php b/app/Http/Livewire/Project/Service/Show.php index 656adff0a..ebeb24ebc 100644 --- a/app/Http/Livewire/Project/Service/Show.php +++ b/app/Http/Livewire/Project/Service/Show.php @@ -20,16 +20,26 @@ class Show extends Component public function mount() { - $this->services = collect([]); - $this->parameters = get_route_parameters(); - $this->query = request()->query(); - $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); - $service = $this->service->applications()->whereName($this->parameters['service_name'])->first(); - if ($service) { - $this->serviceApplication = $service; - } else { - $this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first(); + try { + $this->services = collect([]); + $this->parameters = get_route_parameters(); + $this->query = request()->query(); + $this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail(); + $service = $this->service->applications()->whereName($this->parameters['service_name'])->first(); + if ($service) { + $this->serviceApplication = $service; + $this->serviceApplication->getFilesFromServer(); + } else { + $this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first(); + $this->serviceDatabase->getFilesFromServer(); + } + if (is_null($service)) { + throw new \Exception("Service not found."); + } + } catch(\Throwable $e) { + return handleError($e, $this); } + } public function generateDockerCompose() { diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 80c76e067..08cb16959 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -16,65 +16,66 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $timeout = 500; + public $timeout = 1000; public ?string $dockerRootFilesystem = null; public ?int $usageBefore = null; public function middleware(): array { - return [ - (new WithoutOverlapping("dockerimagejobs"))->shared(), - ]; + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } - public function __construct() + + public function uniqueId(): string + { + return $this->server->uuid; + } + public function __construct(public Server $server) { } public function handle(): void { - $queue = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get(); - if ($queue->count() > 0) { + $queuedCount = 0; + $this->server->applications()->each(function ($application) use ($queuedCount) { + $count = data_get($application->deployments(), 'count', 0); + $queuedCount += $count; + }); + if ($queuedCount > 0) { ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange'); return; } try { - // ray()->showQueries()->color('orange'); - $servers = Server::all(); - foreach ($servers as $server) { - if ( - !$server->isFunctional() - ) { - continue; - } - if (isDev()) { - $this->dockerRootFilesystem = "/"; + if (!$this->server->isFunctional()) { + return; + } + if (isDev()) { + $this->dockerRootFilesystem = "/"; + } else { + $this->dockerRootFilesystem = instant_remote_process( + [ + "stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')" + ], + $this->server, + false + ); + } + if (!$this->dockerRootFilesystem) { + return; + } + $this->usageBefore = $this->getFilesystemUsage(); + if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { + ray('Cleaning up ' . $this->server->name)->color('orange'); + instant_remote_process(['docker image prune -af'], $this->server); + instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server); + instant_remote_process(['docker builder prune -af'], $this->server); + $usageAfter = $this->getFilesystemUsage(); + if ($usageAfter < $this->usageBefore) { + ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name)->color('orange'); + send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name); } else { - $this->dockerRootFilesystem = instant_remote_process( - [ - "stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')" - ], - $server, - false - ); - } - if (!$this->dockerRootFilesystem) { - continue; - } - $this->usageBefore = $this->getFilesystemUsage($server); - if ($this->usageBefore >= $server->settings->cleanup_after_percentage) { - ray('Cleaning up ' . $server->name)->color('orange'); - instant_remote_process(['docker image prune -af'], $server); - instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server); - instant_remote_process(['docker builder prune -af'], $server); - $usageAfter = $this->getFilesystemUsage($server); - if ($usageAfter < $this->usageBefore) { - ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name)->color('orange'); - send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $server->name); - } else { - ray('DockerCleanupJob failed to save disk space on ' . $server->name)->color('orange'); - } - } else { - ray('No need to clean up ' . $server->name)->color('orange'); + ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange'); } + } else { + ray('No need to clean up ' . $this->server->name)->color('orange'); } } catch (\Throwable $e) { send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage()); @@ -83,8 +84,8 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted } } - private function getFilesystemUsage(Server $server) + private function getFilesystemUsage() { - return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $server, false); + return instant_remote_process(["df '{$this->dockerRootFilesystem}'| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this->server, false); } } diff --git a/app/Models/Application.php b/app/Models/Application.php index b08561ef7..59b67fa16 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -226,7 +226,7 @@ class Application extends BaseModel } public function git_based(): bool { - if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->dockercompose || $this->build_pack === 'dockercompose') { + if ($this->dockerfile) { return false; } return true; diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php index c4cd3568d..758bf35c5 100644 --- a/app/Models/GithubApp.php +++ b/app/Models/GithubApp.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; class GithubApp extends BaseModel { + protected $guarded = []; protected $appends = ['type']; protected $casts = [ @@ -17,6 +18,7 @@ class GithubApp extends BaseModel 'webhook_secret', ]; + static public function public() { return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get(); @@ -34,6 +36,7 @@ class GithubApp extends BaseModel if ($applications_count > 0) { throw new \Exception('You cannot delete this GitHub App because it is in use by ' . $applications_count . ' application(s). Delete them first.'); } + $github_app->privateKey()->delete(); }); } diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index 9077a36d5..b45a868aa 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -3,6 +3,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Support\Str; class LocalFileVolume extends BaseModel { @@ -13,4 +14,40 @@ class LocalFileVolume extends BaseModel { return $this->morphTo('resource'); } + public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service) + { + $workdir = $service->service->workdir(); + $server = $service->service->server; + $commands = collect([ + "mkdir -p $workdir > /dev/null 2>&1 || true", + "cd $workdir" + ]); + $fileVolume = $this; + $path = Str::of(data_get($fileVolume, 'fs_path')); + $content = data_get($fileVolume, 'content'); + if ($path->startsWith('.')) { + $path = $path->after('.'); + $path = $workdir . $path; + } + $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); + $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); + ray($isFile); + if ($isFile == 'OK' && $fileVolume->is_directory) { + throw new \Exception("File $path 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."); + } else if ($isDir == 'OK' && !$fileVolume->is_directory) { + throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory."); + } + if (($isFile == 'NOK' && !$fileVolume->is_directory) || $isFile == 'OK') { + $rootDir = Str::of($path)->dirname(); + $commands->push("mkdir -p $rootDir > /dev/null 2>&1 || true"); + $commands->push("touch $path > /dev/null 2>&1 || true"); + if ($content) { + $content = base64_encode($content); + $commands->push("echo '$content' | base64 -d > $path"); + } + } else if ($isDir == 'NOK' && $fileVolume->is_directory) { + $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); + } + return instant_remote_process($commands, $server); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index cc60a8cad..883a2a784 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -196,6 +196,12 @@ class Service extends BaseModel } } + // Check if image changed + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { @@ -306,7 +312,7 @@ class Service extends BaseModel ] ); } - $savedService->saveFileVolumes(); + $savedService->getFilesFromServer(); } } @@ -344,8 +350,7 @@ class Service extends BaseModel } if ($key->startsWith('SERVICE_FQDN')) { if (is_null(data_get($savedService, 'fqdn'))) { - $sslip = sslip($this->server); - $fqdn = "http://$containerName.$sslip"; + $fqdn = generateFqdn($this->server, $containerName); if (substr_count($key->value(), '_') === 2 && $key->contains("=")) { $path = $value->value(); if ($generatedServiceFQDNS->count() > 0) { @@ -358,7 +363,7 @@ class Service extends BaseModel } else { $generatedServiceFQDNS->put($key->value(), $fqdn); } - $fqdn = "http://$containerName.$sslip$path"; + $fqdn = "$fqdn$path"; } if (!$isDatabase) { $savedService->fqdn = $fqdn; @@ -379,8 +384,7 @@ class Service extends BaseModel $forService = $value->afterLast('_'); $generatedValue = null; if ($command->value() === 'FQDN' || $command->value() === 'URL') { - $sslip = sslip($this->server); - $fqdn = "http://$containerName.$sslip"; + $fqdn = generateFqdn($this->server, $containerName); if ($foundEnv) { $fqdn = data_get($foundEnv, 'value'); } else { diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index f835ab753..58569b6bd 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -36,8 +36,8 @@ class ServiceApplication extends BaseModel ); } - public function saveFileVolumes() + public function getFilesFromServer() { - saveFileVolumesHelper($this); + getFilesystemVolumesFromServer($this); } } diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 09b216219..a94bca40a 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -26,8 +26,8 @@ class ServiceDatabase extends BaseModel { return $this->morphMany(LocalFileVolume::class, 'resource'); } - public function saveFileVolumes() + public function getFilesFromServer() { - saveFileVolumesHelper($this); + getFilesystemVolumesFromServer($this); } } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 353e6853b..7977d4d26 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -64,40 +64,53 @@ function serviceStatus(Service $service) } return 'exited'; } -function saveFileVolumesHelper(ServiceApplication|ServiceDatabase $oneService) +function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService) { + // TODO: make this async try { $workdir = $oneService->service->workdir(); $server = $oneService->service->server; - $applicationFileVolume = $oneService->fileStorages()->get(); + $fileVolumes = $oneService->fileStorages()->get(); $commands = collect([ "mkdir -p $workdir > /dev/null 2>&1 || true", - "cd $workdir" + "cd " ]); - foreach ($applicationFileVolume as $fileVolume) { - $path = Str::of($fileVolume->fs_path); - if ($fileVolume->is_directory) { - $commands->push("test -f $path && rm -f $path > /dev/null 2>&1 || true"); - $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); - continue; + instant_remote_process($commands, $server); + foreach ($fileVolumes as $fileVolume) { + $path = Str::of(data_get($fileVolume, 'fs_path')); + $content = data_get($fileVolume, 'content'); + if ($path->startsWith('.')) { + $path = $path->after('.'); + $fileLocation = $workdir . $path; + } else { + $fileLocation = $path; } - $content = $fileVolume->content; - $dir = $path->beforeLast('/'); - if ($dir->startsWith('.')) { - $dir = $dir->after('.'); - $dir = $workdir . $dir; + $isFile = instant_remote_process(["test -f $fileLocation && echo OK || echo NOK"], $server); + $isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server); + if ($isFile == 'OK' && !$fileVolume->is_directory) { + $filesystemContent = instant_remote_process(["cat $fileLocation"], $server); + if (base64_encode($filesystemContent) != base64_encode($content)) { + $fileVolume->content = $filesystemContent; + $fileVolume->save(); + } + } else { + if ($isDir == 'OK') { + $fileVolume->content = null; + $fileVolume->is_directory = true; + $fileVolume->save(); + } else { + $fileVolume->content = null; + $fileVolume->is_directory = false; + $fileVolume->save(); + } } - $content = base64_encode($content); - $commands->push("test -d $path && rm -rf $path > /dev/null 2>&1 || true"); - $commands->push("mkdir -p $dir > /dev/null 2>&1 || true"); - $commands->push("echo '$content' | base64 -d > $path"); } - return instant_remote_process($commands, $server); } catch (\Throwable $e) { return handleError($e); } } -function updateCompose($resource) { +function updateCompose($resource) +{ try { $name = data_get($resource, 'name'); $dockerComposeRaw = data_get($resource, 'service.docker_compose_raw'); @@ -111,7 +124,7 @@ function updateCompose($resource) { $variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper(); ray($variableName); $generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first(); - if ($generatedEnv){ + if ($generatedEnv) { $generatedEnv->value = $resource->fqdn; $generatedEnv->save(); } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 5c19b408d..01f9d55ae 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -395,16 +395,29 @@ function data_get_str($data, $key, $default = null): Stringable return Str::of($str); } +function generateFqdn(Server $server, string $random) +{ + $wildcard = data_get($server, 'settings.wildcard_domain'); + if (is_null($wildcard) || $wildcard === '') { + $wildcard = sslip($server); + } + $url = Url::fromString($wildcard); + $host = $url->getHost(); + $path = $url->getPath() === '/' ? '' : $url->getPath(); + $scheme = $url->getScheme(); + $finalFqdn = "$scheme://{$random}.$host$path" ; + return $finalFqdn; +} function sslip(Server $server) { if (isDev()) { - return "127.0.0.1.sslip.io"; + return "http://127.0.0.1.sslip.io"; } if ($server->ip === 'host.docker.internal') { $baseIp = base_ip(); - return "$baseIp.sslip.io"; + return "http://$baseIp.sslip.io"; } - return "{$server->ip}.sslip.io"; + return "http://{$server->ip}.sslip.io"; } function getServiceTemplates() diff --git a/config/sentry.php b/config/sentry.php index 86047a0ef..469ea55d5 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.50', + 'release' => '4.0.0-beta.51', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/toaster.php b/config/toaster.php index 13c9200a8..43565a9c6 100644 --- a/config/toaster.php +++ b/config/toaster.php @@ -30,7 +30,7 @@ return [ * * Minimum: 3000 (in milliseconds) */ - 'duration' => 3000, + 'duration' => 5000, /** * The horizontal position of each toast. diff --git a/config/version.php b/config/version.php index e7e4e3810..9724bda6a 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ -