From 9cb981f06883b463ac4fb2a015ac05b2f44f9d9d Mon Sep 17 00:00:00 2001 From: Jon Kristian Nilsen Date: Mon, 29 Apr 2024 13:29:42 +0200 Subject: [PATCH 01/44] Added twenty crm template + logo. --- public/svgs/twenty.svg | 1 + templates/compose/twenty.yaml | 46 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 public/svgs/twenty.svg create mode 100644 templates/compose/twenty.yaml diff --git a/public/svgs/twenty.svg b/public/svgs/twenty.svg new file mode 100644 index 0000000000..eef3a382a4 --- /dev/null +++ b/public/svgs/twenty.svg @@ -0,0 +1 @@ + diff --git a/templates/compose/twenty.yaml b/templates/compose/twenty.yaml new file mode 100644 index 0000000000..7524ae9c5f --- /dev/null +++ b/templates/compose/twenty.yaml @@ -0,0 +1,46 @@ +# documentation: https://docs.twenty.com +# slogan: Twenty is a CRM designed to fit your unique business needs. +# tags: crm, self-hosted, dashboard +# logo: svgs/twenty.svg + +services: + twenty: + image: 'twentycrm/twenty:latest' + environment: + SERVER_URL: $SERVICE_FQDN_TWENTY_3000 + FRONT_BASE_URL: $SERVICE_FQDN_TWENTY_3000 + ENABLE_DB_MIGRATIONS: true + SIGN_IN_PREFILLED: true + STORAGE_TYPE: local + STORAGE_S3_REGION: $STORAGE_S3_REGION + STORAGE_S3_NAME: $STORAGE_S3_NAME + STORAGE_S3_ENDPOINT: $STORAGE_S3_ENDPOINT + ACCESS_TOKEN_SECRET: $SERVICE_BASE64_32_ACCESS + LOGIN_TOKEN_SECRET: $SERVICE_BASE64_32_LOGIN + REFRESH_TOKEN_SECRET: $SERVICE_BASE64_32_REFRESH + FILE_TOKEN_SECRET: $SERVICE_BASE64_32_FILE + POSTGRES_ADMIN_PASSWORD: $SERVICE_PASSWORD_POSTGRES + PG_DATABASE_URL: postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default + ports: + - "3000:3000" + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"] + interval: 2s + timeout: 10s + retries: 15 + postgres: + image: "twentycrm/twenty-postgres:latest" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES + POSTGRES_DB: default + volumes: + - pg-data:/bitnami/postgresql + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres" "-d", "default"] + interval: 5s + timeout: 20s + retries: 10 From 91dd3468d5ed9a5e6b7c6c0e09b8b88fa6b42c1e Mon Sep 17 00:00:00 2001 From: Jon Kristian Nilsen Date: Fri, 3 May 2024 12:55:05 +0200 Subject: [PATCH 02/44] Storage type should be exposed. Fixed healthcheck test. --- templates/compose/twenty.yaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/templates/compose/twenty.yaml b/templates/compose/twenty.yaml index 7524ae9c5f..e2a3a193ee 100644 --- a/templates/compose/twenty.yaml +++ b/templates/compose/twenty.yaml @@ -10,11 +10,13 @@ services: SERVER_URL: $SERVICE_FQDN_TWENTY_3000 FRONT_BASE_URL: $SERVICE_FQDN_TWENTY_3000 ENABLE_DB_MIGRATIONS: true - SIGN_IN_PREFILLED: true - STORAGE_TYPE: local - STORAGE_S3_REGION: $STORAGE_S3_REGION - STORAGE_S3_NAME: $STORAGE_S3_NAME - STORAGE_S3_ENDPOINT: $STORAGE_S3_ENDPOINT + SIGN_IN_PREFILLED: false + + STORAGE_TYPE: s3 + STORAGE_S3_REGION: $_APP_STORAGE_S3_REGION + STORAGE_S3_NAME: $_APP_STORAGE_S3_NAME + STORAGE_S3_ENDPOINT: $_APP_STORAGE_S3_ENDPOINT + ACCESS_TOKEN_SECRET: $SERVICE_BASE64_32_ACCESS LOGIN_TOKEN_SECRET: $SERVICE_BASE64_32_LOGIN REFRESH_TOKEN_SECRET: $SERVICE_BASE64_32_REFRESH From 4549223d6d1ad46d4e920d85e4107ced29bf5a49 Mon Sep 17 00:00:00 2001 From: Jon Kristian Nilsen Date: Fri, 3 May 2024 12:56:49 +0200 Subject: [PATCH 03/44] Storage type should be exposed. Fixed healthcheck test. --- templates/compose/twenty.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/compose/twenty.yaml b/templates/compose/twenty.yaml index e2a3a193ee..39fa8275df 100644 --- a/templates/compose/twenty.yaml +++ b/templates/compose/twenty.yaml @@ -12,10 +12,10 @@ services: ENABLE_DB_MIGRATIONS: true SIGN_IN_PREFILLED: false - STORAGE_TYPE: s3 - STORAGE_S3_REGION: $_APP_STORAGE_S3_REGION - STORAGE_S3_NAME: $_APP_STORAGE_S3_NAME - STORAGE_S3_ENDPOINT: $_APP_STORAGE_S3_ENDPOINT + STORAGE_TYPE: ${STORAGE_TYPE:-local} + STORAGE_S3_REGION: $STORAGE_S3_REGION + STORAGE_S3_NAME: $STORAGE_S3_NAME + STORAGE_S3_ENDPOINT: $STORAGE_S3_ENDPOINT ACCESS_TOKEN_SECRET: $SERVICE_BASE64_32_ACCESS LOGIN_TOKEN_SECRET: $SERVICE_BASE64_32_LOGIN @@ -42,7 +42,7 @@ services: volumes: - pg-data:/bitnami/postgresql healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres" "-d", "default"] + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 5s timeout: 20s retries: 10 From 64d27156f5fa7b62fba11aab46c80d156f4b10b9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 May 2024 12:10:35 +0200 Subject: [PATCH 04/44] chore: Update version numbers to 4.0.0-beta.278 --- config/sentry.php | 2 +- config/version.php | 2 +- versions.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/sentry.php b/config/sentry.php index e65072dccd..4ab6d5b122 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ // 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.277', + 'release' => '4.0.0-beta.278', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index fea91e1f0c..980c395f06 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ Date: Fri, 10 May 2024 12:10:47 +0200 Subject: [PATCH 05/44] Fix error handling in GetContainersStatus.php and increase length of stripe_comment field in migrations --- app/Actions/Docker/GetContainersStatus.php | 2 +- app/Jobs/ContainerStatusJob.php | 331 ------------------ ...5_10_085215_make_stripe_comment_longer.php | 28 ++ 3 files changed, 29 insertions(+), 332 deletions(-) create mode 100644 database/migrations/2024_05_10_085215_make_stripe_comment_longer.php diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 667a8c92e9..8f4bfdf251 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -339,7 +339,7 @@ private function sentinel() instant_remote_process($connectProxyToDockerNetworks, $this->server, false); } } catch (\Exception $e) { - send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); + // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); ray($e->getMessage()); return handleError($e); } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 9ffd68f11c..11e7013eef 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -37,336 +37,5 @@ public function uniqueId(): int public function handle() { GetContainersStatus::run($this->server); - return; - // if (!$this->server->isFunctional()) { - // return 'Server is not ready.'; - // }; - // $applications = $this->server->applications(); - // $skip_these_applications = collect([]); - // foreach ($applications as $application) { - // if ($application->additional_servers->count() > 0) { - // $skip_these_applications->push($application); - // ComplexStatusCheck::run($application); - // $applications = $applications->filter(function ($value, $key) use ($application) { - // return $value->id !== $application->id; - // }); - // } - // } - // $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) { - // return !$skip_these_applications->pluck('id')->contains($value->id); - // }); - // try { - // if ($this->server->isSwarm()) { - // $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false); - // $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); - // } else { - // // Precheck for containers - // $containers = instant_remote_process(["docker container ls -q"], $this->server, false); - // if (!$containers) { - // return; - // } - // $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); - // $containerReplicates = null; - // } - // if (is_null($containers)) { - // return; - // } - - // $containers = format_docker_command_output_to_json($containers); - // if ($containerReplicates) { - // $containerReplicates = format_docker_command_output_to_json($containerReplicates); - // foreach ($containerReplicates as $containerReplica) { - // $name = data_get($containerReplica, 'Name'); - // $containers = $containers->map(function ($container) use ($name, $containerReplica) { - // if (data_get($container, 'Spec.Name') === $name) { - // $replicas = data_get($containerReplica, 'Replicas'); - // $running = str($replicas)->explode('/')[0]; - // $total = str($replicas)->explode('/')[1]; - // if ($running === $total) { - // data_set($container, 'State.Status', 'running'); - // data_set($container, 'State.Health.Status', 'healthy'); - // } else { - // data_set($container, 'State.Status', 'starting'); - // data_set($container, 'State.Health.Status', 'unhealthy'); - // } - // } - // return $container; - // }); - // } - // } - // $databases = $this->server->databases(); - // $services = $this->server->services()->get(); - // $previews = $this->server->previews(); - // $foundApplications = []; - // $foundApplicationPreviews = []; - // $foundDatabases = []; - // $foundServices = []; - - // foreach ($containers as $container) { - // if ($this->server->isSwarm()) { - // $labels = data_get($container, 'Spec.Labels'); - // $uuid = data_get($labels, 'coolify.name'); - // } else { - // $labels = data_get($container, 'Config.Labels'); - // } - // $containerStatus = data_get($container, 'State.Status'); - // $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); - // $containerStatus = "$containerStatus ($containerHealth)"; - // $labels = Arr::undot(format_docker_labels_to_json($labels)); - // $applicationId = data_get($labels, 'coolify.applicationId'); - // if ($applicationId) { - // $pullRequestId = data_get($labels, 'coolify.pullRequestId'); - // if ($pullRequestId) { - // if (str($applicationId)->contains('-')) { - // $applicationId = str($applicationId)->before('-'); - // } - // $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first(); - // if ($preview) { - // $foundApplicationPreviews[] = $preview->id; - // $statusFromDb = $preview->status; - // if ($statusFromDb !== $containerStatus) { - // $preview->update(['status' => $containerStatus]); - // } - // } else { - // //Notify user that this container should not be there. - // } - // } else { - // $application = $applications->where('id', $applicationId)->first(); - // if ($application) { - // $foundApplications[] = $application->id; - // $statusFromDb = $application->status; - // if ($statusFromDb !== $containerStatus) { - // $application->update(['status' => $containerStatus]); - // } - // } else { - // //Notify user that this container should not be there. - // } - // } - // } else { - // $uuid = data_get($labels, 'com.docker.compose.service'); - // $type = data_get($labels, 'coolify.type'); - - // if ($uuid) { - // if ($type === 'service') { - // $database_id = data_get($labels, 'coolify.service.subId'); - // if ($database_id) { - // $service_db = ServiceDatabase::where('id', $database_id)->first(); - // if ($service_db) { - // $uuid = $service_db->service->uuid; - // $isPublic = data_get($service_db, 'is_public'); - // if ($isPublic) { - // $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; - // } else { - // return data_get($value, 'Name') === "/$uuid-proxy"; - // } - // })->first(); - // if (!$foundTcpProxy) { - // StartDatabaseProxy::run($service_db); - // // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server)); - // } - // } - // } - // } - // } else { - // $database = $databases->where('uuid', $uuid)->first(); - // if ($database) { - // $isPublic = data_get($database, 'is_public'); - // $foundDatabases[] = $database->id; - // $statusFromDb = $database->status; - // if ($statusFromDb !== $containerStatus) { - // $database->update(['status' => $containerStatus]); - // } - // if ($isPublic) { - // $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid"; - // } else { - // return data_get($value, 'Name') === "/$uuid-proxy"; - // } - // })->first(); - // if (!$foundTcpProxy) { - // StartDatabaseProxy::run($database); - // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); - // } - // } - // } else { - // // Notify user that this container should not be there. - // } - // } - // } - // if (data_get($container, 'Name') === '/coolify-db') { - // $foundDatabases[] = 0; - // } - // } - // $serviceLabelId = data_get($labels, 'coolify.serviceId'); - // if ($serviceLabelId) { - // $subType = data_get($labels, 'coolify.service.subType'); - // $subId = data_get($labels, 'coolify.service.subId'); - // $service = $services->where('id', $serviceLabelId)->first(); - // if (!$service) { - // continue; - // } - // if ($subType === 'application') { - // $service = $service->applications()->where('id', $subId)->first(); - // } else { - // $service = $service->databases()->where('id', $subId)->first(); - // } - // if ($service) { - // $foundServices[] = "$service->id-$service->name"; - // $statusFromDb = $service->status; - // if ($statusFromDb !== $containerStatus) { - // // ray('Updating status: ' . $containerStatus); - // $service->update(['status' => $containerStatus]); - // } - // } - // } - // } - // $exitedServices = collect([]); - // foreach ($services as $service) { - // $apps = $service->applications()->get(); - // $dbs = $service->databases()->get(); - // foreach ($apps as $app) { - // if (in_array("$app->id-$app->name", $foundServices)) { - // continue; - // } else { - // $exitedServices->push($app); - // } - // } - // foreach ($dbs as $db) { - // if (in_array("$db->id-$db->name", $foundServices)) { - // continue; - // } else { - // $exitedServices->push($db); - // } - // } - // } - // $exitedServices = $exitedServices->unique('id'); - // foreach ($exitedServices as $exitedService) { - // if (str($exitedService->status)->startsWith('exited')) { - // continue; - // } - // $name = data_get($exitedService, 'name'); - // $fqdn = data_get($exitedService, 'fqdn'); - // $containerName = $name ? "$name, available at $fqdn" : $fqdn; - // $projectUuid = data_get($service, 'environment.project.uuid'); - // $serviceUuid = data_get($service, 'uuid'); - // $environmentName = data_get($service, 'environment.name'); - - // if ($projectUuid && $serviceUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid; - // } else { - // $url = null; - // } - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // $exitedService->update(['status' => 'exited']); - // } - - // $notRunningApplications = $applications->pluck('id')->diff($foundApplications); - // foreach ($notRunningApplications as $applicationId) { - // $application = $applications->where('id', $applicationId)->first(); - // if (str($application->status)->startsWith('exited')) { - // continue; - // } - // $application->update(['status' => 'exited']); - - // $name = data_get($application, 'name'); - // $fqdn = data_get($application, 'fqdn'); - - // $containerName = $name ? "$name ($fqdn)" : $fqdn; - - // $projectUuid = data_get($application, 'environment.project.uuid'); - // $applicationUuid = data_get($application, 'uuid'); - // $environment = data_get($application, 'environment.name'); - - // if ($projectUuid && $applicationUuid && $environment) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid; - // } else { - // $url = null; - // } - - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - // $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); - // foreach ($notRunningApplicationPreviews as $previewId) { - // $preview = $previews->where('id', $previewId)->first(); - // if (str($preview->status)->startsWith('exited')) { - // continue; - // } - // $preview->update(['status' => 'exited']); - - // $name = data_get($preview, 'name'); - // $fqdn = data_get($preview, 'fqdn'); - - // $containerName = $name ? "$name ($fqdn)" : $fqdn; - - // $projectUuid = data_get($preview, 'application.environment.project.uuid'); - // $environmentName = data_get($preview, 'application.environment.name'); - // $applicationUuid = data_get($preview, 'application.uuid'); - - // if ($projectUuid && $applicationUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid; - // } else { - // $url = null; - // } - - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - // $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); - // foreach ($notRunningDatabases as $database) { - // $database = $databases->where('id', $database)->first(); - // if (str($database->status)->startsWith('exited')) { - // continue; - // } - // $database->update(['status' => 'exited']); - - // $name = data_get($database, 'name'); - // $fqdn = data_get($database, 'fqdn'); - - // $containerName = $name; - - // $projectUuid = data_get($database, 'environment.project.uuid'); - // $environmentName = data_get($database, 'environment.name'); - // $databaseUuid = data_get($database, 'uuid'); - - // if ($projectUuid && $databaseUuid && $environmentName) { - // $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid; - // } else { - // $url = null; - // } - // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url)); - // } - - // // Check if proxy is running - // $this->server->proxyType(); - // $foundProxyContainer = $containers->filter(function ($value, $key) { - // if ($this->server->isSwarm()) { - // return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik'; - // } else { - // return data_get($value, 'Name') === '/coolify-proxy'; - // } - // })->first(); - // if (!$foundProxyContainer) { - // try { - // $shouldStart = CheckProxy::run($this->server); - // if ($shouldStart) { - // StartProxy::run($this->server, false); - // $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server)); - // } - // } catch (\Throwable $e) { - // ray($e); - // } - // } else { - // $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status'); - // $this->server->save(); - // $connectProxyToDockerNetworks = connectProxyToNetworks($this->server); - // instant_remote_process($connectProxyToDockerNetworks, $this->server, false); - // } - // } catch (\Throwable $e) { - // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage()); - // ray($e->getMessage()); - // return handleError($e); - // } } } diff --git a/database/migrations/2024_05_10_085215_make_stripe_comment_longer.php b/database/migrations/2024_05_10_085215_make_stripe_comment_longer.php new file mode 100644 index 0000000000..a51896f42d --- /dev/null +++ b/database/migrations/2024_05_10_085215_make_stripe_comment_longer.php @@ -0,0 +1,28 @@ +longText('stripe_comment')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subscriptions', function (Blueprint $table) { + $table->string('stripe_comment')->change(); + }); + } +}; From 5e531d6f96b97035e303a6d4bc7e3b9b3787a146 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 10 May 2024 12:50:39 +0200 Subject: [PATCH 06/44] fix: only show realtime error on non-cloud instances --- .../views/livewire/layout-popups.blade.php | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/resources/views/livewire/layout-popups.blade.php b/resources/views/livewire/layout-popups.blade.php index 3f4436b8bf..b2cc76f2fd 100644 --- a/resources/views/livewire/layout-popups.blade.php +++ b/resources/views/livewire/layout-popups.blade.php @@ -15,7 +15,7 @@ checkPusherInterval = setInterval(() => { if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') { checkNumber++; - if (checkNumber > 4) { + if (checkNumber > 5) { this.popups.realtime = true; console.error( 'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)' @@ -23,31 +23,36 @@ clearInterval(checkPusherInterval); } } - }, 1000); + }, 2000); } } }"> @auth - - - WARNING: Realtime Error?! - - - Coolify could not connect to its real-time service.
This will cause unusual problems on the UI - if - not fixed!

