diff --git a/README.md b/README.md
index 56bee004e..1b94d1b64 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,21 @@
+
+
[](https://console.algora.io/org/coollabsio/bounties/new)
[](https://console.algora.io/org/coollabsio/bounties?status=open)
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
+
# About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
-It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
+It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
-Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
+Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
-No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
+No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️
-For more information, take a look at our landing page [here](https://coolify.io).
+For more information, take a look at our landing page at [coolify.io](https://coolify.io).
# Installation
@@ -22,36 +26,41 @@ You can find the installation script source [here](./scripts/install.sh).
# Support
-Contact us [here](https://coolify.io/docs/contact).
+Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
# Donations
-To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
+To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
-https://coolify.io/sponsorships
+[coolify.io/sponsorships](https://coolify.io/sponsorships)
Thank you so much!
Special thanks to our biggest sponsors!
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
## Github Sponsors ($40+)
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -83,9 +92,9 @@ Special thanks to our biggest sponsors!
# Cloud
-If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
+If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
-For more information & pricing, take a look at our landing page [here](https://coolify.io).
+For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
## Why should I use the Cloud version?
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
@@ -109,7 +118,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
-
+
diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php
index 984225435..1b262c898 100644
--- a/app/Actions/Database/StopDatabaseProxy.php
+++ b/app/Actions/Database/StopDatabaseProxy.php
@@ -2,6 +2,7 @@
namespace App\Actions\Database;
+use App\Events\DatabaseStatusChanged;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
@@ -28,5 +29,6 @@ class StopDatabaseProxy
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
+ DatabaseStatusChanged::dispatch();
}
}
diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php
index eea429c79..b79bc8f67 100644
--- a/app/Actions/Server/StartSentinel.php
+++ b/app/Actions/Server/StartSentinel.php
@@ -12,12 +12,15 @@ class StartSentinel
public function handle(Server $server, $version = 'latest', bool $restart = false)
{
if ($restart) {
- instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
+ StopSentinel::run($server);
}
+ $metrics_history = $server->settings->metrics_history_days;
+ $refresh_rate = $server->settings->metrics_refresh_rate_seconds;
+ $token = $server->settings->metrics_token;
instant_remote_process([
- "docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
+ "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
- ], $server, false);
+ ], $server, true);
}
}
diff --git a/app/Actions/Server/StopSentinel.php b/app/Actions/Server/StopSentinel.php
new file mode 100644
index 000000000..21ffca3bd
--- /dev/null
+++ b/app/Actions/Server/StopSentinel.php
@@ -0,0 +1,16 @@
+option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments');
+
+ $this->replace_slash_in_environment_name();
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments();
@@ -150,4 +153,15 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n";
}
}
+
+ private function replace_slash_in_environment_name()
+ {
+ $environments = Environment::all();
+ foreach ($environments as $environment) {
+ if (str_contains($environment->name, '/')) {
+ $environment->name = str_replace('/', '-', $environment->name);
+ $environment->save();
+ }
+ }
+ }
}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
index e2698d90e..f529f63b9 100644
--- a/app/Console/Kernel.php
+++ b/app/Console/Kernel.php
@@ -61,7 +61,7 @@ class Kernel extends ConsoleKernel
{
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
- if (config('coolify.is_sentinel_enabled')) {
+ if ($server->isSentinelEnabled()) {
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
diff --git a/app/Data/ServerMetadata.php b/app/Data/ServerMetadata.php
index b96efa622..d95944b15 100644
--- a/app/Data/ServerMetadata.php
+++ b/app/Data/ServerMetadata.php
@@ -11,6 +11,5 @@ class ServerMetadata extends Data
public function __construct(
public ?ProxyTypes $type,
public ?ProxyStatus $status
- ) {
- }
+ ) {}
}
diff --git a/app/Events/ProxyStarted.php b/app/Events/ProxyStarted.php
index ed62eccb1..64d562e0a 100644
--- a/app/Events/ProxyStarted.php
+++ b/app/Events/ProxyStarted.php
@@ -10,8 +10,5 @@ class ProxyStarted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
- public function __construct(public $data)
- {
-
- }
+ public function __construct(public $data) {}
}
diff --git a/app/Exceptions/ProcessException.php b/app/Exceptions/ProcessException.php
index 728a0d81b..47eaa6fd8 100644
--- a/app/Exceptions/ProcessException.php
+++ b/app/Exceptions/ProcessException.php
@@ -4,6 +4,4 @@ namespace App\Exceptions;
use Exception;
-class ProcessException extends Exception
-{
-}
+class ProcessException extends Exception {}
diff --git a/app/Http/Controllers/Api/Applications.php b/app/Http/Controllers/Api/Applications.php
new file mode 100644
index 000000000..a638adecd
--- /dev/null
+++ b/app/Http/Controllers/Api/Applications.php
@@ -0,0 +1,183 @@
+get();
+ $applications = collect();
+ $applications->push($projects->pluck('applications')->flatten());
+ $applications = $applications->flatten();
+
+ return response()->json($applications);
+ }
+
+ public function application_by_uuid(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['error' => 'UUID is required.'], 400);
+ }
+ $application = Application::where('uuid', $uuid)->first();
+ if (! $application) {
+ return response()->json(['error' => 'Application not found.'], 404);
+ }
+
+ return response()->json($application);
+ }
+
+ public function update_by_uuid(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+
+ if ($request->collect()->count() == 0) {
+ return response()->json([
+ 'message' => 'No data provided.',
+ ], 400);
+ }
+ $application = Application::where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ ray($request->collect());
+
+ // if ($request->has('domains')) {
+ // $existingDomains = explode(',', $application->fqdn);
+ // $newDomains = $request->domains;
+ // $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) {
+ // return ! in_array($domain, $existingDomains);
+ // });
+ // $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains));
+ // $application->fqdn = implode(',', $mergedDomains);
+ // $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
+ // $application->save();
+ // }
+
+ return response()->json([
+ 'message' => 'Application updated successfully.',
+ 'application' => serialize_api_response($application),
+ ]);
+ }
+
+ public function action_deploy(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $force = $request->query->get('force') ?? false;
+ $instant_deploy = $request->query->get('instant_deploy') ?? false;
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['error' => 'UUID is required.'], 400);
+ }
+ $application = Application::where('uuid', $uuid)->first();
+ if (! $application) {
+ return response()->json(['error' => 'Application not found.'], 404);
+ }
+
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: $force,
+ is_api: true,
+ no_questions_asked: $instant_deploy
+ );
+
+ return response()->json(
+ [
+ 'message' => 'Deployment request queued.',
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
+ ],
+ 200
+ );
+ }
+
+ public function action_stop(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $uuid = $request->route('uuid');
+ $sync = $request->query->get('sync') ?? false;
+ if (! $uuid) {
+ return response()->json(['error' => 'UUID is required.'], 400);
+ }
+ $application = Application::where('uuid', $uuid)->first();
+ if (! $application) {
+ return response()->json(['error' => 'Application not found.'], 404);
+ }
+ if ($sync) {
+ StopApplication::run($application);
+
+ return response()->json(['message' => 'Stopped the application.'], 200);
+ } else {
+ StopApplication::dispatch($application);
+
+ return response()->json(['message' => 'Stopping request queued.'], 200);
+ }
+ }
+
+ public function action_restart(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['error' => 'UUID is required.'], 400);
+ }
+ $application = Application::where('uuid', $uuid)->first();
+ if (! $application) {
+ return response()->json(['error' => 'Application not found.'], 404);
+ }
+
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ restart_only: true,
+ is_api: true,
+ );
+
+ return response()->json(
+ [
+ 'message' => 'Restart request queued.',
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
+ ],
+ 200
+ );
+
+ }
+}
diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php
index d2abe2e31..f7a34facc 100644
--- a/app/Http/Controllers/Api/Deploy.php
+++ b/app/Http/Controllers/Api/Deploy.php
@@ -38,7 +38,25 @@ class Deploy extends Controller
'status',
])->sortBy('id')->toArray();
- return response()->json($deployments_per_server, 200);
+ return response()->json(serialize_api_response($deployments_per_server), 200);
+ }
+
+ public function deployment_by_uuid(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['error' => 'UUID is required.'], 400);
+ }
+ $deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs');
+ if (! $deployment) {
+ return response()->json(['error' => 'Deployment not found.'], 404);
+ }
+
+ return response()->json(serialize_api_response($deployment), 200);
}
public function deploy(Request $request)
diff --git a/app/Http/Controllers/Api/Domains.php b/app/Http/Controllers/Api/Domains.php
index c27ddf620..6473b64de 100644
--- a/app/Http/Controllers/Api/Domains.php
+++ b/app/Http/Controllers/Api/Domains.php
@@ -3,102 +3,52 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
-use App\Models\InstanceSettings;
-use App\Models\Project as ModelsProject;
+use App\Models\Application;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
class Domains extends Controller
{
- public function domains(Request $request)
+ public function deleteDomains(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
- $projects = ModelsProject::where('team_id', $teamId)->get();
- $domains = collect();
- $applications = $projects->pluck('applications')->flatten();
- $settings = InstanceSettings::get();
- if ($applications->count() > 0) {
- foreach ($applications as $application) {
- $ip = $application->destination->server->ip;
- $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
- return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
- });
- if ($ip === 'host.docker.internal') {
- if ($settings->public_ipv4) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv4,
- ]);
- }
- if ($settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv6,
- ]);
- }
- if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- } else {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- }
- }
- $services = $projects->pluck('services')->flatten();
- if ($services->count() > 0) {
- foreach ($services as $service) {
- $service_applications = $service->applications;
- if ($service_applications->count() > 0) {
- foreach ($service_applications as $application) {
- $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
- return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
- });
- if ($ip === 'host.docker.internal') {
- if ($settings->public_ipv4) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv4,
- ]);
- }
- if ($settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv6,
- ]);
- }
- if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- } else {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- }
- }
- }
- }
- $domains = $domains->groupBy('ip')->map(function ($domain) {
- return $domain->pluck('domain')->flatten();
- })->map(function ($domain, $ip) {
- return [
- 'ip' => $ip,
- 'domains' => $domain,
- ];
- })->values();
+ $validator = Validator::make($request->all(), [
+ 'uuid' => 'required|string|exists:applications,uuid',
+ 'domains' => 'required|array',
+ 'domains.*' => 'required|string|distinct',
+ ]);
- return response()->json($domains);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+
+ $application = Application::where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+
+ $existingDomains = explode(',', $application->fqdn);
+ $domainsToDelete = $request->domains;
+ $updatedDomains = array_diff($existingDomains, $domainsToDelete);
+ $application->fqdn = implode(',', $updatedDomains);
+ $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
+ $application->save();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Domains updated successfully',
+ 'application' => $application,
+ ]);
}
}
diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php
index 9f88a3b28..7a6090bf8 100644
--- a/app/Http/Controllers/Api/Server.php
+++ b/app/Http/Controllers/Api/Server.php
@@ -3,6 +3,9 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
+use App\Models\Application;
+use App\Models\InstanceSettings;
+use App\Models\Project;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
@@ -59,4 +62,106 @@ class Server extends Controller
return response()->json($server);
}
+
+ public function get_domains_by_server(Request $request)
+ {
+ $teamId = get_team_id_from_token();
+ if (is_null($teamId)) {
+ return invalid_token();
+ }
+ $uuid = $request->query->get('uuid');
+ if ($uuid) {
+ $domains = Application::getDomainsByUuid($uuid);
+
+ return response()->json([
+ 'uuid' => $uuid,
+ 'domains' => $domains,
+ ]);
+ }
+ $projects = Project::where('team_id', $teamId)->get();
+ $domains = collect();
+ $applications = $projects->pluck('applications')->flatten();
+ $settings = InstanceSettings::get();
+ if ($applications->count() > 0) {
+ foreach ($applications as $application) {
+ $ip = $application->destination->server->ip;
+ $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
+ return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
+ });
+ if ($ip === 'host.docker.internal') {
+ if ($settings->public_ipv4) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv4,
+ ]);
+ }
+ if ($settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv6,
+ ]);
+ }
+ if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ } else {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ }
+ }
+ $services = $projects->pluck('services')->flatten();
+ if ($services->count() > 0) {
+ foreach ($services as $service) {
+ $service_applications = $service->applications;
+ if ($service_applications->count() > 0) {
+ foreach ($service_applications as $application) {
+ $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
+ return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
+ });
+ if ($ip === 'host.docker.internal') {
+ if ($settings->public_ipv4) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv4,
+ ]);
+ }
+ if ($settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv6,
+ ]);
+ }
+ if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ } else {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ }
+ }
+ }
+ }
+ $domains = $domains->groupBy('ip')->map(function ($domain) {
+ return $domain->pluck('domain')->flatten();
+ })->map(function ($domain, $ip) {
+ return [
+ 'ip' => $ip,
+ 'domains' => $domain,
+ ];
+ })->values();
+
+ return response()->json($domains);
+ }
}
diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php
index 5b17fe926..9569e8cfa 100644
--- a/app/Http/Controllers/OauthController.php
+++ b/app/Http/Controllers/OauthController.php
@@ -2,8 +2,10 @@
namespace App\Http\Controllers;
+use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
+use Symfony\Component\HttpKernel\Exception\HttpException;
class OauthController extends Controller
{
@@ -20,6 +22,11 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (! $user) {
+ $settings = InstanceSettings::get();
+ if (! $settings->is_registration_enabled) {
+ abort(403, 'Registration is disabled');
+ }
+
$user = User::create([
'name' => $oauthUser->name,
'email' => $oauthUser->email,
@@ -31,7 +38,9 @@ class OauthController extends Controller
} catch (\Exception $e) {
ray($e->getMessage());
- return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
+ $errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback';
+
+ return redirect()->route('login')->withErrors([__($errorCode)]);
}
}
}
diff --git a/app/Http/Controllers/Webhook/Bitbucket.php b/app/Http/Controllers/Webhook/Bitbucket.php
index b9035b755..059438ff4 100644
--- a/app/Http/Controllers/Webhook/Bitbucket.php
+++ b/app/Http/Controllers/Webhook/Bitbucket.php
@@ -130,12 +130,23 @@ class Bitbucket extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
- ApplicationPreview::create([
- 'git_type' => 'bitbucket',
- 'application_id' => $application->id,
- 'pull_request_id' => $pull_request_id,
- 'pull_request_html_url' => $pull_request_html_url,
- ]);
+ if ($application->build_pack === 'dockercompose') {
+ $pr_app = ApplicationPreview::create([
+ 'git_type' => 'bitbucket',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ 'docker_compose_domains' => $application->docker_compose_domains,
+ ]);
+ $pr_app->generate_preview_fqdn_compose();
+ } else {
+ ApplicationPreview::create([
+ 'git_type' => 'bitbucket',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ ]);
+ }
}
queue_application_deployment(
application: $application,
diff --git a/app/Http/Controllers/Webhook/Gitea.php b/app/Http/Controllers/Webhook/Gitea.php
index 388481949..e6d91efd6 100644
--- a/app/Http/Controllers/Webhook/Gitea.php
+++ b/app/Http/Controllers/Webhook/Gitea.php
@@ -165,12 +165,24 @@ class Gitea extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
- ApplicationPreview::create([
- 'git_type' => 'gitea',
- 'application_id' => $application->id,
- 'pull_request_id' => $pull_request_id,
- 'pull_request_html_url' => $pull_request_html_url,
- ]);
+ if ($application->build_pack === 'dockercompose') {
+ $pr_app = ApplicationPreview::create([
+ 'git_type' => 'gitea',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ 'docker_compose_domains' => $application->docker_compose_domains,
+ ]);
+ $pr_app->generate_preview_fqdn_compose();
+ } else {
+ ApplicationPreview::create([
+ 'git_type' => 'gitea',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ ]);
+ }
+
}
queue_application_deployment(
application: $application,
diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php
index 403438193..a030e31ca 100644
--- a/app/Http/Controllers/Webhook/Github.php
+++ b/app/Http/Controllers/Webhook/Github.php
@@ -170,12 +170,23 @@ class Github extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
- ApplicationPreview::create([
- 'git_type' => 'github',
- 'application_id' => $application->id,
- 'pull_request_id' => $pull_request_id,
- 'pull_request_html_url' => $pull_request_html_url,
- ]);
+ if ($application->build_pack === 'dockercompose') {
+ $pr_app = ApplicationPreview::create([
+ 'git_type' => 'github',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ 'docker_compose_domains' => $application->docker_compose_domains,
+ ]);
+ $pr_app->generate_preview_fqdn_compose();
+ } else {
+ ApplicationPreview::create([
+ 'git_type' => 'github',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ ]);
+ }
}
queue_application_deployment(
application: $application,
diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php
index a3d7712eb..f6e6cf7e7 100644
--- a/app/Http/Controllers/Webhook/Gitlab.php
+++ b/app/Http/Controllers/Webhook/Gitlab.php
@@ -180,12 +180,23 @@ class Gitlab extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
- ApplicationPreview::create([
- 'git_type' => 'gitlab',
- 'application_id' => $application->id,
- 'pull_request_id' => $pull_request_id,
- 'pull_request_html_url' => $pull_request_html_url,
- ]);
+ if ($application->build_pack === 'dockercompose') {
+ $pr_app = ApplicationPreview::create([
+ 'git_type' => 'gitlab',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ 'docker_compose_domains' => $application->docker_compose_domains,
+ ]);
+ $pr_app->generate_preview_fqdn_compose();
+ } else {
+ ApplicationPreview::create([
+ 'git_type' => 'gitlab',
+ 'application_id' => $application->id,
+ 'pull_request_id' => $pull_request_id,
+ 'pull_request_html_url' => $pull_request_html_url,
+ ]);
+ }
}
queue_application_deployment(
application: $application,
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index 72d8c0ad1..2944b746d 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -9,6 +9,7 @@ use App\Events\ApplicationStatusChanged;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
+use App\Models\EnvironmentVariable;
use App\Models\GithubApp;
use App\Models\GitlabApp;
use App\Models\Server;
@@ -339,7 +340,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function post_deployment()
{
if ($this->server->isProxyShouldRun()) {
- GetContainersStatus::dispatch($this->server);
+ GetContainersStatus::dispatch($this->server)->onQueue('high');
// dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value);
@@ -827,6 +828,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
+ if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
+ $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
+ }
foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
@@ -868,6 +872,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
+ if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
+ $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
+ }
foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') {
@@ -877,7 +884,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$real_value = '\''.$real_value.'\'';
} else {
$real_value = escapeEnvVariables($env->real_value);
- ray($real_value);
}
}
$envs->push($env->key.'='.$real_value);
@@ -946,9 +952,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
- private function framework_based_notification()
+ private function laravel_finetunes()
{
- // Laravel old env variables
if ($this->pull_request_id === 0) {
$nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
@@ -956,9 +961,22 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first();
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
}
- if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
- $this->application_deployment_queue->addLogEntry('There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/resources/laravel', 'stderr');
+ if (! $nixpacks_php_fallback_path) {
+ $nixpacks_php_fallback_path = new EnvironmentVariable();
+ $nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
+ $nixpacks_php_fallback_path->value = '/index.php';
+ $nixpacks_php_fallback_path->application_id = $this->application->id;
+ $nixpacks_php_fallback_path->save();
}
+ if (! $nixpacks_php_root_dir) {
+ $nixpacks_php_root_dir = new EnvironmentVariable();
+ $nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
+ $nixpacks_php_root_dir->value = '/app/public';
+ $nixpacks_php_root_dir->application_id = $this->application->id;
+ $nixpacks_php_root_dir->save();
+ }
+
+ return [$nixpacks_php_fallback_path, $nixpacks_php_root_dir];
}
private function rolling_update()
@@ -1005,7 +1023,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry('Rolling update completed.');
}
}
- $this->framework_based_notification();
}
private function health_check()
@@ -1366,17 +1383,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
}
}
+
if ($this->saved_outputs->get('nixpacks_plan')) {
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
if ($this->nixpacks_plan) {
$this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}.");
$this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}");
$parsed = Toml::Parse($this->nixpacks_plan);
+
// Do any modifications here
$this->generate_env_variables();
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
$aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []);
if (count($aptPkgs) === 0) {
+ $aptPkgs = ['curl', 'wget'];
data_set($parsed, 'phases.setup.aptPkgs', ['curl', 'wget']);
} else {
if (! in_array('curl', $aptPkgs)) {
@@ -1388,6 +1408,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
data_set($parsed, 'phases.setup.aptPkgs', $aptPkgs);
}
data_set($parsed, 'variables', $merged_envs->toArray());
+ $is_laravel = data_get($parsed, 'variables.IS_LARAVEL', false);
+ if ($is_laravel) {
+ $variables = $this->laravel_finetunes();
+ data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value);
+ data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value);
+ }
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
}
@@ -1841,13 +1867,25 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true,
+ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]);
+ $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} else {
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir}"), 'hidden' => true,
+ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]);
+ $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
}
+
+ $base64_build_command = base64_encode($build_command);
+ $this->execute_remote_command(
+ [
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
+ ],
+ [
+ executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
+ ]
+ );
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
} else {
if ($this->force_rebuild) {
@@ -1866,7 +1904,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
]
);
}
-
$dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/
LABEL coolify.deploymentId={$this->deployment_uuid}
@@ -1929,13 +1966,24 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true,
+ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]);
+ $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} else {
$this->execute_remote_command([
- executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir}"), 'hidden' => true,
+ executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]);
+ $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
}
+ $base64_build_command = base64_encode($build_command);
+ $this->execute_remote_command(
+ [
+ executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
+ ],
+ [
+ executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
+ ]
+ );
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
} else {
if ($this->force_rebuild) {
@@ -2184,10 +2232,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
ray($code);
if ($code !== 69420) {
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
- $this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
- $this->execute_remote_command(
- ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true]
- );
+ if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
+ // do not remove already running container
+ } else {
+ $this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
+ $this->execute_remote_command(
+ ["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true]
+ );
+ }
}
}
}
diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php
index d400642dd..6120d1cba 100755
--- a/app/Jobs/ApplicationPullRequestUpdateJob.php
+++ b/app/Jobs/ApplicationPullRequestUpdateJob.php
@@ -25,8 +25,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
public ApplicationPreview $preview,
public ProcessStatus $status,
public ?string $deployment_uuid = null
- ) {
- }
+ ) {}
public function handle()
{
diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php
index 312200f66..16ef85192 100644
--- a/app/Jobs/CheckLogDrainContainerJob.php
+++ b/app/Jobs/CheckLogDrainContainerJob.php
@@ -19,9 +19,7 @@ class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function middleware(): array
{
diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php
index 8f2039ef2..b55ae9967 100644
--- a/app/Jobs/CheckResaleLicenseJob.php
+++ b/app/Jobs/CheckResaleLicenseJob.php
@@ -14,9 +14,7 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(): void
{
diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php
index 418c7a0f4..7b064a464 100644
--- a/app/Jobs/CleanupHelperContainersJob.php
+++ b/app/Jobs/CleanupHelperContainersJob.php
@@ -15,9 +15,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function handle(): void
{
diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php
index b846ad2bc..d9de3f6fe 100644
--- a/app/Jobs/CleanupInstanceStuffsJob.php
+++ b/app/Jobs/CleanupInstanceStuffsJob.php
@@ -16,10 +16,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct()
- {
-
- }
+ public function __construct() {}
// public function uniqueId(): string
// {
diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php
index c50d17d4c..e919855d5 100644
--- a/app/Jobs/ContainerStatusJob.php
+++ b/app/Jobs/ContainerStatusJob.php
@@ -23,9 +23,7 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3;
}
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function middleware(): array
{
diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php
index e5f4dfd5e..5418daa22 100755
--- a/app/Jobs/CoolifyTask.php
+++ b/app/Jobs/CoolifyTask.php
@@ -23,8 +23,7 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
public bool $ignore_errors = false,
public $call_event_on_finish = null,
public $call_event_data = null
- ) {
- }
+ ) {}
/**
* Execute the job.
diff --git a/app/Jobs/DatabaseBackupStatusJob.php b/app/Jobs/DatabaseBackupStatusJob.php
index cf240e0d7..d3b0e99cf 100644
--- a/app/Jobs/DatabaseBackupStatusJob.php
+++ b/app/Jobs/DatabaseBackupStatusJob.php
@@ -18,9 +18,7 @@ class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
public $tries = 1;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle()
{
diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php
index 6d4720f6b..8710fda88 100644
--- a/app/Jobs/DeleteResourceJob.php
+++ b/app/Jobs/DeleteResourceJob.php
@@ -28,9 +28,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false)
- {
- }
+ public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
public function handle()
{
diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php
index 32c41e99c..e637fb6d4 100644
--- a/app/Jobs/DockerCleanupJob.php
+++ b/app/Jobs/DockerCleanupJob.php
@@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public ?int $usageBefore = null;
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function handle(): void
{
@@ -62,7 +60,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
Log::info('No need to clean up '.$this->server->name);
}
} catch (\Throwable $e) {
- send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
+ // send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
ray($e->getMessage());
throw $e;
}
diff --git a/app/Jobs/GithubAppPermissionJob.php b/app/Jobs/GithubAppPermissionJob.php
index bab8f3a25..3188d35d6 100644
--- a/app/Jobs/GithubAppPermissionJob.php
+++ b/app/Jobs/GithubAppPermissionJob.php
@@ -23,9 +23,7 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3;
}
- public function __construct(public GithubApp $github_app)
- {
- }
+ public function __construct(public GithubApp $github_app) {}
public function middleware(): array
{
diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php
index bce60bbc8..1bbfcf8cb 100644
--- a/app/Jobs/InstanceAutoUpdateJob.php
+++ b/app/Jobs/InstanceAutoUpdateJob.php
@@ -19,9 +19,7 @@ class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, Should
public $tries = 1;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(): void
{
diff --git a/app/Jobs/PullCoolifyImageJob.php b/app/Jobs/PullCoolifyImageJob.php
index ccaa785dc..2bcbfc4df 100644
--- a/app/Jobs/PullCoolifyImageJob.php
+++ b/app/Jobs/PullCoolifyImageJob.php
@@ -19,9 +19,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 1000;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(): void
{
diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php
index d3bda2ea1..30a1b8026 100644
--- a/app/Jobs/PullHelperImageJob.php
+++ b/app/Jobs/PullHelperImageJob.php
@@ -27,9 +27,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
return $this->server->uuid;
}
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function handle(): void
{
diff --git a/app/Jobs/PullSentinelImageJob.php b/app/Jobs/PullSentinelImageJob.php
index 1dd4b1dd3..f8c769382 100644
--- a/app/Jobs/PullSentinelImageJob.php
+++ b/app/Jobs/PullSentinelImageJob.php
@@ -28,9 +28,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
return $this->server->uuid;
}
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function handle(): void
{
@@ -52,7 +50,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
}
ray('Sentinel image is up to date');
} catch (\Throwable $e) {
- send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
+ // send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
ray($e->getMessage());
throw $e;
}
diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php
index 948060033..396ff29f4 100644
--- a/app/Jobs/PullTemplatesFromCDN.php
+++ b/app/Jobs/PullTemplatesFromCDN.php
@@ -17,9 +17,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
public $timeout = 10;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(): void
{
diff --git a/app/Jobs/PullVersionsFromCDN.php b/app/Jobs/PullVersionsFromCDN.php
index 1ad4989de..79ebad7a8 100644
--- a/app/Jobs/PullVersionsFromCDN.php
+++ b/app/Jobs/PullVersionsFromCDN.php
@@ -17,9 +17,7 @@ class PullVersionsFromCDN implements ShouldBeEncrypted, ShouldQueue
public $timeout = 10;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(): void
{
diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php
index 4d5618df0..73e8658ee 100755
--- a/app/Jobs/SendConfirmationForWaitlistJob.php
+++ b/app/Jobs/SendConfirmationForWaitlistJob.php
@@ -14,9 +14,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public string $email, public string $uuid)
- {
- }
+ public function __construct(public string $email, public string $uuid) {}
public function handle()
{
diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php
index 90f2e0b30..f38cf823c 100644
--- a/app/Jobs/SendMessageToDiscordJob.php
+++ b/app/Jobs/SendMessageToDiscordJob.php
@@ -31,8 +31,7 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public string $text,
public string $webhookUrl
- ) {
- }
+ ) {}
/**
* Execute the job.
diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php
index b81bbc50b..bf52b782f 100644
--- a/app/Jobs/SendMessageToTelegramJob.php
+++ b/app/Jobs/SendMessageToTelegramJob.php
@@ -33,8 +33,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
public string $token,
public string $chatId,
public ?string $topicId = null,
- ) {
- }
+ ) {}
/**
* Execute the job.
diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php
index 2476c12dd..769dfc004 100644
--- a/app/Jobs/ServerFilesFromServerJob.php
+++ b/app/Jobs/ServerFilesFromServerJob.php
@@ -16,9 +16,7 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public ServiceApplication|ServiceDatabase|Application $resource)
- {
- }
+ public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {}
public function handle()
{
diff --git a/app/Jobs/ServerLimitCheckJob.php b/app/Jobs/ServerLimitCheckJob.php
index 3eaf88ba7..24292025b 100644
--- a/app/Jobs/ServerLimitCheckJob.php
+++ b/app/Jobs/ServerLimitCheckJob.php
@@ -24,9 +24,7 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3;
}
- public function __construct(public Team $team)
- {
- }
+ public function __construct(public Team $team) {}
public function middleware(): array
{
diff --git a/app/Jobs/ServerStatusJob.php b/app/Jobs/ServerStatusJob.php
index aaf8f5784..c7321a74c 100644
--- a/app/Jobs/ServerStatusJob.php
+++ b/app/Jobs/ServerStatusJob.php
@@ -25,9 +25,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3;
}
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function middleware(): array
{
@@ -48,7 +46,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
if ($this->server->isFunctional()) {
$this->cleanup(notify: false);
$this->remove_unnecessary_coolify_yaml();
- if (config('coolify.is_sentinel_enabled')) {
+ if ($this->server->isSentinelEnabled()) {
$this->server->checkSentinel();
}
}
diff --git a/app/Jobs/ServerStorageSaveJob.php b/app/Jobs/ServerStorageSaveJob.php
index c94a3edc5..526cd5375 100644
--- a/app/Jobs/ServerStorageSaveJob.php
+++ b/app/Jobs/ServerStorageSaveJob.php
@@ -14,9 +14,7 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(public LocalFileVolume $localFileVolume)
- {
- }
+ public function __construct(public LocalFileVolume $localFileVolume) {}
public function handle()
{
diff --git a/app/Jobs/SubscriptionInvoiceFailedJob.php b/app/Jobs/SubscriptionInvoiceFailedJob.php
index e4cd219c8..64a75671f 100755
--- a/app/Jobs/SubscriptionInvoiceFailedJob.php
+++ b/app/Jobs/SubscriptionInvoiceFailedJob.php
@@ -15,9 +15,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- public function __construct(protected Team $team)
- {
- }
+ public function __construct(protected Team $team) {}
public function handle()
{
diff --git a/app/Jobs/SubscriptionTrialEndedJob.php b/app/Jobs/SubscriptionTrialEndedJob.php
index ee260d8d9..dd2250dd7 100755
--- a/app/Jobs/SubscriptionTrialEndedJob.php
+++ b/app/Jobs/SubscriptionTrialEndedJob.php
@@ -17,8 +17,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Team $team
- ) {
- }
+ ) {}
public function handle(): void
{
diff --git a/app/Jobs/SubscriptionTrialEndsSoonJob.php b/app/Jobs/SubscriptionTrialEndsSoonJob.php
index fba668108..80e232a3e 100755
--- a/app/Jobs/SubscriptionTrialEndsSoonJob.php
+++ b/app/Jobs/SubscriptionTrialEndsSoonJob.php
@@ -17,8 +17,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
public function __construct(
public Team $team
- ) {
- }
+ ) {}
public function handle(): void
{
diff --git a/app/Listeners/MaintenanceModeDisabledNotification.php b/app/Listeners/MaintenanceModeDisabledNotification.php
index 9f676ca99..ded53ccee 100644
--- a/app/Listeners/MaintenanceModeDisabledNotification.php
+++ b/app/Listeners/MaintenanceModeDisabledNotification.php
@@ -9,9 +9,7 @@ use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class MaintenanceModeDisabledNotification
{
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(EventsMaintenanceModeDisabled $event): void
{
diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php
index 64271cc52..d0541b162 100644
--- a/app/Listeners/ProxyStartedNotification.php
+++ b/app/Listeners/ProxyStartedNotification.php
@@ -9,9 +9,7 @@ class ProxyStartedNotification
{
public Server $server;
- public function __construct()
- {
- }
+ public function __construct() {}
public function handle(ProxyStarted $event): void
{
diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php
index b787ed0cc..7acf5ed87 100644
--- a/app/Livewire/Boarding/Index.php
+++ b/app/Livewire/Boarding/Index.php
@@ -12,7 +12,7 @@ use Livewire\Component;
class Index extends Component
{
- protected $listeners = ['serverInstalled' => 'validateServer'];
+ protected $listeners = ['refreshBoardingIndex' => 'validateServer'];
public string $currentState = 'welcome';
diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php
index cbbe98d99..b3e39d23d 100644
--- a/app/Livewire/Project/Application/DeploymentNavbar.php
+++ b/app/Livewire/Project/Application/DeploymentNavbar.php
@@ -54,9 +54,9 @@ class DeploymentNavbar extends Component
public function cancel()
{
+ $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
+ $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
try {
- $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
- $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
$server = Server::find($server_id);
if ($this->application_deployment_queue->logs) {
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
@@ -84,6 +84,7 @@ class DeploymentNavbar extends Component
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
+ next_after_cancel($server);
}
}
}
diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php
index 60cdee48e..06ff7b1de 100644
--- a/app/Livewire/Project/Application/General.php
+++ b/app/Livewire/Project/Application/General.php
@@ -347,7 +347,9 @@ class General extends Component
public function submit($showToaster = true)
{
try {
- $this->set_redirect();
+ if ($this->application->isDirty('redirect')) {
+ $this->set_redirect();
+ }
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php
index d224f4a9d..feb54c7f0 100644
--- a/app/Livewire/Project/Application/Heading.php
+++ b/app/Livewire/Project/Application/Heading.php
@@ -45,7 +45,7 @@ class Heading extends Component
public function check_status($showNotification = false)
{
if ($this->application->destination->server->isFunctional()) {
- GetContainersStatus::dispatch($this->application->destination->server);
+ GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
// dispatch(new ContainerStatusJob($this->application->destination->server));
} else {
dispatch(new ServerStatusJob($this->application->destination->server));
diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php
index ca911339e..df64c3fd3 100644
--- a/app/Livewire/Project/Application/Previews.php
+++ b/app/Livewire/Project/Application/Previews.php
@@ -131,6 +131,12 @@ class Previews extends Component
}
}
+ public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null)
+ {
+ $this->add($pull_request_id, $pull_request_html_url);
+ $this->deploy($pull_request_id, $pull_request_html_url);
+ }
+
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null)
{
try {
@@ -180,7 +186,7 @@ class Previews extends Component
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
}
}
- GetContainersStatus::dispatchSync($this->application->destination->server);
+ GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high');
$this->dispatch('reloadWindow');
} catch (\Throwable $e) {
return handleError($e, $this);
diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php
index 61dafa76f..ae88ac12b 100644
--- a/app/Livewire/Project/Database/Heading.php
+++ b/app/Livewire/Project/Database/Heading.php
@@ -12,7 +12,6 @@ use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus;
-use App\Jobs\ContainerStatusJob;
use Livewire\Component;
class Heading extends Component
diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php
index 38cac2e5c..1c5d39055 100644
--- a/app/Livewire/Project/Database/Postgresql/General.php
+++ b/app/Livewire/Project/Database/Postgresql/General.php
@@ -25,7 +25,17 @@ class General extends Component
public ?string $db_url_public = null;
- protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
+ public function getListeners()
+ {
+ $userId = auth()->user()->id;
+
+ return [
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped',
+ 'refresh',
+ 'save_init_script',
+ 'delete_init_script',
+ ];
+ }
protected $rules = [
'database.name' => 'required',
@@ -69,6 +79,11 @@ class General extends Component
$this->server = data_get($this->database, 'destination.server');
}
+ public function database_stopped()
+ {
+ $this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.');
+ }
+
public function instantSaveAdvanced()
{
try {
diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php
index 739061f1f..7ac7883dc 100644
--- a/app/Livewire/Project/New/PublicGitRepository.php
+++ b/app/Livewire/Project/New/PublicGitRepository.php
@@ -128,8 +128,8 @@ class PublicGitRepository extends Component
) {
$this->repository_url = $this->repository_url.'.git';
}
- if (str($this->repository_url)->contains('github.com')) {
- $this->repository_url = str($this->repository_url)->before('.git')->value();
+ if (str($this->repository_url)->contains('github.com') && str($this->repository_url)->endsWith('.git')) {
+ $this->repository_url = str($this->repository_url)->beforeLast('.git')->value();
}
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -140,7 +140,6 @@ class PublicGitRepository extends Component
$this->get_branch();
$this->selected_branch = $this->git_branch;
} catch (\Throwable $e) {
- ray($e->getMessage());
if (! $this->branch_found && $this->git_branch == 'main') {
try {
$this->git_branch = 'master';
diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php
index b8d186dab..b25290f71 100644
--- a/app/Livewire/Project/New/Select.php
+++ b/app/Livewire/Project/New/Select.php
@@ -176,10 +176,12 @@ class Select extends Component
return;
}
- // if (count($this->servers) === 1) {
- // $server = $this->servers->first();
- // $this->setServer($server);
- // }
+ if (count($this->servers) === 1) {
+ $server = $this->servers->first();
+ if ($server instanceof Server) {
+ $this->setServer($server);
+ }
+ }
if (! is_null($this->server)) {
$foundServer = $this->servers->where('id', $this->server->id)->first();
if ($foundServer) {
@@ -195,6 +197,13 @@ class Select extends Component
$this->server = $server;
$this->standaloneDockers = $server->standaloneDockers;
$this->swarmDockers = $server->swarmDockers;
+ $count = count($this->standaloneDockers) + count($this->swarmDockers);
+ if ($count === 1) {
+ $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
+ if ($docker) {
+ $this->setDestination($docker->uuid);
+ }
+ }
$this->current_step = 'destinations';
}
diff --git a/app/Livewire/Project/Resource/EnvironmentSelect.php b/app/Livewire/Project/Resource/EnvironmentSelect.php
new file mode 100644
index 000000000..efb1b6ca2
--- /dev/null
+++ b/app/Livewire/Project/Resource/EnvironmentSelect.php
@@ -0,0 +1,35 @@
+selectedEnvironment = request()->route('environment_name');
+ $this->project_uuid = request()->route('project_uuid');
+ }
+
+ public function updatedSelectedEnvironment($value)
+ {
+ if ($value === 'edit') {
+ return redirect()->route('project.show', [
+ 'project_uuid' => $this->project_uuid,
+ ]);
+ } else {
+ return redirect()->route('project.resource.index', [
+ 'project_uuid' => $this->project_uuid,
+ 'environment_name' => $value,
+ ]);
+ }
+ }
+}
diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php
index fd4d684b1..c9bdf12fc 100644
--- a/app/Livewire/Project/Service/EditCompose.php
+++ b/app/Livewire/Project/Service/EditCompose.php
@@ -11,6 +11,8 @@ class EditCompose extends Component
public $serviceId;
+ protected $listeners = ['refreshEnvs' => 'mount'];
+
protected $rules = [
'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required',
diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php
index 05917f895..a1b613a43 100644
--- a/app/Livewire/Project/Service/StackForm.php
+++ b/app/Livewire/Project/Service/StackForm.php
@@ -75,7 +75,6 @@ class StackForm extends Component
$this->service->parse();
$this->service->refresh();
$this->service->saveComposeConfigs();
- $this->dispatch('refreshStacks');
$this->dispatch('refreshEnvs');
$this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) {
diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
index 4c06bfe23..d67dae19e 100644
--- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php
+++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php
@@ -112,7 +112,6 @@ class All extends Component
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
- ray($variables, $this->variables);
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
}
foreach ($variables as $key => $variable) {
diff --git a/app/Livewire/Project/Shared/Logs.php b/app/Livewire/Project/Shared/Logs.php
index 008d743ed..e646f8a26 100644
--- a/app/Livewire/Project/Shared/Logs.php
+++ b/app/Livewire/Project/Shared/Logs.php
@@ -64,7 +64,7 @@ class Logs extends Component
return;
$server = data_get($this->resource, 'destination.server');
if ($server->isFunctional()) {
- $this->cpu = $server->getMetrics();
+ $this->cpu = $server->getCpuMetrics();
}
}
diff --git a/app/Livewire/Project/Shared/Metrics.php b/app/Livewire/Project/Shared/Metrics.php
new file mode 100644
index 000000000..d9d7dd3ef
--- /dev/null
+++ b/app/Livewire/Project/Shared/Metrics.php
@@ -0,0 +1,64 @@
+poll || $this->interval <= 10) {
+ $this->loadData();
+ if ($this->interval > 10) {
+ $this->poll = false;
+ }
+ }
+ }
+
+ public function loadData()
+ {
+ try {
+ $metrics = $this->resource->getMetrics($this->interval);
+ $cpuMetrics = collect($metrics)->map(function ($metric) {
+ return [$metric[0], $metric[1]];
+ });
+ $memoryMetrics = collect($metrics)->map(function ($metric) {
+ return [$metric[0], $metric[2]];
+ });
+ $this->dispatch("refreshChartData-{$this->chartId}-cpu", [
+ 'seriesData' => $cpuMetrics,
+ ]);
+ $this->dispatch("refreshChartData-{$this->chartId}-memory", [
+ 'seriesData' => $memoryMetrics,
+ ]);
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function setInterval()
+ {
+ if ($this->interval <= 10) {
+ $this->poll = true;
+ }
+ $this->loadData();
+ }
+
+ public function render()
+ {
+ return view('livewire.project.shared.metrics');
+ }
+}
diff --git a/app/Livewire/Project/Show.php b/app/Livewire/Project/Show.php
index d5d660017..1082f078c 100644
--- a/app/Livewire/Project/Show.php
+++ b/app/Livewire/Project/Show.php
@@ -9,6 +9,8 @@ class Show extends Component
{
public Project $project;
+ public $environments;
+
public function mount()
{
$projectUuid = request()->route('project_uuid');
@@ -18,7 +20,8 @@ class Show extends Component
if (! $project) {
return redirect()->route('dashboard');
}
- $project->load(['environments']);
+
+ $this->environments = $project->environments->sortBy('created_at');
$this->project = $project;
}
diff --git a/app/Livewire/Server/Charts.php b/app/Livewire/Server/Charts.php
new file mode 100644
index 000000000..0921c7fa4
--- /dev/null
+++ b/app/Livewire/Server/Charts.php
@@ -0,0 +1,62 @@
+poll || $this->interval <= 10) {
+ $this->loadData();
+ if ($this->interval > 10) {
+ $this->poll = false;
+ }
+ }
+ }
+
+ public function loadData()
+ {
+ try {
+ $cpuMetrics = $this->server->getCpuMetrics($this->interval);
+ $memoryMetrics = $this->server->getMemoryMetrics($this->interval);
+ $cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
+ return [$metric[0], $metric[1]];
+ });
+ $memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
+ return [$metric[0], $metric[1]];
+ });
+ $this->dispatch("refreshChartData-{$this->chartId}-cpu", [
+ 'seriesData' => $cpuMetrics,
+ ]);
+ $this->dispatch("refreshChartData-{$this->chartId}-memory", [
+ 'seriesData' => $memoryMetrics,
+ ]);
+
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function setInterval()
+ {
+ if ($this->interval <= 10) {
+ $this->poll = true;
+ }
+ $this->loadData();
+ }
+}
diff --git a/app/Livewire/Server/ConfigureCloudflareTunnels.php b/app/Livewire/Server/ConfigureCloudflareTunnels.php
index 7d2103e37..f7306a5b5 100644
--- a/app/Livewire/Server/ConfigureCloudflareTunnels.php
+++ b/app/Livewire/Server/ConfigureCloudflareTunnels.php
@@ -21,7 +21,7 @@ class ConfigureCloudflareTunnels extends Component
$server->settings->is_cloudflare_tunnel = true;
$server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
- $this->dispatch('serverInstalled');
+ $this->dispatch('refreshServerShow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -37,7 +37,7 @@ class ConfigureCloudflareTunnels extends Component
$server->save();
$server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
- $this->dispatch('serverInstalled');
+ $this->dispatch('refreshServerShow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php
index 263ff6367..5616123a5 100644
--- a/app/Livewire/Server/Form.php
+++ b/app/Livewire/Server/Form.php
@@ -2,6 +2,9 @@
namespace App\Livewire\Server;
+use App\Actions\Server\StartSentinel;
+use App\Actions\Server\StopSentinel;
+use App\Jobs\PullSentinelImageJob;
use App\Models\Server;
use Livewire\Component;
@@ -36,7 +39,12 @@ class Form extends Component
'server.settings.is_build_server' => 'required|boolean',
'server.settings.concurrent_builds' => 'required|integer|min:1',
'server.settings.dynamic_timeout' => 'required|integer|min:1',
+ 'server.settings.is_metrics_enabled' => 'required|boolean',
+ 'server.settings.metrics_token' => 'required',
+ 'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1',
+ 'server.settings.metrics_history_days' => 'required|integer|min:1',
'wildcard_domain' => 'nullable|url',
+ 'server.settings.is_server_api_enabled' => 'required|boolean',
];
protected $validationAttributes = [
@@ -52,7 +60,11 @@ class Form extends Component
'server.settings.is_build_server' => 'Build Server',
'server.settings.concurrent_builds' => 'Concurrent Builds',
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
-
+ 'server.settings.is_metrics_enabled' => 'Metrics',
+ 'server.settings.metrics_token' => 'Metrics Token',
+ 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
+ 'server.settings.metrics_history_days' => 'Metrics History',
+ 'server.settings.is_server_api_enabled' => 'Server API',
];
public function mount()
@@ -69,18 +81,59 @@ class Form extends Component
public function updatedServerSettingsIsBuildServer()
{
- $this->dispatch('serverInstalled');
+ $this->dispatch('refreshServerShow');
$this->dispatch('serverRefresh');
$this->dispatch('proxyStatusUpdated');
}
+ public function checkPortForServerApi()
+ {
+ try {
+ if ($this->server->settings->is_server_api_enabled === true) {
+ $this->server->checkServerApi();
+ $this->dispatch('success', 'Server API is reachable.');
+ }
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
public function instantSave()
{
try {
refresh_server_connection($this->server->privateKey);
$this->validateServer(false);
$this->server->settings->save();
+ $this->server->save();
$this->dispatch('success', 'Server updated.');
+ $this->dispatch('refreshServerShow');
+ if ($this->server->isSentinelEnabled()) {
+ PullSentinelImageJob::dispatchSync($this->server);
+ ray('Sentinel is enabled');
+ if ($this->server->settings->isDirty('is_metrics_enabled')) {
+ $this->dispatch('reloadWindow');
+ }
+ if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
+ ray('Starting sentinel');
+
+ }
+ } else {
+ ray('Sentinel is not enabled');
+ StopSentinel::dispatch($this->server);
+ }
+ // $this->checkPortForServerApi();
+
+ } catch (\Throwable $e) {
+ return handleError($e, $this);
+ }
+ }
+
+ public function restartSentinel()
+ {
+ try {
+ $version = get_latest_sentinel_version();
+ StartSentinel::run($this->server, $version, true);
+ $this->dispatch('success', 'Sentinel restarted.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php
index 7ebf90115..0751b186e 100644
--- a/app/Livewire/Server/Show.php
+++ b/app/Livewire/Server/Show.php
@@ -14,7 +14,7 @@ class Show extends Component
public $parameters = [];
- protected $listeners = ['serverInstalled' => '$refresh'];
+ protected $listeners = ['refreshServerShow' => '$refresh'];
public function mount()
{
diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php
index bd33937e0..422cae779 100644
--- a/app/Livewire/Server/ValidateAndInstall.php
+++ b/app/Livewire/Server/ValidateAndInstall.php
@@ -143,7 +143,8 @@ class ValidateAndInstall extends Component
} else {
$this->docker_version = $this->server->validateDockerEngineVersion();
if ($this->docker_version) {
- $this->dispatch('serverInstalled');
+ $this->dispatch('refreshServerShow');
+ $this->dispatch('refreshBoardingIndex');
$this->dispatch('success', 'Server validated.');
} else {
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: documentation.';
diff --git a/app/Models/Application.php b/app/Models/Application.php
index 6e55f6626..50c7760ff 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -228,18 +228,13 @@ class Application extends BaseModel
public function gitCommitLink($link): string
{
- if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
+ if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) {
if (str($this->source->html_url)->contains('bitbucket')) {
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
}
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}";
- }
if (str($this->git_repository)->contains('bitbucket')) {
$git_repository = str_replace('.git', '', $this->git_repository);
$url = Url::fromString($git_repository);
@@ -248,6 +243,14 @@ class Application extends BaseModel
return $url->__toString();
}
+ if (strpos($this->git_repository, 'git@') === 0) {
+ $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
+ if (data_get($this, 'source.html_url')) {
+ return "{$this->source->html_url}/{$git_repository}/commit/{$link}";
+ }
+
+ return "{$git_repository}/commit/{$link}";
+ }
return $this->git_repository;
}
@@ -532,7 +535,7 @@ class Application extends BaseModel
public function get_last_successful_deployment()
{
- return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'finished')->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first();
+ return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::FINISHED)->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first();
}
public function get_last_days_deployments()
@@ -1167,4 +1170,44 @@ class Application extends BaseModel
return $preview;
}
+
+ public static function getDomainsByUuid(string $uuid): array
+ {
+ $application = self::where('uuid', $uuid)->first();
+
+ if ($application) {
+ return $application->fqdns;
+ }
+
+ return [];
+ }
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/Environment.php b/app/Models/Environment.php
index e84b6989b..b2bb51092 100644
--- a/app/Models/Environment.php
+++ b/app/Models/Environment.php
@@ -109,7 +109,7 @@ class Environment extends Model
protected function name(): Attribute
{
return Attribute::make(
- set: fn (string $value) => strtolower($value),
+ set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(),
);
}
}
diff --git a/app/Models/Kubernetes.php b/app/Models/Kubernetes.php
index 2ad7a2110..174cb5bc8 100644
--- a/app/Models/Kubernetes.php
+++ b/app/Models/Kubernetes.php
@@ -2,6 +2,4 @@
namespace App\Models;
-class Kubernetes extends BaseModel
-{
-}
+class Kubernetes extends BaseModel {}
diff --git a/app/Models/Project.php b/app/Models/Project.php
index acc98e341..1cbce6cac 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -112,4 +112,14 @@ class Project extends BaseModel
{
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
}
+
+ public function default_environment()
+ {
+ $default = $this->environments()->where('name', 'production')->first();
+ if (! $default) {
+ $default = $this->environments()->sortBy('created_at')->first();
+ }
+
+ return $default;
+ }
}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index b1419dc0e..7a99940fd 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -5,12 +5,11 @@ namespace App\Models;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Jobs\PullSentinelImageJob;
-use App\Notifications\Server\Revived;
-use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
@@ -462,10 +461,44 @@ $schema://$host {
Storage::disk('ssh-mux')->delete($this->muxFilename());
}
+ public function isSentinelEnabled()
+ {
+ return $this->isMetricsEnabled() || $this->isServerApiEnabled();
+ }
+
+ public function isMetricsEnabled()
+ {
+ return $this->settings->is_metrics_enabled;
+ }
+
+ public function isServerApiEnabled()
+ {
+ return $this->settings->is_server_api_enabled;
+ }
+
+ public function checkServerApi()
+ {
+ if ($this->isServerApiEnabled()) {
+ $server_ip = $this->ip;
+ if (isDev()) {
+ if ($this->id === 0) {
+ $server_ip = 'localhost';
+ }
+ }
+ $command = "curl -s http://{$server_ip}:12172/api/health";
+ $process = Process::timeout(5)->run($command);
+ if ($process->failed()) {
+ ray($process->exitCode(), $process->output(), $process->errorOutput());
+ throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
+ }
+
+ }
+ }
+
public function checkSentinel()
{
ray("Checking sentinel on server: {$this->name}");
- if ($this->is_metrics_enabled) {
+ if ($this->isSentinelEnabled()) {
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
@@ -478,21 +511,57 @@ $schema://$host {
}
}
- public function getMetrics()
+ public function getCpuMetrics(int $mins = 5)
{
- if ($this->is_metrics_enabled) {
- $from = now()->subMinutes(5)->toIso8601ZuluString();
- $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ if ($this->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
+ if (str($cpu)->contains('error')) {
+ $error = json_decode($cpu, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
$cpu = str($cpu)->explode("\n")->skip(1)->all();
$parsedCollection = collect($cpu)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
- [$time, $value] = explode(',', trim($line));
+ [$time, $cpu_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 0);
- return [(int) $time, (float) $value];
+ return [(int) $time, (float) $cpu_usage_percent];
});
- })->toArray();
+ });
- return $parsedCollection;
+ return $parsedCollection->toArray();
+ }
+ }
+
+ public function getMemoryMetrics(int $mins = 5)
+ {
+ if ($this->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
+ if (str($memory)->contains('error')) {
+ $error = json_decode($memory, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $memory = str($memory)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($memory)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $used, $free, $usedPercent] = explode(',', trim($line));
+ $usedPercent = number_format($usedPercent, 0);
+
+ return [(int) $time, (float) $usedPercent];
+ });
+ });
+
+ return $parsedCollection->toArray();
}
}
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 7851eb58a..c608e38dd 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
+use Symfony\Component\Yaml\Yaml;
class Service extends BaseModel
{
@@ -837,14 +838,34 @@ class Service extends BaseModel
$commands[] = "mkdir -p $workdir";
$commands[] = "cd $workdir";
+ $json = Yaml::parse($this->docker_compose);
+ $envs_from_coolify = $this->environment_variables()->get();
+ foreach ($json['services'] as $service => $config) {
+ $envs = collect($config['environment']);
+ $envs->push("COOLIFY_CONTAINER_NAME=$service-{$this->uuid}");
+ foreach ($envs_from_coolify as $env) {
+ $envs = $envs->map(function ($value) use ($env) {
+ if (str($value)->startsWith($env->key)) {
+ return "{$env->key}={$env->real_value}";
+ }
+
+ return $value;
+ });
+ }
+ $envs = $envs->unique();
+ data_set($json, "services.$service.environment", $envs->toArray());
+ }
+
+ $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$docker_compose_base64 = base64_encode($this->docker_compose);
+
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
- $envs = $this->environment_variables()->get();
$commands[] = 'rm -f .env || true';
- foreach ($envs as $env) {
+
+ foreach ($envs_from_coolify as $env) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
}
- if ($envs->count() === 0) {
+ if ($envs_from_coolify->count() === 0) {
$commands[] = 'touch .env';
}
instant_remote_process($commands, $this->server);
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index c5e252c34..e968db18d 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -226,4 +226,33 @@ class StandaloneClickhouse extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index 8c739d984..c6718acfe 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -226,4 +226,33 @@ class StandaloneDragonfly extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 5216681c9..142f960aa 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -226,4 +226,33 @@ class StandaloneKeydb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 33fd2cbc2..7e6d2e0d1 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -226,4 +226,33 @@ class StandaloneMariadb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index 0cc52b3f7..df895bb34 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -246,4 +246,33 @@ class StandaloneMongodb extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index 174736f77..bd160f877 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -227,4 +227,33 @@ class StandaloneMysql extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index a5bf4dc2a..114d376e8 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -227,4 +227,33 @@ class StandalonePostgresql extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index ed379750e..022cd8d09 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -222,4 +222,33 @@ class StandaloneRedis extends BaseModel
{
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
}
+
+ public function getMetrics(int $mins = 5)
+ {
+ $server = $this->destination->server;
+ $container_name = $this->uuid;
+ if ($server->isMetricsEnabled()) {
+ $from = now()->subMinutes($mins)->toIso8601ZuluString();
+ $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
+ if (str($metrics)->contains('error')) {
+ $error = json_decode($metrics, true);
+ $error = data_get($error, 'error', 'Something is not okay, are you okay?');
+ if ($error == 'Unauthorized') {
+ $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
+ }
+ throw new \Exception($error);
+ }
+ $metrics = str($metrics)->explode("\n")->skip(1)->all();
+ $parsedCollection = collect($metrics)->flatMap(function ($item) {
+ return collect(explode("\n", trim($item)))->map(function ($line) {
+ [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
+ $cpu_usage_percent = number_format($cpu_usage_percent, 2);
+
+ return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
+ });
+ });
+
+ return $parsedCollection->toArray();
+ }
+ }
}
diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php
index a55f16a83..86c1e6e69 100644
--- a/app/Notifications/Container/ContainerRestarted.php
+++ b/app/Notifications/Container/ContainerRestarted.php
@@ -14,9 +14,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public string $name, public Server $server, public ?string $url = null)
- {
- }
+ public function __construct(public string $name, public Server $server, public ?string $url = null) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php
index d9dc57b98..75b4872cb 100644
--- a/app/Notifications/Container/ContainerStopped.php
+++ b/app/Notifications/Container/ContainerStopped.php
@@ -14,9 +14,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public string $name, public Server $server, public ?string $url = null)
- {
- }
+ public function __construct(public string $name, public Server $server, public ?string $url = null) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Database/DailyBackup.php b/app/Notifications/Database/DailyBackup.php
index c74676eb7..90abee8a6 100644
--- a/app/Notifications/Database/DailyBackup.php
+++ b/app/Notifications/Database/DailyBackup.php
@@ -16,9 +16,7 @@ class DailyBackup extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public $databases)
- {
- }
+ public function __construct(public $databases) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php
index 6acd770f6..1d4d648c8 100644
--- a/app/Notifications/Internal/GeneralNotification.php
+++ b/app/Notifications/Internal/GeneralNotification.php
@@ -14,9 +14,7 @@ class GeneralNotification extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public string $message)
- {
- }
+ public function __construct(public string $message) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php
index 0e445f035..f8195ec1d 100644
--- a/app/Notifications/Server/DockerCleanup.php
+++ b/app/Notifications/Server/DockerCleanup.php
@@ -15,9 +15,7 @@ class DockerCleanup extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server, public string $message)
- {
- }
+ public function __construct(public Server $server, public string $message) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php
index 960a7c79f..9a76558e2 100644
--- a/app/Notifications/Server/ForceDisabled.php
+++ b/app/Notifications/Server/ForceDisabled.php
@@ -17,9 +17,7 @@ class ForceDisabled extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php
index 6a4b5d74b..a43e30376 100644
--- a/app/Notifications/Server/ForceEnabled.php
+++ b/app/Notifications/Server/ForceEnabled.php
@@ -17,9 +17,7 @@ class ForceEnabled extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server)
- {
- }
+ public function __construct(public Server $server) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php
index 5f63ef8f1..a6e248170 100644
--- a/app/Notifications/Server/HighDiskUsage.php
+++ b/app/Notifications/Server/HighDiskUsage.php
@@ -17,9 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage)
- {
- }
+ public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php
index e7d3baf3e..8eaadf359 100644
--- a/app/Notifications/Server/Revived.php
+++ b/app/Notifications/Server/Revived.php
@@ -24,7 +24,7 @@ class Revived extends Notification implements ShouldQueue
if ($this->server->unreachable_notification_sent === false) {
return;
}
- GetContainersStatus::dispatch($server);
+ GetContainersStatus::dispatch($server)->onQueue('high');
// dispatch(new ContainerStatusJob($server));
}
diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php
index 2dcfe28b8..ebbd6af77 100644
--- a/app/Notifications/Server/Unreachable.php
+++ b/app/Notifications/Server/Unreachable.php
@@ -17,10 +17,7 @@ class Unreachable extends Notification implements ShouldQueue
public $tries = 1;
- public function __construct(public Server $server)
- {
-
- }
+ public function __construct(public Server $server) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php
index 925859aba..f873a95d3 100644
--- a/app/Notifications/Test.php
+++ b/app/Notifications/Test.php
@@ -13,9 +13,7 @@ class Test extends Notification implements ShouldQueue
public $tries = 5;
- public function __construct(public ?string $emails = null)
- {
- }
+ public function __construct(public ?string $emails = null) {}
public function via(object $notifiable): array
{
diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php
index a251b47ea..49d2ad487 100644
--- a/app/Notifications/TransactionalEmails/InvitationLink.php
+++ b/app/Notifications/TransactionalEmails/InvitationLink.php
@@ -22,9 +22,7 @@ class InvitationLink extends Notification implements ShouldQueue
return [TransactionalEmailChannel::class];
}
- public function __construct(public User $user)
- {
- }
+ public function __construct(public User $user) {}
public function toMail(): MailMessage
{
diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php
index ed30c1883..a417e1ee5 100644
--- a/app/Notifications/TransactionalEmails/Test.php
+++ b/app/Notifications/TransactionalEmails/Test.php
@@ -14,9 +14,7 @@ class Test extends Notification implements ShouldQueue
public $tries = 5;
- public function __construct(public string $emails)
- {
- }
+ public function __construct(public string $emails) {}
public function via(): array
{
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 1bce22c12..6822dec13 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -9,9 +9,7 @@ use Laravel\Sanctum\Sanctum;
class AppServiceProvider extends ServiceProvider
{
- public function register(): void
- {
- }
+ public function register(): void {}
public function boot(): void
{
diff --git a/app/View/Components/ApexCharts.php b/app/View/Components/ApexCharts.php
new file mode 100644
index 000000000..6b86055d9
--- /dev/null
+++ b/app/View/Components/ApexCharts.php
@@ -0,0 +1,34 @@
+chartId = $chartId;
+ $this->seriesData = $seriesData;
+ $this->categories = $categories;
+ $this->seriesName = $seriesName ?? 'Series';
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ */
+ public function render(): View|Closure|string
+ {
+ return view('components.apex-charts');
+ }
+}
diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php
index 36c07dae1..35448d5e5 100644
--- a/app/View/Components/Forms/Input.php
+++ b/app/View/Components/Forms/Input.php
@@ -22,8 +22,7 @@ class Input extends Component
public bool $allowToPeak = true,
public bool $isMultiline = false,
public string $defaultClass = 'input',
- ) {
- }
+ ) {}
public function render(): View|Closure|string
{
diff --git a/app/View/Components/ResourceView.php b/app/View/Components/ResourceView.php
index 5a11b159d..d1107465b 100644
--- a/app/View/Components/ResourceView.php
+++ b/app/View/Components/ResourceView.php
@@ -16,9 +16,7 @@ class ResourceView extends Component
public ?string $logo = null,
public ?string $documentation = null,
public bool $upgrade = false,
- ) {
-
- }
+ ) {}
/**
* Get the view / contents that represent the component.
diff --git a/app/View/Components/Status/Index.php b/app/View/Components/Status/Index.php
index f8436a102..ada9eb682 100644
--- a/app/View/Components/Status/Index.php
+++ b/app/View/Components/Status/Index.php
@@ -14,8 +14,7 @@ class Index extends Component
public function __construct(
public $resource = null,
public bool $showRefreshButton = true,
- ) {
- }
+ ) {}
/**
* Get the view / contents that represent the component.
diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php
index 999de45c2..c278a5045 100644
--- a/bootstrap/helpers/api.php
+++ b/bootstrap/helpers/api.php
@@ -1,5 +1,7 @@
user()->currentAccessToken();
@@ -10,3 +12,27 @@ function invalid_token()
{
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
}
+
+function serialize_api_response($data)
+{
+ if (! $data instanceof Collection) {
+ $data = collect($data);
+ }
+ $data = $data->sortKeys();
+ $created_at = data_get($data, 'created_at');
+ $updated_at = data_get($data, 'updated_at');
+ if ($created_at) {
+ unset($data['created_at']);
+ $data['created_at'] = $created_at;
+
+ }
+ if ($updated_at) {
+ unset($data['updated_at']);
+ $data['updated_at'] = $updated_at;
+ }
+ if (data_get($data, 'id')) {
+ $data = $data->prepend($data['id'], 'id');
+ }
+
+ return $data;
+}
diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php
index 376b0f2aa..df891b824 100644
--- a/bootstrap/helpers/applications.php
+++ b/bootstrap/helpers/applications.php
@@ -8,7 +8,7 @@ use App\Models\Server;
use App\Models\StandaloneDocker;
use Spatie\Url\Url;
-function queue_application_deployment(Application $application, string $deployment_uuid, ?int $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)
+function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = 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)
{
$application_id = $application->id;
$deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}");
@@ -35,6 +35,7 @@ function queue_application_deployment(Application $application, string $deployme
'pull_request_id' => $pull_request_id,
'force_rebuild' => $force_rebuild,
'is_webhook' => $is_webhook,
+ 'is_api' => $is_api,
'restart_only' => $restart_only,
'commit' => $commit,
'rollback' => $rollback,
@@ -65,7 +66,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
function queue_next_deployment(Application $application)
{
$server_id = $application->destination->server_id;
- $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();
+ $next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at')->first();
if ($next_found) {
$next_found->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
@@ -79,7 +80,7 @@ function queue_next_deployment(Application $application)
function next_queuable(string $server_id, string $application_id): bool
{
- $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
+ $deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at');
$same_application_deployments = $deployments->where('application_id', $application_id);
$in_progress = $same_application_deployments->filter(function ($value, $key) {
return $value->status === 'in_progress';
@@ -98,3 +99,26 @@ function next_queuable(string $server_id, string $application_id): bool
return true;
}
+function next_after_cancel(?Server $server = null)
+{
+ if ($server) {
+ $next_found = ApplicationDeploymentQueue::where('server_id', data_get($server, 'id'))->where('status', ApplicationDeploymentStatus::QUEUED)->get()->sortBy('created_at');
+ if ($next_found->count() > 0) {
+ foreach ($next_found as $next) {
+ $server = Server::find($next->server_id);
+ $concurrent_builds = $server->settings->concurrent_builds;
+ $inprogress_deployments = ApplicationDeploymentQueue::where('server_id', $next->server_id)->whereIn('status', [ApplicationDeploymentStatus::QUEUED])->get()->sortByDesc('created_at');
+ if ($inprogress_deployments->count() < $concurrent_builds) {
+ $next->update([
+ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
+ ]);
+
+ dispatch(new ApplicationDeploymentJob(
+ application_deployment_queue_id: $next->id,
+ ));
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 7994c10af..aef362491 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -79,6 +79,10 @@ function backup_dir(): string
{
return base_configuration_dir().'/backups';
}
+function metrics_dir(): string
+{
+ return base_configuration_dir().'/metrics';
+}
function generate_readme_file(string $name, string $updated_at): string
{
@@ -158,10 +162,10 @@ function get_route_parameters(): array
function get_latest_sentinel_version(): string
{
try {
- $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
+ $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json');
$versions = $response->json();
- return data_get($versions, 'coolify.sentinel.version');
+ return data_get($versions, 'sentinel.version');
} catch (\Throwable $e) {
//throw $e;
ray($e->getMessage());
@@ -1250,6 +1254,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
]);
}
}
+ $envs_from_coolify = $resource->environment_variables()->get();
+ $serviceVariables = $serviceVariables->map(function ($variable) use ($envs_from_coolify) {
+ $env_variable_key = str($variable)->before('=');
+ $env_variable_value = str($variable)->after('=');
+ $found_env = $envs_from_coolify->where('key', $env_variable_key)->first();
+ if ($found_env) {
+ $env_variable_value = $found_env->value;
+ }
+
+ return "$env_variable_key=$env_variable_value";
+ });
+
}
// Add labels to the service
if ($savedService->serviceType()) {
@@ -1314,19 +1330,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_forget($service, 'volumes.*.isDirectory');
data_forget($service, 'volumes.*.is_directory');
data_forget($service, 'exclude_from_hc');
-
- // Remove unnecessary variables from service.environment
- // $withoutServiceEnvs = collect([]);
- // collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
- // ray($key, $value);
- // if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
- // $k = Str::of($value)->before("=");
- // $v = Str::of($value)->after("=");
- // $withoutServiceEnvs->put($k->value(), $v->value());
- // }
- // });
- // ray($withoutServiceEnvs);
- // data_set($service, 'environment', $withoutServiceEnvs->toArray());
+ data_set($service, 'environment', $serviceVariables->toArray());
updateCompose($savedService);
return $service;
@@ -2282,3 +2286,10 @@ function isAnyDeploymentInprogress()
echo "No deployments in progress.\n";
exit(0);
}
+
+function generateSentinelToken()
+{
+ $token = Str::random(64);
+
+ return $token;
+}
diff --git a/config/constants.php b/config/constants.php
index 444d144a8..861b645ed 100644
--- a/config/constants.php
+++ b/config/constants.php
@@ -22,8 +22,8 @@ return [
],
'services' => [
// Temporary disabled until cache is implemented
- 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
- // 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
+ // 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
+ 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
],
'limits' => [
'trial_period' => 0,
diff --git a/config/coolify.php b/config/coolify.php
index c7cfe6101..a6d6d8581 100644
--- a/config/coolify.php
+++ b/config/coolify.php
@@ -14,5 +14,4 @@ return [
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
- 'is_sentinel_enabled' => env('SENTINEL_ENABLED', false),
];
diff --git a/config/horizon.php b/config/horizon.php
index ef7df3f1b..939d74883 100644
--- a/config/horizon.php
+++ b/config/horizon.php
@@ -182,7 +182,7 @@ return [
'defaults' => [
's6' => [
'connection' => 'redis',
- 'queue' => ['default'],
+ 'queue' => ['high', 'default'],
'balance' => env('HORIZON_BALANCE', 'auto'),
'maxTime' => 0,
'maxJobs' => 0,
diff --git a/config/sentry.php b/config/sentry.php
index 33a24edfb..caa659921 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.297',
+ 'release' => '4.0.0-beta.298',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/session.php b/config/session.php
index c7b176a5a..44ca7ded9 100644
--- a/config/session.php
+++ b/config/session.php
@@ -18,7 +18,7 @@ return [
|
*/
- 'driver' => env('SESSION_DRIVER', 'redis'),
+ 'driver' => env('SESSION_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
diff --git a/config/version.php b/config/version.php
index 06c1e6c66..ddcd3f2d4 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
dropColumn('is_metrics_enabled');
+ });
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->boolean('is_metrics_enabled')->default(false);
+ $table->integer('metrics_refresh_rate_seconds')->default(5);
+ $table->integer('metrics_history_days')->default(30);
+ $table->string('metrics_token')->default(generateSentinelToken());
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('servers', function (Blueprint $table) {
+ $table->boolean('is_metrics_enabled')->default(true);
+ });
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->dropColumn('is_metrics_enabled');
+ $table->dropColumn('metrics_refresh_rate_seconds');
+ $table->dropColumn('metrics_history_days');
+ $table->dropColumn('metrics_token');
+ });
+ }
+};
diff --git a/database/migrations/2024_06_20_102551_add_server_api_sentinel.php b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php
new file mode 100644
index 000000000..b840195af
--- /dev/null
+++ b/database/migrations/2024_06_20_102551_add_server_api_sentinel.php
@@ -0,0 +1,28 @@
+boolean('is_server_api_enabled')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('server_settings', function (Blueprint $table) {
+ $table->dropColumn('is_server_api_enabled');
+ });
+ }
+};
diff --git a/database/migrations/2024_06_21_143358_add_api_deployment_type.php b/database/migrations/2024_06_21_143358_add_api_deployment_type.php
new file mode 100644
index 000000000..03f4d4796
--- /dev/null
+++ b/database/migrations/2024_06_21_143358_add_api_deployment_type.php
@@ -0,0 +1,28 @@
+boolean('is_api')->default(false);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('application_deployment_queues', function (Blueprint $table) {
+ $table->dropColumn('is_api');
+ });
+ }
+};
diff --git a/database/seeders/EnvironmentSeeder.php b/database/seeders/EnvironmentSeeder.php
index 0e980f22b..1c6d562a9 100644
--- a/database/seeders/EnvironmentSeeder.php
+++ b/database/seeders/EnvironmentSeeder.php
@@ -9,7 +9,5 @@ class EnvironmentSeeder extends Seeder
/**
* Run the database seeds.
*/
- public function run(): void
- {
- }
+ public function run(): void {}
}
diff --git a/lang/ar.json b/lang/ar.json
new file mode 100644
index 000000000..c5ec96c8d
--- /dev/null
+++ b/lang/ar.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "تسجيل الدخول",
+ "auth.login.azure": "تسجيل الدخول باستخدام Microsoft",
+ "auth.login.bitbucket": "تسجيل الدخول باستخدام Bitbucket",
+ "auth.login.github": "تسجيل الدخول باستخدام GitHub",
+ "auth.login.gitlab": "تسجيل الدخول باستخدام Gitlab",
+ "auth.login.google": "تسجيل الدخول باستخدام Google",
+ "auth.already_registered": "هل سبق لك التسجيل؟",
+ "auth.confirm_password": "تأكيد كلمة المرور",
+ "auth.forgot_password": "نسيت كلمة المرور",
+ "auth.forgot_password_send_email": "إرسال بريد إلكتروني لإعادة تعيين كلمة المرور",
+ "auth.register_now": "تسجيل",
+ "auth.logout": "تسجيل الخروج",
+ "auth.register": "تسجيل",
+ "auth.registration_disabled": "تم تعطيل التسجيل. يرجى التواصل مع المسؤول.",
+ "auth.reset_password": "إعادة تعيين كلمة المرور",
+ "auth.failed": "هذه البيانات لا تتطابق مع سجلاتنا.",
+ "auth.failed.callback": "فشل في معالجة استدعاء من مزود تسجيل الدخول.",
+ "auth.failed.password": "كلمة المرور المقدمة غير صحيحة.",
+ "auth.failed.email": "لا يمكننا العثور على مستخدم بهذا البريد الإلكتروني.",
+ "auth.throttle": "عدد محاولات تسجيل الدخول كثيرة جدًا. يرجى المحاولة مرة أخرى في :seconds ثانية.",
+ "input.name": "الاسم",
+ "input.email": "البريد الإلكتروني",
+ "input.password": "كلمة المرور",
+ "input.password.again": "كلمة المرور مرة أخرى",
+ "input.code": "الرمز لمرة واحدة",
+ "input.recovery_code": "رمز الاسترداد",
+ "button.save": "حفظ",
+ "repository.url": "أمثلة للمستودعات العامة، استخدم https://.... للمستودعات الخاصة، استخدم git@....
سيتم تحديد الفرع main لـ https://github.com/coollabsio/coolify-examples سيتم تحديد الفرع nodejs-fastify لـ https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify سيتم تحديد الفرع main لـ https://gitea.com/sedlav/expressjs.git سيتم تحديد الفرع main لـ https://gitlab.com/andrasbacsai/nodejs-example.git."
+}
diff --git a/lang/de.json b/lang/de.json
new file mode 100644
index 000000000..29fec629f
--- /dev/null
+++ b/lang/de.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Anmelden",
+ "auth.login.azure": "Mit Microsoft anmelden",
+ "auth.login.bitbucket": "Mit Bitbucket anmelden",
+ "auth.login.github": "Mit GitHub anmelden",
+ "auth.login.gitlab": "Mit GitLab anmelden",
+ "auth.login.google": "Mit Google anmelden",
+ "auth.already_registered": "Bereits registriert?",
+ "auth.confirm_password": "Passwort bestätigen",
+ "auth.forgot_password": "Passwort vergessen",
+ "auth.forgot_password_send_email": "Passwort zurücksetzen E-Mail senden",
+ "auth.register_now": "Registrieren",
+ "auth.logout": "Abmelden",
+ "auth.register": "Registrieren",
+ "auth.registration_disabled": "Registrierung ist deaktiviert. Bitte kontaktiere einen Administrator.",
+ "auth.reset_password": "Passwort zurücksetzen",
+ "auth.failed": "Diese Anmeldedaten wurden nicht gefunden.",
+ "auth.failed.callback": "Fehlerhafte Verarbeitung der Antwort des Anmeldeanbieters.",
+ "auth.failed.password": "Das angegebene Passwort ist inkorrekt.",
+ "auth.failed.email": "Wir können keinen Benutzer mit dieser E-Mail Adresse finden.",
+ "auth.throttle": "Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.",
+ "input.name": "Name",
+ "input.email": "E-Mail",
+ "input.password": "Passwort",
+ "input.password.again": "Passwort wiederholen",
+ "input.code": "Einmalcode",
+ "input.recovery_code": "Wiederherstellungscode",
+ "button.save": "Speichern",
+ "repository.url": "Beispiele Für öffentliche Repositories benutze https://.... Für private Repositories benutze git@....
https://github.com/coollabsio/coolify-examples main Branch wird ausgewählt https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify Branch wird ausgewählt. https://gitea.com/sedlav/expressjs.git main Branch wird ausgewählt. https://gitlab.com/andrasbacsai/nodejs-example.git main Branch wird ausgewählt."
+}
diff --git a/lang/es.json b/lang/es.json
new file mode 100644
index 000000000..0d8c0c940
--- /dev/null
+++ b/lang/es.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Iniciar Sesión",
+ "auth.login.azure": "Acceder con Microsoft",
+ "auth.login.bitbucket": "Acceder con Bitbucket",
+ "auth.login.github": "Acceder con GitHub",
+ "auth.login.gitlab": "Acceder con Gitlab",
+ "auth.login.google": "Acceder con Google",
+ "auth.already_registered": "¿Ya estás registrado?",
+ "auth.confirm_password": "Confirmar contraseña",
+ "auth.forgot_password": "¿Olvidaste tu contraseña?",
+ "auth.forgot_password_send_email": "Enviar correo de recuperación de contraseña",
+ "auth.register_now": "Registrar",
+ "auth.logout": "Cerrar sesión",
+ "auth.register": "Registrar",
+ "auth.registration_disabled": "El registro está desactivado. Por favor contacta con el administrador.",
+ "auth.reset_password": "Cambiar contraseña",
+ "auth.failed": "Las credenciales no coinciden con nuestro registro..",
+ "auth.failed.callback": "Falló el proceso de inicio de sesión con el proveedor.",
+ "auth.failed.password": "La contraseña es incorrecta.",
+ "auth.failed.email": "No encontramos un usuario con ese correo.",
+ "auth.throttle": "Demasiados intentos. Por favor inténtalo en :seconds segundos.",
+ "input.name": "Nombre",
+ "input.email": "Correo",
+ "input.password": "Contraseña",
+ "input.password.again": "Escribe la contraseña otra vez",
+ "input.code": "Código de único uso",
+ "input.recovery_code": "Código de recuperación",
+ "button.save": "Guardar",
+ "repository.url": "Examples Para repositorios públicos, usar https://.... Para repositorios privados, usar git@....
https://github.com/coollabsio/coolify-examples main la rama 'main' será seleccionada. https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify la rama 'nodejs-fastify' será seleccionada. https://gitea.com/sedlav/expressjs.git main la rama 'main' será seleccionada. https://gitlab.com/andrasbacsai/nodejs-example.git main la rama 'main' será seleccionada."
+}
\ No newline at end of file
diff --git a/lang/fr.json b/lang/fr.json
new file mode 100644
index 000000000..ae7fa0a03
--- /dev/null
+++ b/lang/fr.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Connexion",
+ "auth.login.azure": "Connexion avec Microsoft",
+ "auth.login.bitbucket": "Connexion avec Bitbucket",
+ "auth.login.github": "Connexion avec GitHub",
+ "auth.login.gitlab": "Connexion avec Gitlab",
+ "auth.login.google": "Connexion avec Google",
+ "auth.already_registered": "Déjà enregistré ?",
+ "auth.confirm_password": "Confirmer le mot de passe",
+ "auth.forgot_password": "Mot de passe oublié",
+ "auth.forgot_password_send_email": "Envoyer l'email de réinitialisation de mot de passe",
+ "auth.register_now": "S'enregistrer",
+ "auth.logout": "Déconnexion",
+ "auth.register": "S'enregistrer",
+ "auth.registration_disabled": "L'enregistrement est désactivé. Merci de contacter l'administateur.",
+ "auth.reset_password": "Réinitialiser le mot de passe",
+ "auth.failed": "Aucune correspondance n'a été trouvé pour les informations d'identification renseignées.",
+ "auth.failed.callback": "Erreur lors du processus de retour de la plateforme de connexion.",
+ "auth.failed.password": "Le mot de passe renseigné est incorrect.",
+ "auth.failed.email": "Aucun utilisateur avec cette adresse email n'a été trouvé.",
+ "auth.throttle": "Trop de tentatives de connexion. Merci de réessayer dans :seconds secondes.",
+ "input.name": "Nom",
+ "input.email": "Email",
+ "input.password": "Mot de passe",
+ "input.password.again": "Mot de passe identique",
+ "input.code": "Code à usage unique",
+ "input.recovery_code": "Code de récupération",
+ "button.save": "Sauvegarder",
+ "repository.url": "Exemples Pour les dépôts publiques, utilisez https://.... Pour les dépôts privés, utilisez git@....
https://github.com/coollabsio/coolify-examples main sera la branche selectionnée https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify sera la branche selectionnée. https://gitea.com/sedlav/expressjs.git main sera la branche selectionnée. https://gitlab.com/andrasbacsai/nodejs-example.git main sera la branche selectionnée."
+}
diff --git a/lang/it.json b/lang/it.json
new file mode 100644
index 000000000..6e4feb9cc
--- /dev/null
+++ b/lang/it.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Accedi",
+ "auth.login.azure": "Accedi con Microsoft",
+ "auth.login.bitbucket": "Accedi con Bitbucket",
+ "auth.login.github": "Accedi con GitHub",
+ "auth.login.gitlab": "Accedi con Gitlab",
+ "auth.login.google": "Accedi con Google",
+ "auth.already_registered": "Già registrato?",
+ "auth.confirm_password": "Conferma password",
+ "auth.forgot_password": "Password dimenticata",
+ "auth.forgot_password_send_email": "Invia email per reimpostare la password",
+ "auth.register_now": "Registrati",
+ "auth.logout": "Esci",
+ "auth.register": "Registrati",
+ "auth.registration_disabled": "La registrazione è disabilitata. Si prega di contattare l'amministratore.",
+ "auth.reset_password": "Reimposta password",
+ "auth.failed": "Queste credenziali non corrispondono ai nostri record.",
+ "auth.failed.callback": "Errore durante l'elaborazione del callback dal provider di accesso.",
+ "auth.failed.password": "La password fornita non è corretta.",
+ "auth.failed.email": "Non possiamo trovare un utente con questo indirizzo email.",
+ "auth.throttle": "Troppi tentativi di accesso. Per favore riprova tra :seconds secondi.",
+ "input.name": "Nome",
+ "input.email": "Email",
+ "input.password": "Password",
+ "input.password.again": "Ripeti password",
+ "input.code": "Codice monouso",
+ "input.recovery_code": "Codice di recupero",
+ "button.save": "Salva",
+ "repository.url": "Esempi Per i repository pubblici, utilizza https://.... Per i repository privati, utilizza git@....
https://github.com/coollabsio/coolify-examples mainブランチが選択されます https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastifyブランチが選択されます。 https://gitea.com/sedlav/expressjs.git mainブランチが選択されます。 https://gitlab.com/andrasbacsai/nodejs-example.git mainブランチが選択されます。"
+}
diff --git a/lang/pt.json b/lang/pt.json
new file mode 100644
index 000000000..b5dd5c434
--- /dev/null
+++ b/lang/pt.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Entrar",
+ "auth.login.azure": "Entrar com Microsoft",
+ "auth.login.bitbucket": "Entrar com Bitbucket",
+ "auth.login.github": "Entrar com GitHub",
+ "auth.login.gitlab": "Entrar com Gitlab",
+ "auth.login.google": "Entrar com Google",
+ "auth.already_registered": "Já tem uma conta?",
+ "auth.confirm_password": "Confirmar senha",
+ "auth.forgot_password": "Esqueceu a senha?",
+ "auth.forgot_password_send_email": "Enviar e-mail de redefinição de senha",
+ "auth.register_now": "Cadastrar-se",
+ "auth.logout": "Sair",
+ "auth.register": "Cadastrar",
+ "auth.registration_disabled": "Cadastro desativado. Por favor, entre em contato com o administrador.",
+ "auth.reset_password": "Redefinir senha",
+ "auth.failed": "Essas credenciais não correspondem aos nossos registros.",
+ "auth.failed.callback": "Falha ao processar o callback do provedor de login.",
+ "auth.failed.password": "A senha fornecida está incorreta.",
+ "auth.failed.email": "Não encontramos um usuário com esse endereço de e-mail.",
+ "auth.throttle": "Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.",
+ "input.name": "Nome",
+ "input.email": "E-mail",
+ "input.password": "Senha",
+ "input.password.again": "Repetir senha",
+ "input.code": "Código único",
+ "input.recovery_code": "Código de recuperação",
+ "button.save": "Salvar",
+ "repository.url": "Exemplos Para repositórios públicos, use https://.... Para repositórios privados, use git@....
https://github.com/coollabsio/coolify-examples a branch main será selecionada https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify a branch nodejs-fastify será selecionada. https://gitea.com/sedlav/expressjs.git a branch main será selecionada. https://gitlab.com/andrasbacsai/nodejs-example.git a branch main será selecionada."
+}
diff --git a/lang/tr.json b/lang/tr.json
new file mode 100644
index 000000000..255b0d15b
--- /dev/null
+++ b/lang/tr.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Giriş",
+ "auth.login.azure": "Microsoft ile Giriş Yap",
+ "auth.login.bitbucket": "Bitbucket ile Giriş Yap",
+ "auth.login.github": "GitHub ile Giriş Yap",
+ "auth.login.gitlab": "GitLab ile Giriş Yap",
+ "auth.login.google": "Google ile Giriş Yap",
+ "auth.already_registered": "Zaten kayıtlı mısınız?",
+ "auth.confirm_password": "Şifreyi Onayla",
+ "auth.forgot_password": "Şifremi Unuttum",
+ "auth.forgot_password_send_email": "Şifre sıfırlama e-postası gönder",
+ "auth.register_now": "Kayıt Ol",
+ "auth.logout": "Çıkış Yap",
+ "auth.register": "Kayıt Ol",
+ "auth.registration_disabled": "Kayıt devre dışı bırakıldı. Lütfen yöneticiyle iletişime geçin.",
+ "auth.reset_password": "Şifreyi Sıfırla",
+ "auth.failed": "Bu kimlik bilgileri kayıtlarımızla eşleşmiyor.",
+ "auth.failed.callback": "Giriş sağlayıcıdan gelen istek işlenemedi.",
+ "auth.failed.password": "Sağlanan şifre yanlış.",
+ "auth.failed.email": "Bu e-posta adresiyle bir kullanıcı bulamıyoruz.",
+ "auth.throttle": "Çok fazla giriş denemesi. Lütfen :seconds saniye sonra tekrar deneyin.",
+ "input.name": "İsim",
+ "input.email": "E-posta",
+ "input.password": "Şifre",
+ "input.password.again": "Şifreyi Tekrar Girin",
+ "input.code": "Tek Kullanımlık Kod",
+ "input.recovery_code": "Kurtarma Kodu",
+ "button.save": "Kaydet",
+ "repository.url": "Örnekler Halka açık depolar için https://... kullanın. Özel depolar için git@... kullanın.
https://github.com/coollabsio/coolify-examples main dalı seçilecek https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek. https://gitea.com/sedlav/expressjs.git main dalı seçilecek. https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek."
+}
diff --git a/lang/vi.json b/lang/vi.json
new file mode 100644
index 000000000..548dbe8b7
--- /dev/null
+++ b/lang/vi.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "Đăng Nhập",
+ "auth.login.azure": "Đăng Nhập Bằng Microsoft",
+ "auth.login.bitbucket": "Đăng Nhập Bằng Bitbucket",
+ "auth.login.github": "Đăng Nhập Bằng GitHub",
+ "auth.login.gitlab": "Đăng Nhập Bằng Gitlab",
+ "auth.login.google": "Đăng Nhập Bằng Google",
+ "auth.already_registered": "Đã đăng ký?",
+ "auth.confirm_password": "Nhập lại mật khẩu",
+ "auth.forgot_password": "Quên mật khẩu",
+ "auth.forgot_password_send_email": "Gửi email đặt lại mật khẩu",
+ "auth.register_now": "Đăng ký ngay",
+ "auth.logout": "Đăng xuất",
+ "auth.register": "Đăng ký",
+ "auth.registration_disabled": "Đăng ký không khả dụng. Vui lòng liên hệ quản trị viên.",
+ "auth.reset_password": "Đặt lại mật khẩu",
+ "auth.failed": "Thông tin đăng nhập không khớp với bất kỳ tài khoản nào.",
+ "auth.failed.callback": "Xử lý thông tin từ nhà cung cấp đăng nhập thất bại.",
+ "auth.failed.password": "Mật khẩu bạn cung cấp không chính xác.",
+ "auth.failed.email": "Không có người dùng nào đã đăng ký với email đó.",
+ "auth.throttle": "Quá nhiều lần đăng nhập thất bại. Vui lòng thử lại sau :seconds giây.",
+ "input.name": "Tên",
+ "input.email": "Email",
+ "input.password": "Mật khẩu",
+ "input.password.again": "Mật khẩu lần nữa",
+ "input.code": "One-time code",
+ "input.recovery_code": "Mã khôi phục",
+ "button.save": "Lưu",
+ "repository.url": "Ví dụ Với repo công khai, sử dụng https://.... Với repo riêng tư, sử dụng git@....