diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7b642fdf5..1ba4d1876 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -238,7 +238,7 @@ After completing these steps, you'll have a fresh development setup.
### Contributing a New Service
To add a new service to Coolify, please refer to our documentation:
-[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service)
+[Adding a New Service](https://coolify.io/docs/get-started/contribute/service)
### Contributing to Documentation
diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php
new file mode 100644
index 000000000..a5dcc6cf4
--- /dev/null
+++ b/app/Actions/Proxy/StopProxy.php
@@ -0,0 +1,56 @@
+isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
+ $timeout = 30;
+
+ $process = $this->stopContainer($containerName, $timeout);
+
+ $startTime = Carbon::now()->getTimestamp();
+ while ($process->running()) {
+ if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
+ $this->forceStopContainer($containerName, $server);
+ break;
+ }
+ usleep(100000);
+ }
+
+ $this->removeContainer($containerName, $server);
+ } catch (\Throwable $e) {
+ return handleError($e);
+ } finally {
+ $server->proxy->force_stop = $forceStop;
+ $server->proxy->status = 'exited';
+ $server->save();
+ }
+ }
+
+ private function stopContainer(string $containerName, int $timeout): InvokedProcess
+ {
+ return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
+ }
+
+ private function forceStopContainer(string $containerName, Server $server)
+ {
+ instant_remote_process(["docker kill $containerName"], $server, throwError: false);
+ }
+
+ private function removeContainer(string $containerName, Server $server)
+ {
+ instant_remote_process(["docker rm -f $containerName"], $server, throwError: false);
+ }
+}
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index cbeb6b55d..0044e86b4 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -2881,198 +2881,198 @@ class ApplicationsController extends Controller
);
}
- #[OA\Post(
- summary: 'Execute Command',
- description: "Execute a command on the application's current container.",
- path: '/applications/{uuid}/execute',
- operationId: 'execute-command-application',
- security: [
- ['bearerAuth' => []],
- ],
- tags: ['Applications'],
- parameters: [
- new OA\Parameter(
- name: 'uuid',
- in: 'path',
- description: 'UUID of the application.',
- required: true,
- schema: new OA\Schema(
- type: 'string',
- format: 'uuid',
- )
- ),
- ],
- requestBody: new OA\RequestBody(
- required: true,
- description: 'Command to execute.',
- content: new OA\MediaType(
- mediaType: 'application/json',
- schema: new OA\Schema(
- type: 'object',
- properties: [
- 'command' => ['type' => 'string', 'description' => 'Command to execute.'],
- ],
- ),
- ),
- ),
- responses: [
- new OA\Response(
- response: 200,
- description: "Execute a command on the application's current container.",
- content: [
- new OA\MediaType(
- mediaType: 'application/json',
- schema: new OA\Schema(
- type: 'object',
- properties: [
- 'message' => ['type' => 'string', 'example' => 'Command executed.'],
- 'response' => ['type' => 'string'],
- ]
- )
- ),
- ]
- ),
- new OA\Response(
- response: 401,
- ref: '#/components/responses/401',
- ),
- new OA\Response(
- response: 400,
- ref: '#/components/responses/400',
- ),
- new OA\Response(
- response: 404,
- ref: '#/components/responses/404',
- ),
- ]
- )]
- public function execute_command_by_uuid(Request $request)
- {
- // TODO: Need to review this from security perspective, to not allow arbitrary command execution
- $allowedFields = ['command'];
- $teamId = getTeamIdFromToken();
- if (is_null($teamId)) {
- return invalidTokenResponse();
- }
- $uuid = $request->route('uuid');
- if (! $uuid) {
- return response()->json(['message' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['message' => 'Application not found.'], 404);
- }
- $return = validateIncomingRequest($request);
- if ($return instanceof \Illuminate\Http\JsonResponse) {
- return $return;
- }
- $validator = customApiValidator($request->all(), [
- 'command' => 'string|required',
- ]);
+ // #[OA\Post(
+ // summary: 'Execute Command',
+ // description: "Execute a command on the application's current container.",
+ // path: '/applications/{uuid}/execute',
+ // operationId: 'execute-command-application',
+ // security: [
+ // ['bearerAuth' => []],
+ // ],
+ // tags: ['Applications'],
+ // parameters: [
+ // new OA\Parameter(
+ // name: 'uuid',
+ // in: 'path',
+ // description: 'UUID of the application.',
+ // required: true,
+ // schema: new OA\Schema(
+ // type: 'string',
+ // format: 'uuid',
+ // )
+ // ),
+ // ],
+ // requestBody: new OA\RequestBody(
+ // required: true,
+ // description: 'Command to execute.',
+ // content: new OA\MediaType(
+ // mediaType: 'application/json',
+ // schema: new OA\Schema(
+ // type: 'object',
+ // properties: [
+ // 'command' => ['type' => 'string', 'description' => 'Command to execute.'],
+ // ],
+ // ),
+ // ),
+ // ),
+ // responses: [
+ // new OA\Response(
+ // response: 200,
+ // description: "Execute a command on the application's current container.",
+ // content: [
+ // new OA\MediaType(
+ // mediaType: 'application/json',
+ // schema: new OA\Schema(
+ // type: 'object',
+ // properties: [
+ // 'message' => ['type' => 'string', 'example' => 'Command executed.'],
+ // 'response' => ['type' => 'string'],
+ // ]
+ // )
+ // ),
+ // ]
+ // ),
+ // new OA\Response(
+ // response: 401,
+ // ref: '#/components/responses/401',
+ // ),
+ // new OA\Response(
+ // response: 400,
+ // ref: '#/components/responses/400',
+ // ),
+ // new OA\Response(
+ // response: 404,
+ // ref: '#/components/responses/404',
+ // ),
+ // ]
+ // )]
+ // public function execute_command_by_uuid(Request $request)
+ // {
+ // // TODO: Need to review this from security perspective, to not allow arbitrary command execution
+ // $allowedFields = ['command'];
+ // $teamId = getTeamIdFromToken();
+ // if (is_null($teamId)) {
+ // return invalidTokenResponse();
+ // }
+ // $uuid = $request->route('uuid');
+ // if (! $uuid) {
+ // return response()->json(['message' => 'UUID is required.'], 400);
+ // }
+ // $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ // if (! $application) {
+ // return response()->json(['message' => 'Application not found.'], 404);
+ // }
+ // $return = validateIncomingRequest($request);
+ // if ($return instanceof \Illuminate\Http\JsonResponse) {
+ // return $return;
+ // }
+ // $validator = customApiValidator($request->all(), [
+ // 'command' => 'string|required',
+ // ]);
- $extraFields = array_diff(array_keys($request->all()), $allowedFields);
- if ($validator->fails() || ! empty($extraFields)) {
- $errors = $validator->errors();
- if (! empty($extraFields)) {
- foreach ($extraFields as $field) {
- $errors->add($field, 'This field is not allowed.');
- }
- }
+ // $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ // if ($validator->fails() || ! empty($extraFields)) {
+ // $errors = $validator->errors();
+ // if (! empty($extraFields)) {
+ // foreach ($extraFields as $field) {
+ // $errors->add($field, 'This field is not allowed.');
+ // }
+ // }
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => $errors,
+ // ], 422);
+ // }
- $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
- $status = getContainerStatus($application->destination->server, $container['Names']);
+ // $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail();
+ // $status = getContainerStatus($application->destination->server, $container['Names']);
- if ($status !== 'running') {
- return response()->json([
- 'message' => 'Application is not running.',
- ], 400);
- }
+ // if ($status !== 'running') {
+ // return response()->json([
+ // 'message' => 'Application is not running.',
+ // ], 400);
+ // }
- $commands = collect([
- executeInDocker($container['Names'], $request->command),
- ]);
+ // $commands = collect([
+ // executeInDocker($container['Names'], $request->command),
+ // ]);
- $res = instant_remote_process(command: $commands, server: $application->destination->server);
+ // $res = instant_remote_process(command: $commands, server: $application->destination->server);
- return response()->json([
- 'message' => 'Command executed.',
- 'response' => $res,
- ]);
- }
+ // return response()->json([
+ // 'message' => 'Command executed.',
+ // 'response' => $res,
+ // ]);
+ // }
- private function validateDataApplications(Request $request, Server $server)
- {
- $teamId = getTeamIdFromToken();
+ // private function validateDataApplications(Request $request, Server $server)
+ // {
+ // $teamId = getTeamIdFromToken();
- // Validate ports_mappings
- if ($request->has('ports_mappings')) {
- $ports = [];
- foreach (explode(',', $request->ports_mappings) as $portMapping) {
- $port = explode(':', $portMapping);
- if (in_array($port[0], $ports)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'ports_mappings' => 'The first number before : should be unique between mappings.',
- ],
- ], 422);
- }
- $ports[] = $port[0];
- }
- }
- // Validate custom_labels
- if ($request->has('custom_labels')) {
- if (! isBase64Encoded($request->custom_labels)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'custom_labels' => 'The custom_labels should be base64 encoded.',
- ],
- ], 422);
- }
- $customLabels = base64_decode($request->custom_labels);
- if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'custom_labels' => 'The custom_labels should be base64 encoded.',
- ],
- ], 422);
- }
- }
- if ($request->has('domains') && $server->isProxyShouldRun()) {
- $uuid = $request->uuid;
- $fqdn = $request->domains;
- $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
- $fqdn = str($fqdn)->replaceStart(',', '')->trim();
- $errors = [];
- $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
- if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
- $errors[] = 'Invalid domain: '.$domain;
- }
+ // // Validate ports_mappings
+ // if ($request->has('ports_mappings')) {
+ // $ports = [];
+ // foreach (explode(',', $request->ports_mappings) as $portMapping) {
+ // $port = explode(':', $portMapping);
+ // if (in_array($port[0], $ports)) {
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => [
+ // 'ports_mappings' => 'The first number before : should be unique between mappings.',
+ // ],
+ // ], 422);
+ // }
+ // $ports[] = $port[0];
+ // }
+ // }
+ // // Validate custom_labels
+ // if ($request->has('custom_labels')) {
+ // if (! isBase64Encoded($request->custom_labels)) {
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => [
+ // 'custom_labels' => 'The custom_labels should be base64 encoded.',
+ // ],
+ // ], 422);
+ // }
+ // $customLabels = base64_decode($request->custom_labels);
+ // if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => [
+ // 'custom_labels' => 'The custom_labels should be base64 encoded.',
+ // ],
+ // ], 422);
+ // }
+ // }
+ // if ($request->has('domains') && $server->isProxyShouldRun()) {
+ // $uuid = $request->uuid;
+ // $fqdn = $request->domains;
+ // $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
+ // $fqdn = str($fqdn)->replaceStart(',', '')->trim();
+ // $errors = [];
+ // $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
+ // if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
+ // $errors[] = 'Invalid domain: '.$domain;
+ // }
- return str($domain)->trim()->lower();
- });
- if (count($errors) > 0) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
- if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'domains' => 'One of the domain is already used.',
- ],
- ], 422);
- }
- }
- }
+ // return str($domain)->trim()->lower();
+ // });
+ // if (count($errors) > 0) {
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => $errors,
+ // ], 422);
+ // }
+ // if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
+ // return response()->json([
+ // 'message' => 'Validation failed.',
+ // 'errors' => [
+ // 'domains' => 'One of the domain is already used.',
+ // ],
+ // ], 422);
+ // }
+ // }
+ // }
}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index b72d2ade3..5dbdbf215 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -329,13 +329,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} else {
$this->write_deployment_configurations();
}
- $this->execute_remote_command(
- [
- "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
- 'hidden' => true,
- 'ignore_errors' => true,
- ]
- );
+ $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
+ $this->graceful_shutdown_container($this->deployment_uuid);
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
@@ -1366,13 +1361,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
- $this->execute_remote_command(
- [
- 'command' => "docker rm -f {$this->deployment_uuid}",
- 'ignore_errors' => true,
- 'hidden' => true,
- ]
- );
+ $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}");
+ $this->graceful_shutdown_container($this->deployment_uuid);
$this->execute_remote_command(
[
$runCommand,
diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php
new file mode 100644
index 000000000..7fc716f70
--- /dev/null
+++ b/app/Jobs/RestartProxyJob.php
@@ -0,0 +1,46 @@
+server->uuid))->dontRelease()];
+ }
+
+ public function __construct(public Server $server) {}
+
+ public function handle()
+ {
+ try {
+ StopProxy::run($this->server);
+
+ $this->server->proxy->force_stop = false;
+ $this->server->save();
+ StartProxy::run($this->server, force: true);
+
+ CheckProxy::run($this->server, true);
+ } catch (\Throwable $e) {
+ return handleError($e);
+ }
+ }
+}
diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php
index 4a7e4124e..48eede4e5 100644
--- a/app/Livewire/Server/Proxy/Deploy.php
+++ b/app/Livewire/Server/Proxy/Deploy.php
@@ -4,11 +4,10 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy;
+use App\Actions\Proxy\StopProxy;
use App\Events\ProxyStatusChanged;
+use App\Jobs\RestartProxyJob;
use App\Models\Server;
-use Carbon\Carbon;
-use Illuminate\Process\InvokedProcess;
-use Illuminate\Support\Facades\Process;
use Livewire\Component;
class Deploy extends Component
@@ -65,6 +64,7 @@ class Deploy extends Component
public function restart()
{
try {
+ RestartProxyJob::dispatch($this->server);
$this->dispatch('checkProxy');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -97,43 +97,10 @@ class Deploy extends Component
public function stop(bool $forceStop = true)
{
try {
- $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
- $timeout = 30;
-
- $process = $this->stopContainer($containerName, $timeout);
-
- $startTime = Carbon::now()->getTimestamp();
- while ($process->running()) {
- if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
- $this->forceStopContainer($containerName);
- break;
- }
- usleep(100000);
- }
-
- $this->removeContainer($containerName);
+ StopProxy::run($this->server, $forceStop);
+ $this->dispatch('proxyStatusUpdated');
} catch (\Throwable $e) {
return handleError($e, $this);
- } finally {
- $this->server->proxy->force_stop = $forceStop;
- $this->server->proxy->status = 'exited';
- $this->server->save();
- $this->dispatch('proxyStatusUpdated');
}
}
-
- private function stopContainer(string $containerName, int $timeout): InvokedProcess
- {
- return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
- }
-
- private function forceStopContainer(string $containerName)
- {
- instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
- }
-
- private function removeContainer(string $containerName)
- {
- instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
- }
}
diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php
index 1b0599ffe..bb5ed0aa8 100644
--- a/app/Livewire/SettingsBackup.php
+++ b/app/Livewire/SettingsBackup.php
@@ -15,6 +15,8 @@ class SettingsBackup extends Component
{
public InstanceSettings $settings;
+ public Server $server;
+
public ?StandalonePostgresql $database = null;
public ScheduledDatabaseBackup|null|array $backup = [];
@@ -46,6 +48,7 @@ class SettingsBackup extends Component
return redirect()->route('dashboard');
} else {
$settings = instanceSettings();
+ $this->server = Server::findOrFail(0);
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($this->database) {
@@ -60,6 +63,10 @@ class SettingsBackup extends Component
$this->database->save();
}
$this->backup = $this->database->scheduledBackups->first();
+ if ($this->backup && ! $this->server->isFunctional()) {
+ $this->backup->enabled = false;
+ $this->backup->save();
+ }
$this->executions = $this->backup->executions;
}
$this->settings = $settings;
diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php
index 858f3b187..4426c7812 100644
--- a/app/Livewire/SettingsEmail.php
+++ b/app/Livewire/SettingsEmail.php
@@ -4,7 +4,7 @@ namespace App\Livewire;
use App\Models\InstanceSettings;
use App\Models\Team;
-use App\Notifications\Test;
+use App\Notifications\TransactionalEmails\Test;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php
index a452ef8dd..8a9a95107 100644
--- a/app/Notifications/Channels/EmailChannel.php
+++ b/app/Notifications/Channels/EmailChannel.php
@@ -12,8 +12,9 @@ class EmailChannel
public function send(SendsEmail $notifiable, Notification $notification): void
{
$useInstanceEmailSettings = $notifiable->emailNotificationSettings->use_instance_email_settings;
+ $isTransactionalEmail = data_get($notification, 'isTransactionalEmail', false);
$customEmails = data_get($notification, 'emails', null);
- if ($useInstanceEmailSettings) {
+ if ($useInstanceEmailSettings || $isTransactionalEmail) {
$settings = instanceSettings();
} else {
$settings = $notifiable->emailNotificationSettings;
@@ -49,8 +50,8 @@ class EmailChannel
$settings->smtp_port,
$encryption
);
- $transport->setUsername($settings->smtp_username);
- $transport->setPassword($settings->smtp_password);
+ $transport->setUsername($settings->smtp_username ?? '');
+ $transport->setPassword($settings->smtp_password ?? '');
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php
index 30ace99dc..fce3eb948 100644
--- a/app/Notifications/TransactionalEmails/InvitationLink.php
+++ b/app/Notifications/TransactionalEmails/InvitationLink.php
@@ -16,9 +16,10 @@ class InvitationLink extends CustomEmailNotification
return [TransactionalEmailChannel::class];
}
- public function __construct(public User $user)
+ public function __construct(public User $user, public bool $isTransactionalEmail)
{
$this->onQueue('high');
+ $this->isTransactionalEmail = true;
}
public function toMail(): MailMessage
diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php
index 4593ddb0d..031a2d95a 100644
--- a/app/Notifications/TransactionalEmails/ResetPassword.php
+++ b/app/Notifications/TransactionalEmails/ResetPassword.php
@@ -17,10 +17,11 @@ class ResetPassword extends Notification
public InstanceSettings $settings;
- public function __construct($token)
+ public function __construct($token, public bool $isTransactionalEmail)
{
$this->settings = instanceSettings();
$this->token = $token;
+ $this->isTransactionalEmail = true;
}
public static function createUrlUsing($callback)
diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php
index eeb32a254..951c007d9 100644
--- a/app/Notifications/TransactionalEmails/Test.php
+++ b/app/Notifications/TransactionalEmails/Test.php
@@ -8,9 +8,10 @@ use Illuminate\Notifications\Messages\MailMessage;
class Test extends CustomEmailNotification
{
- public function __construct(public string $emails)
+ public function __construct(public string $emails, public string $isTransactionalEmail)
{
$this->onQueue('high');
+ $this->isTransactionalEmail = true;
}
public function via(): array
diff --git a/lang/az.json b/lang/az.json
new file mode 100644
index 000000000..973c70c2f
--- /dev/null
+++ b/lang/az.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Daxil ol",
+ "auth.login.authentik": "Authentik ilə daxil ol",
+ "auth.login.azure": "Azure ilə daxil ol",
+ "auth.login.bitbucket": "Bitbucket ilə daxil ol",
+ "auth.login.github": "Github ilə daxil ol",
+ "auth.login.gitlab": "GitLab ilə daxil ol",
+ "auth.login.google": "Google ilə daxil ol",
+ "auth.login.infomaniak": "Infomaniak ilə daxil ol",
+ "auth.already_registered": "Qeytiyatınız var?",
+ "auth.confirm_password": "Şifrəni təsdiqləyin",
+ "auth.forgot_password": "Şifrəmi unutdum",
+ "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər",
+ "auth.register_now": "Qeydiyyat",
+ "auth.logout": "Çıxış",
+ "auth.register": "Qeydiyyat",
+ "auth.registration_disabled": "Qeydiyyat bağlıdır. Administratorla əlaqə saxlayın.",
+ "auth.reset_password": "Şifrənin bərpası",
+ "auth.failed": "Bu məlumatlar bizim qeydlərimizlə uyğun gəlmir.",
+ "auth.failed.callback": "Giriş təminatçısından geri çağırma işlənə bilmədi.",
+ "auth.failed.password": "Daxil etdiyiniz şifrə yanlışdır.",
+ "auth.failed.email": "Bu e-poçt ünvanı ilə istifadəçi tapılmadı.",
+ "auth.throttle": "Çox sayda uğursuz giriş cəhdi. Zəhmət olmasa :seconds saniyə sonra yenidən cəhd edin.",
+ "input.name": "Ad",
+ "input.email": "E-poçt",
+ "input.password": "Şifrə",
+ "input.password.again": "Şifrəni təkrar daxil edin",
+ "input.code": "Bir dəfəlik kod",
+ "input.recovery_code": "Bərpa kodu",
+ "button.save": "Yadda saxla",
+ "repository.url": "Nümunələr
Publik repozitoriyalar üçün https://... istifadə edin.
Özəl repozitoriyalar üçün git@... istifadə edin.
https://github.com/coollabsio/coolify-examples main branch-ı seçiləcək
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch-ı seçiləcək.
https://gitea.com/sedlav/expressjs.git main branch-ı seçiləcək.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch-ı seçiləcək.",
+ "service.stop": "Bu xidmət dayandırılacaq.",
+ "resource.docker_cleanup": "Docker təmizlənməsini işə salın (istifadə olunmayan şəkillər və builder keşini silin).",
+ "resource.non_persistent": "Bütün qeyri-daimi məlumatlar silinəcək.",
+ "resource.delete_volumes": "Bu resursla əlaqəli bütün həcm məlumatları tamamilə silinəcək.",
+ "resource.delete_connected_networks": "Bu resursla əlaqəli bütün əvvəlcədən təyin olunmamış şəbəkələr tamamilə silinəcək.",
+ "resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.",
+ "database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.",
+ "warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).
Əvəzində öz domeninizdən istifadə edin."
+}
diff --git a/lang/id.json b/lang/id.json
new file mode 100644
index 000000000..d35556402
--- /dev/null
+++ b/lang/id.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Masuk",
+ "auth.login.authentik": "Masuk dengan Authentik",
+ "auth.login.azure": "Masuk dengan Microsoft",
+ "auth.login.bitbucket": "Masuk dengan Bitbucket",
+ "auth.login.github": "Masuk dengan GitHub",
+ "auth.login.gitlab": "Masuk dengan Gitlab",
+ "auth.login.google": "Masuk dengan Google",
+ "auth.login.infomaniak": "Masuk dengan Infomaniak",
+ "auth.already_registered": "Sudah terdaftar?",
+ "auth.confirm_password": "Konfirmasi kata sandi",
+ "auth.forgot_password": "Lupa kata sandi",
+ "auth.forgot_password_send_email": "Kirim email reset kata sandi",
+ "auth.register_now": "Daftar",
+ "auth.logout": "Keluar",
+ "auth.register": "Daftar",
+ "auth.registration_disabled": "Pendaftaran dinonaktifkan. Harap hubungi administrator.",
+ "auth.reset_password": "Reset kata sandi",
+ "auth.failed": "Kredensial ini tidak cocok dengan catatan kami.",
+ "auth.failed.callback": "Gagal memproses callback dari penyedia login.",
+ "auth.failed.password": "Kata sandi yang diberikan salah.",
+ "auth.failed.email": "Kami tidak dapat menemukan pengguna dengan alamat e-mail tersebut.",
+ "auth.throttle": "Terlalu banyak percobaan login. Silakan coba lagi dalam :seconds detik.",
+ "input.name": "Nama",
+ "input.email": "Email",
+ "input.password": "Kata sandi",
+ "input.password.again": "Kata sandi lagi",
+ "input.code": "Kode sekali pakai",
+ "input.recovery_code": "Kode pemulihan",
+ "button.save": "Simpan",
+ "repository.url": "Contoh
Untuk repositori Publik, gunakan https://....
Untuk repositori Privat, gunakan git@....
https://github.com/coollabsio/coolify-examples cabang main akan dipilih
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify cabang nodejs-fastify akan dipilih.
https://gitea.com/sedlav/expressjs.git cabang main akan dipilih.
https://gitlab.com/andrasbacsai/nodejs-example.git cabang main akan dipilih.",
+ "service.stop": "Layanan ini akan dihentikan.",
+ "resource.docker_cleanup": "Jalankan Pembersihan Docker (hapus gambar yang tidak digunakan dan cache builder).",
+ "resource.non_persistent": "Semua data non-persisten akan dihapus.",
+ "resource.delete_volumes": "Hapus permanen semua volume yang terkait dengan sumber daya ini.",
+ "resource.delete_connected_networks": "Hapus permanen semua jaringan non-predefined yang terkait dengan sumber daya ini.",
+ "resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.",
+ "database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.",
+ "warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).
Gunakan domain Anda sendiri sebagai gantinya."
+}
diff --git a/lang/pt-br.json b/lang/pt-br.json
new file mode 100644
index 000000000..2e793890f
--- /dev/null
+++ b/lang/pt-br.json
@@ -0,0 +1,40 @@
+{
+ "auth.login": "Entrar",
+ "auth.login.authentik": "Entrar com Authentik",
+ "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.login.infomaniak": "Entrar com Infomaniak",
+ "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 para redefinir senha",
+ "auth.register_now": "Cadastre-se",
+ "auth.logout": "Sair",
+ "auth.register": "Cadastrar",
+ "auth.registration_disabled": "O registro está desativado. Por favor, contate 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 nenhum 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": "Senha novamente",
+ "input.code": "Código de uso ú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 main branch será selecionado
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch será selecionado.
https://gitea.com/sedlav/expressjs.git main branch será selecionado.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch será selecionado.",
+ "service.stop": "Este serviço será parado.",
+ "resource.docker_cleanup": "Executar limpeza do Docker (remover imagens não utilizadas e cache de build).",
+ "resource.non_persistent": "Todos os dados não persistentes serão excluídos.",
+ "resource.delete_volumes": "Excluir permanentemente todos os volumes associados a este recurso.",
+ "resource.delete_connected_networks": "Excluir permanentemente todas as redes não predefinidas associadas a este recurso.",
+ "resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.",
+ "database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.",
+ "warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).
Use seu próprio domínio em vez disso."
+}
diff --git a/lang/tr.json b/lang/tr.json
index 3cbcee409..663c756f9 100644
--- a/lang/tr.json
+++ b/lang/tr.json
@@ -27,5 +27,13 @@
"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."
+ "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.",
+ "service.stop": "Bu servis durdurulacak.",
+ "resource.docker_cleanup": "Docker temizliği çalıştır (kullanılmayan imajları ve oluşturucu önbelleğini kaldır).",
+ "resource.non_persistent": "Tüm kalıcı olmayan veriler silinecek.",
+ "resource.delete_volumes": "Bu kaynakla ilişkili tüm hacimler kalıcı olarak silinecek.",
+ "resource.delete_connected_networks": "Bu kaynakla ilişkili önceden tanımlanmamış tüm ağlar kalıcı olarak silinecek.",
+ "resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.",
+ "database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.",
+ "warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).
Bunun yerine kendi domaininizi kullanın."
}
diff --git a/openapi.json b/openapi.json
index 98447067e..dbbc3dc24 100644
--- a/openapi.json
+++ b/openapi.json
@@ -2771,80 +2771,6 @@
]
}
},
- "\/applications\/{uuid}\/execute": {
- "post": {
- "tags": [
- "Applications"
- ],
- "summary": "Execute Command",
- "description": "Execute a command on the application's current container.",
- "operationId": "execute-command-application",
- "parameters": [
- {
- "name": "uuid",
- "in": "path",
- "description": "UUID of the application.",
- "required": true,
- "schema": {
- "type": "string",
- "format": "uuid"
- }
- }
- ],
- "requestBody": {
- "description": "Command to execute.",
- "required": true,
- "content": {
- "application\/json": {
- "schema": {
- "properties": {
- "command": {
- "type": "string",
- "description": "Command to execute."
- }
- },
- "type": "object"
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "Execute a command on the application's current container.",
- "content": {
- "application\/json": {
- "schema": {
- "properties": {
- "message": {
- "type": "string",
- "example": "Command executed."
- },
- "response": {
- "type": "string"
- }
- },
- "type": "object"
- }
- }
- }
- },
- "401": {
- "$ref": "#\/components\/responses\/401"
- },
- "400": {
- "$ref": "#\/components\/responses\/400"
- },
- "404": {
- "$ref": "#\/components\/responses\/404"
- }
- },
- "security": [
- {
- "bearerAuth": []
- }
- ]
- }
- },
"\/databases": {
"get": {
"tags": [
diff --git a/openapi.yaml b/openapi.yaml
index ba4b7193e..b8f34ef19 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -1905,52 +1905,6 @@ paths:
security:
-
bearerAuth: []
- '/applications/{uuid}/execute':
- post:
- tags:
- - Applications
- summary: 'Execute Command'
- description: "Execute a command on the application's current container."
- operationId: execute-command-application
- parameters:
- -
- name: uuid
- in: path
- description: 'UUID of the application.'
- required: true
- schema:
- type: string
- format: uuid
- requestBody:
- description: 'Command to execute.'
- required: true
- content:
- application/json:
- schema:
- properties:
- command:
- type: string
- description: 'Command to execute.'
- type: object
- responses:
- '200':
- description: "Execute a command on the application's current container."
- content:
- application/json:
- schema:
- properties:
- message: { type: string, example: 'Command executed.' }
- response: { type: string }
- type: object
- '401':
- $ref: '#/components/responses/401'
- '400':
- $ref: '#/components/responses/400'
- '404':
- $ref: '#/components/responses/404'
- security:
- -
- bearerAuth: []
/databases:
get:
tags:
diff --git a/other/nightly/versions.json b/other/nightly/versions.json
index 52d58882e..38ae7c427 100644
--- a/other/nightly/versions.json
+++ b/other/nightly/versions.json
@@ -1,10 +1,10 @@
{
"coolify": {
"v4": {
- "version": "4.0.0-beta.403"
+ "version": "4.0.0-beta.404"
},
"nightly": {
- "version": "4.0.0-beta.404"
+ "version": "4.0.0-beta.405"
},
"helper": {
"version": "1.0.8"
diff --git a/resources/views/components/forms/copy-button.blade.php b/resources/views/components/forms/copy-button.blade.php
index 39be2bc6a..12fadc595 100644
--- a/resources/views/components/forms/copy-button.blade.php
+++ b/resources/views/components/forms/copy-button.blade.php
@@ -1,8 +1,9 @@
@props(['text'])
-