- Please ensure that you have opened the - required ports, - check the - related documentation or get - help on Discord.
-
- - Acknowledge & Disable This Popup - -
+ @if (!isCloud()) + + + WARNING: Realtime Error?! + + + Coolify could not connect to its real-time service.
This will cause unusual problems on the + UI + if + not fixed!

+ Please ensure that you have opened the + required ports, + check the + related documentation or get + help on Discord. +
+
+ + Acknowledge & Disable This Popup + +
+ @endif
@endauth From 1988c617a0c76b58045e8a035f297c2b04347622 Mon Sep 17 00:00:00 2001 From: Lucas Heinschke Date: Fri, 10 May 2024 16:28:14 +0200 Subject: [PATCH 07/44] Correct repository links in source view for git SSH URLs --- app/Models/Application.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index 1ce33cefb3..d84c4af392 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -146,9 +146,13 @@ public function gitBranchLocation(): Attribute if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/tree/{$this->git_branch}"; + } return $this->git_repository; } - ); } @@ -159,6 +163,11 @@ public function gitWebhook(): Attribute if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/settings/hooks"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/settings/hooks"; + } return $this->git_repository; } ); @@ -171,6 +180,11 @@ public function gitCommits(): Attribute if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}"; } + // Convert the SSH URL to HTTPS URL + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commits/{$this->git_branch}"; + } return $this->git_repository; } ); From b5552a216de58109b286378907b4978d9db9ac1d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 11:55:20 +0200 Subject: [PATCH 08/44] fix: only allow push and mr gitlab events --- app/Http/Controllers/Webhook/Gitlab.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 65ce9910b2..763f95bbc0 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -38,6 +38,15 @@ public function manual(Request $request) $headers = $request->headers->all(); $x_gitlab_token = data_get($headers, 'x-gitlab-token.0'); $x_gitlab_event = data_get($payload, 'object_kind'); + $allowed_events = ['push', 'merge_request']; + if (!in_array($x_gitlab_event, $allowed_events)) { + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Event not allowed. Only push and merge_request events are allowed.', + ]); + return response($return_payloads); + } + if ($x_gitlab_event === 'push') { $branch = data_get($payload, 'ref'); $full_name = data_get($payload, 'project.path_with_namespace'); From 24f923e88e930dbb701156123e9be2cf105cc4fa Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 14 May 2024 12:19:33 +0200 Subject: [PATCH 09/44] fix: docker compose dependencies for pr previews --- bootstrap/helpers/shared.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index b93b0ec3dc..e34a58e065 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1665,6 +1665,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if ($pull_request_id !== 0) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { + $service->depends_on = $service->depends_on->map(function ($dependency) use ($pull_request_id) { + return $dependency . "-pr-$pull_request_id"; + }); $services[$serviceName . "-pr-$pull_request_id"] = $service; data_forget($services, $serviceName); }); From 69dd9d0cac662b805ea068b6d7364f97865a17e2 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 12:45:17 +0200 Subject: [PATCH 10/44] chore: Update hover behavior and cursor style in scheduled task executions view --- .../livewire/project/shared/scheduled-task/executions.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/livewire/project/shared/scheduled-task/executions.blade.php b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php index 56f7e88546..4daa4933ae 100644 --- a/resources/views/livewire/project/shared/scheduled-task/executions.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/executions.blade.php @@ -12,7 +12,7 @@ @endif data_get($execution, 'id') == $selectedKey, 'border-green-500' => data_get($execution, 'status') === 'success', From 9dc3ec0bf8361bc45efe8ae8e291c31f8aa0441f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 12:45:21 +0200 Subject: [PATCH 11/44] chore: Refactor scheduled task view to improve code readability and maintainability --- .../shared/scheduled-task/all.blade.php | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/resources/views/livewire/project/shared/scheduled-task/all.blade.php b/resources/views/livewire/project/shared/scheduled-task/all.blade.php index 2d8b61df2b..3ed22e5e7b 100644 --- a/resources/views/livewire/project/shared/scheduled-task/all.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/all.blade.php @@ -7,17 +7,29 @@ From 5ad08791ea3f6ff924939ab6a2a15b852a29fc10 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 12:52:01 +0200 Subject: [PATCH 12/44] chore: Skip scheduled tasks if application or service is not running --- app/Console/Kernel.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 89d679a476..d67a267206 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -138,7 +138,18 @@ private function check_scheduled_tasks($schedule) $scheduled_task->delete(); continue; } - + if ($application) { + if (str($application->status)->contains('running') === false) { + ray('application not running, skipping'); + continue; + } + } + if ($service) { + if (str($service->status)->contains('running') === false) { + ray('service not running, skipping'); + continue; + } + } if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } From b70a78b7aa04f29bcc5ee85c452f532ff1fb21e4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 13:04:17 +0200 Subject: [PATCH 13/44] chore: Remove debug logging statements in Kernel.php --- app/Console/Kernel.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index d67a267206..0c9f59f906 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -140,13 +140,11 @@ private function check_scheduled_tasks($schedule) } if ($application) { if (str($application->status)->contains('running') === false) { - ray('application not running, skipping'); continue; } } if ($service) { if (str($service->status)->contains('running') === false) { - ray('service not running, skipping'); continue; } } From 5321a75272962652092c7c05abcb3ae89ec32e8b Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 14 May 2024 13:33:29 +0200 Subject: [PATCH 14/44] fix: properly populating dependencies --- bootstrap/helpers/shared.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index e34a58e065..eebaa4bce0 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1259,6 +1259,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $servicePorts = collect(data_get($service, 'ports', [])); $serviceNetworks = collect(data_get($service, 'networks', [])); $serviceVariables = collect(data_get($service, 'environment', [])); + $serviceDependencies = collect(data_get($service, 'depends_on', [])); $serviceLabels = collect(data_get($service, 'labels', [])); $serviceBuildVariables = collect(data_get($service, 'build.args', [])); $serviceVariables = $serviceVariables->merge($serviceBuildVariables); @@ -1370,6 +1371,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'volumes', $serviceVolumes->toArray()); } + if ($pull_request_id !== 0 && count($serviceDependencies) > 0) { + $serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) { + return $dependency . "-pr-$pull_request_id"; + }); + data_set($service, 'depends_on', $serviceDependencies->toArray()); + } + // Decide if the service is a database $isDatabase = isDatabaseImage(data_get_str($service, 'image')); data_set($service, 'is_database', $isDatabase); @@ -1665,9 +1673,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if ($pull_request_id !== 0) { $services->each(function ($service, $serviceName) use ($pull_request_id, $services) { - $service->depends_on = $service->depends_on->map(function ($dependency) use ($pull_request_id) { - return $dependency . "-pr-$pull_request_id"; - }); $services[$serviceName . "-pr-$pull_request_id"] = $service; data_forget($services, $serviceName); }); From cc870ca3024790ff20e08bc360335a0b16ca0eb7 Mon Sep 17 00:00:00 2001 From: "Mauro E. Bender" Date: Tue, 14 May 2024 14:29:27 +0200 Subject: [PATCH 15/44] Fix scheduled tasks being executed using host environment variables --- app/Jobs/ScheduledTaskJob.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 4a38a005b6..a389dceec4 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -89,7 +89,7 @@ public function handle(): void foreach ($this->containers as $containerName) { if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) { - $cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"'; + $cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->task_output = instant_remote_process([$exec], $this->server, true); $this->task_log->update([ From f06065337c81a035726bf313f5f91a88d39c258e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 15:18:59 +0200 Subject: [PATCH 16/44] chore: Handle invalid cron strings in Kernel.php --- app/Console/Kernel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 0c9f59f906..51e4cfc171 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -144,7 +144,7 @@ private function check_scheduled_tasks($schedule) } } if ($service) { - if (str($service->status)->contains('running') === false) { + if (str($service->status())->contains('running') === false) { continue; } } From 317dc10af47ff47a795698e11c91c9c56aeca0ad Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 15:19:28 +0200 Subject: [PATCH 17/44] fix: improve scheduled task adding/removing --- app/Jobs/ScheduledTaskJob.php | 6 +++- .../Project/Service/Configuration.php | 1 - .../Project/Shared/ScheduledTask/Add.php | 11 +++++++ .../Project/Shared/ScheduledTask/All.php | 19 +++++++++--- .../Project/Shared/ScheduledTask/Show.php | 14 +++++++-- app/Models/Service.php | 11 +++++++ .../project/service/configuration.blade.php | 18 +++++++++--- .../livewire/project/service/index.blade.php | 29 +------------------ .../shared/scheduled-task/add.blade.php | 29 +++++++++++++++---- .../shared/scheduled-task/all.blade.php | 21 +++++++++++--- .../scheduled-task/executions.blade.php | 2 +- .../shared/scheduled-task/show.blade.php | 18 +++++++++--- 12 files changed, 123 insertions(+), 56 deletions(-) diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 4a38a005b6..5c7b0d8af2 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -77,8 +77,12 @@ public function handle(): void $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'); } }); + $this->resource->databases()->get()->each(function ($database) { + if (str(data_get($database, 'status'))->contains('running')) { + $this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'); + } + }); } - if (count($this->containers) == 0) { throw new \Exception('ScheduledTaskJob failed: No containers running.'); } diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 0b26af22f3..86c9a8a319 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -3,7 +3,6 @@ namespace App\Livewire\Project\Service; use App\Actions\Docker\GetContainersStatus; -use App\Jobs\ContainerStatusJob; use App\Models\Service; use Livewire\Component; diff --git a/app/Livewire/Project/Shared/ScheduledTask/Add.php b/app/Livewire/Project/Shared/ScheduledTask/Add.php index 3a7a3fa23a..c415ff3e45 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Add.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Add.php @@ -2,11 +2,14 @@ namespace App\Livewire\Project\Shared\ScheduledTask; +use Illuminate\Support\Collection; use Livewire\Component; class Add extends Component { public $parameters; + public string $type; + public Collection $containerNames; public string $name; public string $command; public string $frequency; @@ -29,6 +32,9 @@ class Add extends Component public function mount() { $this->parameters = get_route_parameters(); + if ($this->containerNames->count() > 0) { + $this->container = $this->containerNames->first(); + } } public function submit() @@ -40,6 +46,11 @@ public function submit() $this->dispatch('error', 'Invalid Cron / Human expression.'); return; } + if (empty($this->container) || $this->container == 'null') { + if ($this->type == 'service') { + $this->container = $this->subServiceName; + } + } $this->dispatch('saveScheduledTask', [ 'name' => $this->name, 'command' => $this->command, diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 975d695fa8..73b2733b45 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -3,14 +3,13 @@ namespace App\Livewire\Project\Shared\ScheduledTask; use App\Models\ScheduledTask; +use Illuminate\Support\Collection; use Livewire\Component; -use Visus\Cuid2\Cuid2; -use Illuminate\Support\Str; class All extends Component { public $resource; - public string|null $modalId = null; + public Collection $containerNames; public ?string $variables = null; public array $parameters; protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit']; @@ -18,7 +17,19 @@ class All extends Component public function mount() { $this->parameters = get_route_parameters(); - $this->modalId = new Cuid2(7); + if ($this->resource->type() == 'service') { + $this->containerNames = $this->resource->applications()->pluck('name'); + $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); + ray($this->containerNames); + } elseif ($this->resource->type() == 'application') { + if ($this->resource->build_pack === 'dockercompose') { + $parsed = $this->resource->parseCompose(); + $containers = collect($parsed['services'])->keys(); + $this->containerNames = $containers; + } else { + $this->containerNames = collect([]); + } + } } public function refreshTasks() { diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 87b7525092..7490c70552 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -17,6 +17,7 @@ class Show extends Component public string $type; protected $rules = [ + 'task.enabled' => 'required|boolean', 'task.name' => 'required|string', 'task.command' => 'required|string', 'task.frequency' => 'required|string', @@ -45,9 +46,18 @@ public function mount() $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first(); } + public function instantSave() + { + $this->validateOnly('task.enabled'); + $this->task->save(['enabled' => $this->task->enabled]); + $this->dispatch('success', 'Scheduled task updated.'); + $this->dispatch('refreshTasks'); + } public function submit() { $this->validate(); + $this->task->name = str($this->task->name)->trim()->value(); + $this->task->container = str($this->task->container)->trim()->value(); $this->task->save(); $this->dispatch('success', 'Scheduled task updated.'); $this->dispatch('refreshTasks'); @@ -60,11 +70,9 @@ public function delete() if ($this->type == 'application') { return redirect()->route('project.application.configuration', $this->parameters); - } - else { + } else { return redirect()->route('project.service.configuration', $this->parameters); } - } catch (\Exception $e) { return handleError($e); } diff --git a/app/Models/Service.php b/app/Models/Service.php index cd8e578d61..770dfae2e9 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -677,6 +677,17 @@ public function server() { return $this->belongsTo(Server::class); } + public function byUuid(string $uuid) { + $app = $this->applications()->whereUuid($uuid)->first(); + if ($app) { + return $app; + } + $db = $this->databases()->whereUuid($uuid)->first(); + if ($db) { + return $db; + } + return null; + } public function byName(string $name) { $app = $this->applications()->whereName($name)->first(); diff --git a/resources/views/livewire/project/service/configuration.blade.php b/resources/views/livewire/project/service/configuration.blade.php index 16cabf6894..185fbdb77e 100644 --- a/resources/views/livewire/project/service/configuration.blade.php +++ b/resources/views/livewire/project/service/configuration.blade.php @@ -16,6 +16,10 @@ @click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages + Scheduled Tasks + Settings - - This application will be unavailable during the restart.
Please think again. -
+ @if (str($application->status)->contains('running')) + + This application will be unavailable during the restart.
Please think + again. +
+ @endif @@ -169,6 +176,9 @@ class="w-4 h-4 dark:text-warning text-coollabs" @endforeach +
+ +
diff --git a/resources/views/livewire/project/service/index.blade.php b/resources/views/livewire/project/service/index.blade.php index 318df88d03..0ce919049d 100644 --- a/resources/views/livewire/project/service/index.blade.php +++ b/resources/views/livewire/project/service/index.blade.php @@ -10,14 +10,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ General - Storages - - Scheduled Tasks - @if (str($serviceDatabase?->databaseType())->contains('mysql') || str($serviceDatabase?->databaseType())->contains('postgres') || str($serviceDatabase?->databaseType())->contains('mariadb')) @@ -30,28 +22,12 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
-
-
-

Storages

-
-
Persistent storage to preserve data between deployments.
- Please modify storage layout in your Docker Compose file. - -
+ @endisset @isset($serviceDatabase)
-
-
-

Storages

-
-
Persistent storage to preserve data between deployments.
- Please modify storage layout in your Docker Compose file. - -

Scheduled Backups

@@ -62,9 +38,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
@endisset -
- -
diff --git a/resources/views/livewire/project/shared/scheduled-task/add.blade.php b/resources/views/livewire/project/shared/scheduled-task/add.blade.php index 3226981b8a..9e9af07bf4 100644 --- a/resources/views/livewire/project/shared/scheduled-task/add.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/add.blade.php @@ -1,10 +1,27 @@
- - - - - + + + + @if ($type === 'application') + @if ($containerNames->count() > 1) + + @foreach ($containerNames as $containerName) + + @endforeach + + @else + + @endif + @elseif ($type === 'service') + + @foreach ($containerNames as $containerName) + + @endforeach + + @endif + + Save diff --git a/resources/views/livewire/project/shared/scheduled-task/all.blade.php b/resources/views/livewire/project/shared/scheduled-task/all.blade.php index 3ed22e5e7b..a3c0587ca4 100644 --- a/resources/views/livewire/project/shared/scheduled-task/all.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/all.blade.php @@ -1,8 +1,12 @@

Scheduled Tasks

- - + + @if ($resource->type() == 'application') + + @elseif ($resource->type() == 'service') + + @endif
@endif data_get($execution, 'id') == $selectedKey, 'border-green-500' => data_get($execution, 'status') === 'success', diff --git a/resources/views/livewire/project/shared/scheduled-task/show.blade.php b/resources/views/livewire/project/shared/scheduled-task/show.blade.php index 144d22f165..9a08fe04bb 100644 --- a/resources/views/livewire/project/shared/scheduled-task/show.blade.php +++ b/resources/views/livewire/project/shared/scheduled-task/show.blade.php @@ -1,13 +1,13 @@
-

Scheduled Task

@if ($type === 'application') +

Scheduled Task

@elseif ($type === 'service') @endif
-
+

Scheduled Task

@@ -17,13 +17,23 @@ You will delete scheduled task {{ $task->name }}.
+
+ +
- + @if ($type === 'application') + + @elseif ($type === 'service') + + @endif
From e95e2cf152be6bb909a9a6eef8dc8c133840c580 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 May 2024 15:40:45 +0200 Subject: [PATCH 18/44] feat: Add AdminRemoveUser command to remove users from the database --- app/Console/Commands/AdminRemoveUser.php | 54 +++++++++++++++++++ app/Console/Commands/Cloud.php | 33 ------------ app/Console/Commands/RootResetPassword.php | 1 - .../Project/Shared/ScheduledTask/All.php | 1 - 4 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 app/Console/Commands/AdminRemoveUser.php delete mode 100644 app/Console/Commands/Cloud.php diff --git a/app/Console/Commands/AdminRemoveUser.php b/app/Console/Commands/AdminRemoveUser.php new file mode 100644 index 0000000000..76af0a97f7 --- /dev/null +++ b/app/Console/Commands/AdminRemoveUser.php @@ -0,0 +1,54 @@ +argument('email'); + $confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?'); + if (!$confirm) { + $this->info('User removal cancelled.'); + return; + } + $this->info("Removing user with email: $email"); + $user = User::whereEmail($email)->firstOrFail(); + $teams = $user->teams; + foreach ($teams as $team) { + if ($team->members->count() > 1) { + $this->error('User is a member of a team with more than one member. Please remove user from team first.'); + return; + } + $team->delete(); + } + $user->delete(); + } catch (\Exception $e) { + $this->error('Failed to remove user.'); + $this->error($e->getMessage()); + return; + } + } +} diff --git a/app/Console/Commands/Cloud.php b/app/Console/Commands/Cloud.php deleted file mode 100644 index 1386b296c9..0000000000 --- a/app/Console/Commands/Cloud.php +++ /dev/null @@ -1,33 +0,0 @@ -whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){ - $this->info($server->name); - }); - } -} diff --git a/app/Console/Commands/RootResetPassword.php b/app/Console/Commands/RootResetPassword.php index df385002e4..af2b1a45c0 100644 --- a/app/Console/Commands/RootResetPassword.php +++ b/app/Console/Commands/RootResetPassword.php @@ -29,7 +29,6 @@ class RootResetPassword extends Command */ public function handle() { - // $this->info('You are about to reset the root password.'); $password = password('Give me a new password for root user: '); $passwordAgain = password('Again'); diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 73b2733b45..5182639d7d 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -20,7 +20,6 @@ public function mount() if ($this->resource->type() == 'service') { $this->containerNames = $this->resource->applications()->pluck('name'); $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); - ray($this->containerNames); } elseif ($this->resource->type() == 'application') { if ($this->resource->build_pack === 'dockercompose') { $parsed = $this->resource->parseCompose(); From 32ff3461541279fdc5db32dcdf460e99049e9d91 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 15 May 2024 09:46:28 +0200 Subject: [PATCH 19/44] chore: Refactor Service.php to handle missing admin user in extraFields() method --- app/Models/Service.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/Models/Service.php b/app/Models/Service.php index 770dfae2e9..d8950137b0 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -450,14 +450,16 @@ public function extraFields() $data = collect([]); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); - $data = $data->merge([ - 'User' => [ - 'key' => 'SERVICE_USER_ADMIN', - 'value' => data_get($admin_user, 'value', 'admin'), - 'readonly' => true, - 'rules' => 'required', - ], - ]); + if ($admin_user) { + $data = $data->merge([ + 'User' => [ + 'key' => 'SERVICE_USER_ADMIN', + 'value' => data_get($admin_user, 'value', 'admin'), + 'readonly' => true, + 'rules' => 'required', + ], + ]); + } if ($admin_password) { $data = $data->merge([ 'Password' => [ From 0ffba45517194d09198da21a39826e43f0f051c5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 15 May 2024 09:46:31 +0200 Subject: [PATCH 20/44] chore: Update twenty CRM template with environment variables and dependencies --- templates/compose/twenty.yaml | 61 ++++++++++++++++++++++---------- templates/service-templates.json | 13 +++++++ 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/templates/compose/twenty.yaml b/templates/compose/twenty.yaml index 39fa8275df..a36675cbaa 100644 --- a/templates/compose/twenty.yaml +++ b/templates/compose/twenty.yaml @@ -2,29 +2,43 @@ # slogan: Twenty is a CRM designed to fit your unique business needs. # tags: crm, self-hosted, dashboard # logo: svgs/twenty.svg +# port: 3000 services: twenty: image: 'twentycrm/twenty:latest' environment: - SERVER_URL: $SERVICE_FQDN_TWENTY_3000 - FRONT_BASE_URL: $SERVICE_FQDN_TWENTY_3000 - ENABLE_DB_MIGRATIONS: true - SIGN_IN_PREFILLED: false + - SERVICE_FQDN_TRIGGER_3000 + - SERVER_URL=$SERVICE_FQDN_TWENTY + - FRONT_BASE_URL=$SERVICE_FQDN_TWENTY + - ENABLE_DB_MIGRATIONS=true + - SIGN_IN_PREFILLED=false - STORAGE_TYPE: ${STORAGE_TYPE:-local} - STORAGE_S3_REGION: $STORAGE_S3_REGION - STORAGE_S3_NAME: $STORAGE_S3_NAME - STORAGE_S3_ENDPOINT: $STORAGE_S3_ENDPOINT + - STORAGE_TYPE=${STORAGE_TYPE:-local} + - STORAGE_S3_REGION=$STORAGE_S3_REGION + - STORAGE_S3_NAME=$STORAGE_S3_NAME + - STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT - ACCESS_TOKEN_SECRET: $SERVICE_BASE64_32_ACCESS - LOGIN_TOKEN_SECRET: $SERVICE_BASE64_32_LOGIN - REFRESH_TOKEN_SECRET: $SERVICE_BASE64_32_REFRESH - FILE_TOKEN_SECRET: $SERVICE_BASE64_32_FILE - POSTGRES_ADMIN_PASSWORD: $SERVICE_PASSWORD_POSTGRES - PG_DATABASE_URL: postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default - ports: - - "3000:3000" + - ACCESS_TOKEN_SECRET=$SERVICE_BASE64_32_ACCESS + - LOGIN_TOKEN_SECRET=$SERVICE_BASE64_32_LOGIN + - REFRESH_TOKEN_SECRET=$SERVICE_BASE64_32_REFRESH + - FILE_TOKEN_SECRET=$SERVICE_BASE64_32_FILE + - POSTGRES_ADMIN_PASSWORD=$SERVICE_PASSWORD_POSTGRES + - PG_DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default + + - EMAIL_FROM_ADDRESS=$EMAIL_FROM_ADDRESS + - EMAIL_FROM_NAME=$EMAIL_FROM_NAME + - EMAIL_SYSTEM_ADDRESS=$EMAIL_SYSTEM_ADDRESS + - EMAIL_DRIVER=${EMAIL_DRIVER:-logger} + - EMAIL_SMTP_HOST=$EMAIL_SMTP_HOST + - EMAIL_SMTP_PORT=$EMAIL_SMTP_PORT + - EMAIL_SMTP_USER=$EMAIL_SMTP_USER + - EMAIL_SMTP_PASSWORD=$EMAIL_SMTP_PASSWORD + + - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false} + - CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis} + - REDIS_HOST=redis + - REDIS_PORT=6379 depends_on: postgres: condition: service_healthy @@ -36,9 +50,9 @@ services: postgres: image: "twentycrm/twenty-postgres:latest" environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES - POSTGRES_DB: default + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES + - POSTGRES_DB=default volumes: - pg-data:/bitnami/postgresql healthcheck: @@ -46,3 +60,12 @@ services: interval: 5s timeout: 20s retries: 10 + redis: + image: "redis:latest" + volumes: + - "redis-data:/data" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 20s + retries: 10 diff --git a/templates/service-templates.json b/templates/service-templates.json index 5b3fd2b931..ee1b02a35f 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -948,6 +948,19 @@ "minversion": "0.0.0", "port": "3000" }, + "twenty": { + "documentation": "https:\/\/docs.twenty.com", + "slogan": "Twenty is a CRM designed to fit your unique business needs.", + "compose": "c2VydmljZXM6CiAgdHdlbnR5OgogICAgaW1hZ2U6ICd0d2VudHljcm0vdHdlbnR5OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fVFdFTlRZCiAgICAgIC0gRlJPTlRfQkFTRV9VUkw9JFNFUlZJQ0VfRlFETl9UV0VOVFkKICAgICAgLSBFTkFCTEVfREJfTUlHUkFUSU9OUz10cnVlCiAgICAgIC0gU0lHTl9JTl9QUkVGSUxMRUQ9ZmFsc2UKICAgICAgLSAnU1RPUkFHRV9UWVBFPSR7U1RPUkFHRV9UWVBFOi1sb2NhbH0nCiAgICAgIC0gU1RPUkFHRV9TM19SRUdJT049JFNUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gU1RPUkFHRV9TM19OQU1FPSRTVE9SQUdFX1MzX05BTUUKICAgICAgLSBTVE9SQUdFX1MzX0VORFBPSU5UPSRTVE9SQUdFX1MzX0VORFBPSU5UCiAgICAgIC0gQUNDRVNTX1RPS0VOX1NFQ1JFVD0kU0VSVklDRV9CQVNFNjRfMzJfQUNDRVNTCiAgICAgIC0gTE9HSU5fVE9LRU5fU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF8zMl9MT0dJTgogICAgICAtIFJFRlJFU0hfVE9LRU5fU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF8zMl9SRUZSRVNICiAgICAgIC0gRklMRV9UT0tFTl9TRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzMyX0ZJTEUKICAgICAgLSBQT1NUR1JFU19BRE1JTl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtICdQR19EQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly9wb3N0Z3JlczokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlczo1NDMyL2RlZmF1bHQnCiAgICAgIC0gRU1BSUxfRlJPTV9BRERSRVNTPSRFTUFJTF9GUk9NX0FERFJFU1MKICAgICAgLSBFTUFJTF9GUk9NX05BTUU9JEVNQUlMX0ZST01fTkFNRQogICAgICAtIEVNQUlMX1NZU1RFTV9BRERSRVNTPSRFTUFJTF9TWVNURU1fQUREUkVTUwogICAgICAtICdFTUFJTF9EUklWRVI9JHtFTUFJTF9EUklWRVI6LWxvZ2dlcn0nCiAgICAgIC0gRU1BSUxfU01UUF9IT1NUPSRFTUFJTF9TTVRQX0hPU1QKICAgICAgLSBFTUFJTF9TTVRQX1BPUlQ9JEVNQUlMX1NNVFBfUE9SVAogICAgICAtIEVNQUlMX1NNVFBfVVNFUj0kRU1BSUxfU01UUF9VU0VSCiAgICAgIC0gRU1BSUxfU01UUF9QQVNTV09SRD0kRU1BSUxfU01UUF9QQVNTV09SRAogICAgICAtICdURUxFTUVUUllfRU5BQkxFRD0ke1RFTEVNRVRSWV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ0NBQ0hFX1NUT1JBR0VfVFlQRT0ke0NBQ0hFX1NUT1JBR0VfVFlQRTotcmVkaXN9JwogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBSRURJU19QT1JUPTYzNzkKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9oZWFsdGh6JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3R3ZW50eWNybS90d2VudHktcG9zdGdyZXM6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfREI9ZGVmYXVsdAogICAgdm9sdW1lczoKICAgICAgLSAncGctZGF0YTovYml0bmFtaS9wb3N0Z3Jlc3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==", + "tags": [ + "crm", + "self-hosted", + "dashboard" + ], + "logo": "svgs\/twenty.svg", + "minversion": "0.0.0", + "port": "3000" + }, "umami": { "documentation": "https:\/\/umami.is", "slogan": "Umami is web analytics platform which provides insights into visitor behavior without compromising user privacy.", From 1e09b2bbd801441cf5080460589e721a7d69fea9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 15 May 2024 10:44:45 +0200 Subject: [PATCH 21/44] fix: use commit hash on webhooks --- app/Http/Controllers/Webhook/Bitbucket.php | 3 ++- app/Http/Controllers/Webhook/Github.php | 4 ++++ app/Http/Controllers/Webhook/Gitlab.php | 2 ++ app/Jobs/ApplicationDeploymentJob.php | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php index 485720c232..7b569c2786 100644 --- a/app/Http/Controllers/Webhook/Bitbucket.php +++ b/app/Http/Controllers/Webhook/Bitbucket.php @@ -47,7 +47,7 @@ public function manual(Request $request) if ($x_bitbucket_event === 'repo:push') { $branch = data_get($payload, 'push.changes.0.new.name'); $full_name = data_get($payload, 'repository.full_name'); - + $commit = data_get($payload, 'push.changes.0.new.target.hash'); if (!$branch) { return response([ 'status' => 'failed', @@ -104,6 +104,7 @@ public function manual(Request $request) queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: $commit, force_rebuild: false, is_webhook: true ); diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 214843aabc..baa23deec5 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -129,6 +129,7 @@ public function manual(Request $request) application: $application, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'after', 'HEAD'), is_webhook: true, ); $return_payloads->push([ @@ -177,6 +178,7 @@ public function manual(Request $request) pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'head.sha', 'HEAD'), is_webhook: true, git_type: 'github' ); @@ -338,6 +340,7 @@ public function normal(Request $request) queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); @@ -387,6 +390,7 @@ public function normal(Request $request) pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, force_rebuild: false, + commit: data_get($payload, 'head.sha', 'HEAD'), is_webhook: true, git_type: 'github' ); diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index 763f95bbc0..dfa9394eb4 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -133,6 +133,7 @@ public function manual(Request $request) queue_application_deployment( application: $application, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'after', 'HEAD'), force_rebuild: false, is_webhook: true, ); @@ -182,6 +183,7 @@ public function manual(Request $request) application: $application, pull_request_id: $pull_request_id, deployment_uuid: $deployment_uuid, + commit: data_get($payload, 'object_attributes.last_commit.id', 'HEAD'), force_rebuild: false, is_webhook: true, git_type: 'gitlab' diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 69bfa29d03..8c7695f92b 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1113,6 +1113,8 @@ private function check_git_if_build_needed() } if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) { $this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t"); + $this->application_deployment_queue->commit = $this->commit; + $this->application_deployment_queue->save(); } } private function clone_repository() From 346faf1d07f8796b67f2bf903a29cc76f8f30f88 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 15 May 2024 10:45:01 +0200 Subject: [PATCH 22/44] chore: Refactor applications.php to remove unused imports and improve code readability --- bootstrap/helpers/applications.php | 1 - 1 file changed, 1 deletion(-) diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index c0aaf4abfd..a1995c6455 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -6,7 +6,6 @@ use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\StandaloneDocker; -use Illuminate\Support\Collection; use Spatie\Url\Url; function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) From cd3e2963b3050a6c307fcd0adbc3fbac52579b84 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 15 May 2024 10:45:08 +0200 Subject: [PATCH 23/44] Refactor gitCommitLink method to handle different git repository formats --- app/Models/Application.php | 11 ++++++ .../application/deployment/index.blade.php | 36 ++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/Models/Application.php b/app/Models/Application.php index d84c4af392..fd3faa166a 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -189,6 +189,17 @@ public function gitCommits(): Attribute } ); } + public function gitCommitLink($link): string + { + if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) { + return "{$this->source->html_url}/{$this->git_repository}/commit/{$link}"; + } + if (strpos($this->git_repository, 'git@') === 0) { + $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + return "https://{$git_repository}/commit/{$link}"; + } + return $this->git_repository; + } public function dockerfileLocation(): Attribute { return Attribute::make( diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 9c5e440801..65ba941525 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -27,18 +27,15 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> @endif @forelse ($deployments as $deployment) -
- data_get($deployment, 'status') === 'queued', - 'border-warning hover:bg-warning hover:text-black' => +
data_get($deployment, 'status') === 'in_progress' || data_get($deployment, 'status') === 'cancelled-by-user', - 'border-error dark:hover:bg-error hover:bg-neutral-200' => - data_get($deployment, 'status') === 'failed', - 'border-success dark:hover:bg-success hover:bg-neutral-200' => - data_get($deployment, 'status') === 'finished', - ]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"> + 'border-error' => data_get($deployment, 'status') === 'failed', + 'border-success' => data_get($deployment, 'status') === 'finished', + ]) + x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')">
{{ $deployment->created_at }} UTC @@ -64,8 +61,17 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> @endif
@else -
- Manual +
+ Manual @if (data_get($deployment, 'rollback') === true) +
rolled back to
+ @endif + @if (data_get($deployment, 'commit')) +
+
+ ({{ data_get($deployment, 'commit') }})
+
+ @endif
@endif @if (data_get($deployment, 'server_name')) @@ -85,15 +91,19 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> 0s
-
+
@empty
No deployments found
@endforelse + @if ($deployments_count > 0)