Merge branch 'next' into fix_service_app_log_drain
This commit is contained in:
20
CHANGELOG.md
20
CHANGELOG.md
@@ -33,6 +33,26 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [4.0.0-beta.400] - 2025-03-27
|
## [4.0.0-beta.400] - 2025-03-27
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(database)* Disable MongoDB SSL by default in migration
|
||||||
|
|
||||||
|
### 🚜 Refactor
|
||||||
|
|
||||||
|
- *(proxy)* Improve port availability checks with multiple methods
|
||||||
|
- *(database)* Update MongoDB SSL configuration for improved security
|
||||||
|
- *(database)* Enhance SSL configuration handling for various databases
|
||||||
|
- *(notifications)* Update Telegram button URL for staging environment
|
||||||
|
- *(models)* Remove unnecessary cloud check in isEnabled method
|
||||||
|
- *(database)* Streamline event listeners in Redis General component
|
||||||
|
- *(database)* Remove redundant database status display in MongoDB view
|
||||||
|
- *(database)* Update import statements for Auth in database components
|
||||||
|
- *(database)* Require PEM key file for SSL certificate regeneration
|
||||||
|
- *(database)* Change MySQL daemon command to MariaDB daemon
|
||||||
|
|
||||||
|
## [4.0.0-beta.399] - 2025-03-25
|
||||||
|
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|
||||||
- *(database)* Disable MongoDB SSL by default in migration
|
- *(database)* Disable MongoDB SSL by default in migration
|
||||||
|
@@ -243,4 +243,4 @@ To add a new service to Coolify, please refer to our documentation:
|
|||||||
### Contributing to Documentation
|
### Contributing to Documentation
|
||||||
|
|
||||||
To contribute to the Coolify documentation, please refer to this guide:
|
To contribute to the Coolify documentation, please refer to this guide:
|
||||||
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md)
|
[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/readme.md)
|
||||||
|
@@ -57,6 +57,17 @@ class StartDragonfly
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -58,6 +58,17 @@ class StartKeydb
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -59,6 +59,17 @@ class StartMariadb
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -63,6 +63,16 @@ class StartMongodb
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -59,6 +59,17 @@ class StartMysql
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -64,6 +64,17 @@ class StartPostgresql
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -58,6 +58,17 @@ class StartRedis
|
|||||||
$server = $this->database->destination->server;
|
$server = $this->database->destination->server;
|
||||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
$this->ssl_certificate = $this->database->sslCertificates()->first();
|
||||||
|
|
||||||
if (! $this->ssl_certificate) {
|
if (! $this->ssl_certificate) {
|
||||||
|
@@ -5,12 +5,10 @@ namespace App\Console\Commands;
|
|||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev {--init} {--generate-openapi}';
|
protected $signature = 'dev {--init}';
|
||||||
|
|
||||||
protected $description = 'Helper commands for development.';
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
@@ -21,36 +19,6 @@ class Dev extends Command
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->option('generate-openapi')) {
|
|
||||||
$this->generateOpenApi();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generateOpenApi()
|
|
||||||
{
|
|
||||||
// Generate OpenAPI documentation
|
|
||||||
echo "Generating OpenAPI documentation.\n";
|
|
||||||
// https://github.com/OAI/OpenAPI-Specification/releases
|
|
||||||
$process = Process::run([
|
|
||||||
'/var/www/html/vendor/bin/openapi',
|
|
||||||
'app',
|
|
||||||
'-o',
|
|
||||||
'openapi.yaml',
|
|
||||||
'--version',
|
|
||||||
'3.1.0',
|
|
||||||
]);
|
|
||||||
$error = $process->errorOutput();
|
|
||||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
|
||||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
|
||||||
echo $error;
|
|
||||||
echo $process->output();
|
|
||||||
// Convert YAML to JSON
|
|
||||||
$yaml = file_get_contents('openapi.yaml');
|
|
||||||
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
|
|
||||||
file_put_contents('openapi.json', $json);
|
|
||||||
echo "Converted OpenAPI YAML to JSON.\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function init()
|
public function init()
|
||||||
|
@@ -4,6 +4,7 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class OpenApi extends Command
|
class OpenApi extends Command
|
||||||
{
|
{
|
||||||
@@ -29,5 +30,10 @@ class OpenApi extends Command
|
|||||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
echo $error;
|
echo $error;
|
||||||
echo $process->output();
|
echo $process->output();
|
||||||
|
|
||||||
|
$yaml = file_get_contents('openapi.yaml');
|
||||||
|
$json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents('openapi.json', $json);
|
||||||
|
echo "Converted OpenAPI YAML to JSON.\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
|
|||||||
use App\Actions\Database\StartDatabase;
|
use App\Actions\Database\StartDatabase;
|
||||||
use App\Actions\Service\StartService;
|
use App\Actions\Service\StartService;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
@@ -142,6 +143,7 @@ class DeployController extends Controller
|
|||||||
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||||
|
new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.', schema: new OA\Schema(type: 'integer')),
|
||||||
],
|
],
|
||||||
|
|
||||||
responses: [
|
responses: [
|
||||||
@@ -184,26 +186,32 @@ class DeployController extends Controller
|
|||||||
public function deploy(Request $request)
|
public function deploy(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
$uuids = $request->query->get('uuid');
|
$uuids = $request->query->get('uuid');
|
||||||
$tags = $request->query->get('tag');
|
$tags = $request->query->get('tag');
|
||||||
$force = $request->query->get('force') ?? false;
|
$force = $request->query->get('force') ?? false;
|
||||||
|
$pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0;
|
||||||
|
|
||||||
if ($uuids && $tags) {
|
if ($uuids && $tags) {
|
||||||
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
}
|
}
|
||||||
if (is_null($teamId)) {
|
if ($tags && $pr) {
|
||||||
return invalidTokenResponse();
|
return response()->json(['message' => 'You can only use tag or pr, not both.'], 400);
|
||||||
}
|
}
|
||||||
if ($tags) {
|
if ($tags) {
|
||||||
return $this->by_tags($tags, $teamId, $force);
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
} elseif ($uuids) {
|
} elseif ($uuids) {
|
||||||
return $this->by_uuids($uuids, $teamId, $force);
|
return $this->by_uuids($uuids, $teamId, $force, $pr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0)
|
||||||
{
|
{
|
||||||
$uuids = explode(',', $uuid);
|
$uuids = explode(',', $uuid);
|
||||||
$uuids = collect(array_filter($uuids));
|
$uuids = collect(array_filter($uuids));
|
||||||
@@ -216,7 +224,7 @@ class DeployController extends Controller
|
|||||||
foreach ($uuids as $uuid) {
|
foreach ($uuids as $uuid) {
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
if ($resource) {
|
if ($resource) {
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force, $pr);
|
||||||
if ($deployment_uuid) {
|
if ($deployment_uuid) {
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
} else {
|
} else {
|
||||||
@@ -281,7 +289,7 @@ class DeployController extends Controller
|
|||||||
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deploy_resource($resource, bool $force = false): array
|
public function deploy_resource($resource, bool $force = false, int $pr = 0): array
|
||||||
{
|
{
|
||||||
$message = null;
|
$message = null;
|
||||||
$deployment_uuid = null;
|
$deployment_uuid = null;
|
||||||
@@ -295,6 +303,7 @@ class DeployController extends Controller
|
|||||||
application: $resource,
|
application: $resource,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: $force,
|
force_rebuild: $force,
|
||||||
|
pull_request_id: $pr,
|
||||||
);
|
);
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
break;
|
break;
|
||||||
@@ -314,4 +323,68 @@ class DeployController extends Controller
|
|||||||
|
|
||||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List application deployments',
|
||||||
|
description: 'List application deployments by using the app uuid',
|
||||||
|
path: '/deployments/applications/{uuid}',
|
||||||
|
operationId: 'list-deployments-by-app-uuid',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List application deployments by using the app uuid.',
|
||||||
|
content: [
|
||||||
|
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Application'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function get_application_deployments(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'skip' => ['nullable', 'integer', 'min:0'],
|
||||||
|
'take' => ['nullable', 'integer', 'min:1'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$app_uuid = $request->route('uuid', null);
|
||||||
|
$skip = $request->get('skip', 0);
|
||||||
|
$take = $request->get('take', 10);
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
|
||||||
|
if (is_null($app_uuid)) {
|
||||||
|
return response()->json(['message' => 'Application uuid is required'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $app_uuid)->first();
|
||||||
|
|
||||||
|
if (is_null($application)) {
|
||||||
|
return response()->json(['message' => 'Application not found'], 404);
|
||||||
|
}
|
||||||
|
$deployments = $application->deployments($skip, $take);
|
||||||
|
|
||||||
|
return response()->json($deployments);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2026,9 +2026,13 @@ RUN rm -f /usr/share/nginx/html/.env
|
|||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||||
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||||
|
} else {
|
||||||
|
if ($this->application->settings->is_spa) {
|
||||||
|
$nginx_config = base64_encode(defaultNginxConfiguration('spa'));
|
||||||
} else {
|
} else {
|
||||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($this->application->build_pack === 'nixpacks') {
|
if ($this->application->build_pack === 'nixpacks') {
|
||||||
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
|
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
|
||||||
@@ -2093,10 +2097,14 @@ COPY --from=$this->build_image_name /app/{$this->application->publish_directory}
|
|||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||||
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||||
|
} else {
|
||||||
|
if ($this->application->settings->is_spa) {
|
||||||
|
$nginx_config = base64_encode(defaultNginxConfiguration('spa'));
|
||||||
} else {
|
} else {
|
||||||
$nginx_config = base64_encode(defaultNginxConfiguration());
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
|
@@ -86,6 +86,7 @@ class General extends Component
|
|||||||
'application.post_deployment_command_container' => 'nullable',
|
'application.post_deployment_command_container' => 'nullable',
|
||||||
'application.custom_nginx_configuration' => 'nullable',
|
'application.custom_nginx_configuration' => 'nullable',
|
||||||
'application.settings.is_static' => 'boolean|required',
|
'application.settings.is_static' => 'boolean|required',
|
||||||
|
'application.settings.is_spa' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||||
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
|
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
|
||||||
@@ -124,6 +125,7 @@ class General extends Component
|
|||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||||
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
||||||
'application.settings.is_static' => 'Is static',
|
'application.settings.is_static' => 'Is static',
|
||||||
|
'application.settings.is_spa' => 'Is SPA',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||||
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
|
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
|
||||||
@@ -171,6 +173,9 @@ class General extends Component
|
|||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
|
if ($this->application->settings->isDirty('is_spa')) {
|
||||||
|
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
|
||||||
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
@@ -190,6 +195,7 @@ class General extends Component
|
|||||||
if ($this->application->settings->is_container_label_readonly_enabled) {
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
$this->resetDefaultLabels(false);
|
$this->resetDefaultLabels(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadComposeFile($isInit = false)
|
public function loadComposeFile($isInit = false)
|
||||||
@@ -287,9 +293,9 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateNginxConfiguration()
|
public function generateNginxConfiguration($type = 'static')
|
||||||
{
|
{
|
||||||
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
|
$this->application->custom_nginx_configuration = defaultNginxConfiguration($type);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->dispatch('success', 'Nginx configuration generated.');
|
$this->dispatch('success', 'Nginx configuration generated.');
|
||||||
}
|
}
|
||||||
|
@@ -214,10 +214,23 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
|
$server = $this->database->destination->server;
|
||||||
|
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)
|
||||||
->where('is_ca_certificate', true)
|
->where('is_ca_certificate', true)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$server->generateCaCertificate();
|
||||||
|
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $caCert) {
|
||||||
|
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SslHelper::generateSslCertificate(
|
SslHelper::generateSslCertificate(
|
||||||
commonName: $existingCert->commonName,
|
commonName: $existingCert->commonName,
|
||||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||||
|
@@ -1530,7 +1530,6 @@ class Application extends BaseModel
|
|||||||
$interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/');
|
$interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/');
|
||||||
$timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/');
|
$timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/');
|
||||||
$start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/');
|
$start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/');
|
||||||
$start_interval = str($healthcheckCommand)->match('/--start-interval=([0-9]+[a-zµ]*)/');
|
|
||||||
$retries = str($healthcheckCommand)->match('/--retries=(\d+)/');
|
$retries = str($healthcheckCommand)->match('/--retries=(\d+)/');
|
||||||
|
|
||||||
if ($interval->isNotEmpty()) {
|
if ($interval->isNotEmpty()) {
|
||||||
@@ -1542,13 +1541,10 @@ class Application extends BaseModel
|
|||||||
if ($start_period->isNotEmpty()) {
|
if ($start_period->isNotEmpty()) {
|
||||||
$this->health_check_start_period = parseDockerfileInterval($start_period);
|
$this->health_check_start_period = parseDockerfileInterval($start_period);
|
||||||
}
|
}
|
||||||
if ($start_interval->isNotEmpty()) {
|
|
||||||
$this->health_check_start_interval = parseDockerfileInterval($start_interval);
|
|
||||||
}
|
|
||||||
if ($retries->isNotEmpty()) {
|
if ($retries->isNotEmpty()) {
|
||||||
$this->health_check_retries = $retries->toInteger();
|
$this->health_check_retries = $retries->toInteger();
|
||||||
}
|
}
|
||||||
if ($interval || $timeout || $start_period || $start_interval || $retries) {
|
if ($interval || $timeout || $start_period || $retries) {
|
||||||
$this->custom_healthcheck_found = true;
|
$this->custom_healthcheck_found = true;
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,9 @@ use App\Actions\Server\InstallDocker;
|
|||||||
use App\Actions\Server\StartSentinel;
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Events\ServerReachabilityChanged;
|
use App\Events\ServerReachabilityChanged;
|
||||||
|
use App\Helpers\SslHelper;
|
||||||
use App\Jobs\CheckAndStartSentinelJob;
|
use App\Jobs\CheckAndStartSentinelJob;
|
||||||
|
use App\Jobs\RegenerateSslCertJob;
|
||||||
use App\Notifications\Server\Reachable;
|
use App\Notifications\Server\Reachable;
|
||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use App\Services\ConfigurationRepository;
|
use App\Services\ConfigurationRepository;
|
||||||
@@ -1337,4 +1339,41 @@ $schema://$host {
|
|||||||
$configRepository = app(ConfigurationRepository::class);
|
$configRepository = app(ConfigurationRepository::class);
|
||||||
$configRepository->disableSshMux();
|
$configRepository->disableSshMux();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generateCaCertificate()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ray('Generating CA certificate for server', $this->id);
|
||||||
|
SslHelper::generateSslCertificate(
|
||||||
|
commonName: 'Coolify CA Certificate',
|
||||||
|
serverId: $this->id,
|
||||||
|
isCaCertificate: true,
|
||||||
|
validityDays: 10 * 365
|
||||||
|
);
|
||||||
|
$caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first();
|
||||||
|
ray('CA certificate generated', $caCertificate);
|
||||||
|
if ($caCertificate) {
|
||||||
|
$certificateContent = $caCertificate->ssl_certificate;
|
||||||
|
$caCertPath = config('constants.coolify.base_config_path').'/ssl/';
|
||||||
|
|
||||||
|
$commands = collect([
|
||||||
|
"mkdir -p $caCertPath",
|
||||||
|
"chown -R 9999:root $caCertPath",
|
||||||
|
"chmod -R 700 $caCertPath",
|
||||||
|
"rm -rf $caCertPath/coolify-ca.crt",
|
||||||
|
"echo '{$certificateContent}' > $caCertPath/coolify-ca.crt",
|
||||||
|
"chmod 644 $caCertPath/coolify-ca.crt",
|
||||||
|
]);
|
||||||
|
|
||||||
|
instant_remote_process($commands, $this, false);
|
||||||
|
|
||||||
|
dispatch(new RegenerateSslCertJob(
|
||||||
|
server_id: $this->id,
|
||||||
|
force_regeneration: true
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4071,9 +4071,35 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla
|
|||||||
return $rateLimited;
|
return $rateLimited;
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultNginxConfiguration(): string
|
function defaultNginxConfiguration(string $type = 'static'): string
|
||||||
{
|
{
|
||||||
return 'server {
|
if ($type === 'spa') {
|
||||||
|
return <<<'NGINX'
|
||||||
|
server {
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle 404 errors
|
||||||
|
error_page 404 /404.html;
|
||||||
|
location = /404.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle server errors (50x)
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX;
|
||||||
|
} else {
|
||||||
|
return <<<'NGINX'
|
||||||
|
server {
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.htm;
|
index index.html index.htm;
|
||||||
@@ -4093,7 +4119,9 @@ function defaultNginxConfiguration(): string
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
internal;
|
internal;
|
||||||
}
|
}
|
||||||
}';
|
}
|
||||||
|
NGINX;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array
|
function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array
|
||||||
|
@@ -13,15 +13,17 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
if (DB::table('local_file_volumes')->exists()) {
|
if (DB::table('local_file_volumes')->exists()) {
|
||||||
|
// First, get all volumes and decrypt their values
|
||||||
|
$decryptedVolumes = collect();
|
||||||
|
|
||||||
DB::table('local_file_volumes')
|
DB::table('local_file_volumes')
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->chunk(100, function ($volumes) {
|
->chunk(100, function ($volumes) use (&$decryptedVolumes) {
|
||||||
foreach ($volumes as $volume) {
|
foreach ($volumes as $volume) {
|
||||||
DB::beginTransaction();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$fs_path = $volume->fs_path;
|
$fs_path = $volume->fs_path;
|
||||||
$mount_path = $volume->mount_path;
|
$mount_path = $volume->mount_path;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ($fs_path) {
|
if ($fs_path) {
|
||||||
$fs_path = Crypt::decryptString($fs_path);
|
$fs_path = Crypt::decryptString($fs_path);
|
||||||
@@ -36,18 +38,59 @@ return new class extends Migration
|
|||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::table('local_file_volumes')->where('id', $volume->id)->update([
|
$decryptedVolumes->push([
|
||||||
|
'id' => $volume->id,
|
||||||
'fs_path' => $fs_path,
|
'fs_path' => $fs_path,
|
||||||
'mount_path' => $mount_path,
|
'mount_path' => $mount_path,
|
||||||
|
'resource_id' => $volume->resource_id,
|
||||||
|
'resource_type' => $volume->resource_type,
|
||||||
]);
|
]);
|
||||||
echo "Updated volume {$volume->id}\n";
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
echo "Error encrypting local file volume fields: {$e->getMessage()}\n";
|
echo "Error decrypting volume {$volume->id}: {$e->getMessage()}\n";
|
||||||
Log::error('Error encrypting local file volume fields: '.$e->getMessage());
|
Log::error("Error decrypting volume {$volume->id}: ".$e->getMessage());
|
||||||
}
|
}
|
||||||
DB::commit();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Group by the unique constraint fields and keep only the first occurrence
|
||||||
|
$uniqueVolumes = $decryptedVolumes->groupBy(function ($volume) {
|
||||||
|
return $volume['mount_path'].'|'.$volume['resource_id'].'|'.$volume['resource_type'];
|
||||||
|
})->map(function ($group) {
|
||||||
|
return $group->first();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get IDs to delete (all except the ones we're keeping)
|
||||||
|
$idsToKeep = $uniqueVolumes->pluck('id')->toArray();
|
||||||
|
$idsToDelete = $decryptedVolumes->pluck('id')->diff($idsToKeep)->toArray();
|
||||||
|
|
||||||
|
// Delete duplicate records
|
||||||
|
if (! empty($idsToDelete)) {
|
||||||
|
// Show details of volumes being deleted
|
||||||
|
$volumesToDelete = $decryptedVolumes->whereIn('id', $idsToDelete);
|
||||||
|
echo "\nVolumes to be deleted:\n";
|
||||||
|
foreach ($volumesToDelete as $volume) {
|
||||||
|
echo "ID: {$volume['id']}, Mount Path: {$volume['mount_path']}, Resource ID: {$volume['resource_id']}, Resource Type: {$volume['resource_type']}\n";
|
||||||
|
echo "FS Path: {$volume['fs_path']}\n";
|
||||||
|
echo "-------------------\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('local_file_volumes')->whereIn('id', $idsToDelete)->delete();
|
||||||
|
echo 'Deleted '.count($idsToDelete)." duplicate volume(s)\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the remaining records with decrypted values
|
||||||
|
foreach ($uniqueVolumes as $volume) {
|
||||||
|
try {
|
||||||
|
DB::table('local_file_volumes')->where('id', $volume['id'])->update([
|
||||||
|
'fs_path' => $volume['fs_path'],
|
||||||
|
'mount_path' => $volume['mount_path'],
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "Error updating volume {$volume['id']}: {$e->getMessage()}\n";
|
||||||
|
Log::error("Error updating volume {$volume['id']}: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_spa')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_spa');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
40
lang/no.json
Normal file
40
lang/no.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"auth.login": "Logg inn",
|
||||||
|
"auth.login.authentik": "Logg inn med Authentik",
|
||||||
|
"auth.login.azure": "Logg inn med Microsoft",
|
||||||
|
"auth.login.bitbucket": "Logg inn med Bitbucket",
|
||||||
|
"auth.login.github": "Logg inn med GitHub",
|
||||||
|
"auth.login.gitlab": "Logg inn med Gitlab",
|
||||||
|
"auth.login.google": "Logg inn med Google",
|
||||||
|
"auth.login.infomaniak": "Logg inn med Infomaniak",
|
||||||
|
"auth.already_registered": "Allerede registrert?",
|
||||||
|
"auth.confirm_password": "Bekreft passord",
|
||||||
|
"auth.forgot_password": "Glemt passord",
|
||||||
|
"auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord",
|
||||||
|
"auth.register_now": "Registrer deg",
|
||||||
|
"auth.logout": "Logg ut",
|
||||||
|
"auth.register": "Registrer",
|
||||||
|
"auth.registration_disabled": "Registrering er deaktivert. Vennligst kontakt administrator.",
|
||||||
|
"auth.reset_password": "Tilbakestill passord",
|
||||||
|
"auth.failed": "Disse legitimasjonene samsvarer ikke med våre registre.",
|
||||||
|
"auth.failed.callback": "Klarte ikke å behandle tilbakekall fra innloggingsleverandør.",
|
||||||
|
"auth.failed.password": "Det oppgitte passordet er feil.",
|
||||||
|
"auth.failed.email": "Vi finner ingen bruker med den e-postadressen.",
|
||||||
|
"auth.throttle": "For mange innloggingsforsøk. Vennligst prøv igjen om :seconds sekunder.",
|
||||||
|
"input.name": "Navn",
|
||||||
|
"input.email": "E-post",
|
||||||
|
"input.password": "Passord",
|
||||||
|
"input.password.again": "Passord igjen",
|
||||||
|
"input.code": "Engangskode",
|
||||||
|
"input.recovery_code": "Gjenopprettingskode",
|
||||||
|
"button.save": "Lagre",
|
||||||
|
"repository.url": "<span class='text-helper'>Eksempler</span><br>For offentlige repositorier, bruk <span class='text-helper'>https://...</span>.<br>For private repositorier, bruk <span class='text-helper'>git@...</span>.<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> gren vil bli valgt<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> gren vil bli valgt.<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> gren vil bli valgt.<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> gren vil bli valgt.",
|
||||||
|
"service.stop": "Denne tjenesten vil bli stoppet.",
|
||||||
|
"resource.docker_cleanup": "Kjør Docker-opprydding (fjern ubrukte bilder og byggebuffer).",
|
||||||
|
"resource.non_persistent": "Alle ikke-persistente data vil bli slettet.",
|
||||||
|
"resource.delete_volumes": "Slett alle volumer tilknyttet denne ressursen permanent.",
|
||||||
|
"resource.delete_connected_networks": "Slett alle ikke-forhåndsdefinerte nettverk tilknyttet denne ressursen permanent.",
|
||||||
|
"resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.",
|
||||||
|
"database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.",
|
||||||
|
"warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er <span class='dark:text-red-500 text-red-500 font-bold'>IKKE</span> anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes). <br><br>Bruk ditt eget domene i stedet."
|
||||||
|
}
|
222
openapi.json
222
openapi.json
@@ -2105,6 +2105,70 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"\/applications\/{uuid}\/logs": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Applications"
|
||||||
|
],
|
||||||
|
"summary": "Get application logs.",
|
||||||
|
"description": "Get application logs by UUID.",
|
||||||
|
"operationId": "get-application-logs-by-uuid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"in": "path",
|
||||||
|
"description": "UUID of the application.",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lines",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Number of lines to show from the end of the logs.",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32",
|
||||||
|
"default": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Get application logs by UUID.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"logs": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"\/applications\/{uuid}\/envs": {
|
"\/applications\/{uuid}\/envs": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -4477,6 +4541,14 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pr",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -4523,6 +4595,42 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"\/deployments\/applications\/{uuid}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Deployments"
|
||||||
|
],
|
||||||
|
"summary": "List application deployments",
|
||||||
|
"description": "List application deployments by using the app uuid",
|
||||||
|
"operationId": "list-deployments-by-app-uuid",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "List application deployments by using the app uuid.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#\/components\/schemas\/Application"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"\/version": {
|
"\/version": {
|
||||||
"get": {
|
"get": {
|
||||||
"summary": "Version",
|
"summary": "Version",
|
||||||
@@ -5854,8 +5962,8 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Services"
|
"Services"
|
||||||
],
|
],
|
||||||
"summary": "Create",
|
"summary": "Create service",
|
||||||
"description": "Create a one-click service",
|
"description": "Create a one-click \/ custom service",
|
||||||
"operationId": "create-service",
|
"operationId": "create-service",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -6005,7 +6113,7 @@
|
|||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"201": {
|
||||||
"description": "Create a service.",
|
"description": "Service created successfully.",
|
||||||
"content": {
|
"content": {
|
||||||
"application\/json": {
|
"application\/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -6177,6 +6285,114 @@
|
|||||||
"bearerAuth": []
|
"bearerAuth": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"Services"
|
||||||
|
],
|
||||||
|
"summary": "Update",
|
||||||
|
"description": "Update service by UUID.",
|
||||||
|
"operationId": "update-service-by-uuid",
|
||||||
|
"requestBody": {
|
||||||
|
"description": "Service updated.",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"required": [
|
||||||
|
"server_uuid",
|
||||||
|
"project_uuid",
|
||||||
|
"environment_name",
|
||||||
|
"environment_uuid",
|
||||||
|
"docker_compose_raw"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The service name."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The service description."
|
||||||
|
},
|
||||||
|
"project_uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The project UUID."
|
||||||
|
},
|
||||||
|
"environment_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The environment name."
|
||||||
|
},
|
||||||
|
"environment_uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The environment UUID."
|
||||||
|
},
|
||||||
|
"server_uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The server UUID."
|
||||||
|
},
|
||||||
|
"destination_uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The destination UUID."
|
||||||
|
},
|
||||||
|
"instant_deploy": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "The flag to indicate if the service should be deployed instantly."
|
||||||
|
},
|
||||||
|
"connect_to_docker_network": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Connect the service to the predefined docker network."
|
||||||
|
},
|
||||||
|
"docker_compose_raw": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The Docker Compose raw content."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Service updated.",
|
||||||
|
"content": {
|
||||||
|
"application\/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"uuid": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service UUID."
|
||||||
|
},
|
||||||
|
"domains": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Service domains."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#\/components\/responses\/401"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#\/components\/responses\/400"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#\/components\/responses\/404"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"\/services\/{uuid}\/envs": {
|
"\/services\/{uuid}\/envs": {
|
||||||
|
106
openapi.yaml
106
openapi.yaml
@@ -3152,6 +3152,12 @@ paths:
|
|||||||
description: 'Force rebuild (without cache)'
|
description: 'Force rebuild (without cache)'
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
-
|
||||||
|
name: pr
|
||||||
|
in: query
|
||||||
|
description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.'
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: "Get deployment(s) UUID's"
|
description: "Get deployment(s) UUID's"
|
||||||
@@ -3168,6 +3174,29 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
'/deployments/applications/{uuid}':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Deployments
|
||||||
|
summary: 'List application deployments'
|
||||||
|
description: 'List application deployments by using the app uuid'
|
||||||
|
operationId: list-deployments-by-app-uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'List application deployments by using the app uuid.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Application'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
/version:
|
/version:
|
||||||
get:
|
get:
|
||||||
summary: Version
|
summary: Version
|
||||||
@@ -3995,80 +4024,9 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Services
|
- Services
|
||||||
summary: Create
|
summary: 'Create service'
|
||||||
description: 'Create a service'
|
description: 'Create a one-click / custom service'
|
||||||
operationId: create-service
|
operationId: create-service
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
required:
|
|
||||||
- server_uuid
|
|
||||||
- project_uuid
|
|
||||||
- environment_name
|
|
||||||
- environment_uuid
|
|
||||||
- docker_compose_raw
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
maxLength: 255
|
|
||||||
description: 'Name of the service.'
|
|
||||||
description:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
description: 'Description of the service.'
|
|
||||||
project_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'Project UUID.'
|
|
||||||
environment_name:
|
|
||||||
type: string
|
|
||||||
description: 'Environment name. You need to provide at least one of environment_name or environment_uuid.'
|
|
||||||
environment_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.'
|
|
||||||
server_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'Server UUID.'
|
|
||||||
destination_uuid:
|
|
||||||
type: string
|
|
||||||
description: 'Destination UUID. Required if server has multiple destinations.'
|
|
||||||
instant_deploy:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: 'Start the service immediately after creation.'
|
|
||||||
connect_to_docker_network:
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
description: 'The flag to connect the service to the predefined Docker network.'
|
|
||||||
docker_compose_raw:
|
|
||||||
type: string
|
|
||||||
description: 'The Docker Compose raw content.'
|
|
||||||
type: object
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
description: 'Service created successfully.'
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
uuid: { type: string, description: 'Service UUID.' }
|
|
||||||
domains: { type: array, items: { type: string, nullable: true }, description: 'Service domains.' }
|
|
||||||
type: object
|
|
||||||
'401':
|
|
||||||
$ref: '#/components/responses/401'
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/responses/400'
|
|
||||||
security:
|
|
||||||
-
|
|
||||||
bearerAuth: []
|
|
||||||
/services/one-click:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Services
|
|
||||||
summary: 'Create one-click'
|
|
||||||
description: 'Create a one-click service'
|
|
||||||
operationId: create-one-click-service
|
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@@ -4271,7 +4229,7 @@ paths:
|
|||||||
connect_to_docker_network:
|
connect_to_docker_network:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
description: 'The flag to connect the service to the predefined Docker network.'
|
description: 'Connect the service to the predefined docker network.'
|
||||||
docker_compose_raw:
|
docker_compose_raw:
|
||||||
type: string
|
type: string
|
||||||
description: 'The Docker Compose raw content.'
|
description: 'The Docker Compose raw content.'
|
||||||
|
@@ -69,6 +69,17 @@
|
|||||||
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
<x-forms.button wire:click="generateNginxConfiguration">Generate Default Nginx
|
||||||
Configuration</x-forms.button>
|
Configuration</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
<div class="w-96 pb-8">
|
||||||
|
@if ($application->could_set_build_commands())
|
||||||
|
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
|
||||||
|
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||||
|
@endif
|
||||||
|
@if ($application->settings->is_static && $application->build_pack !== 'static')
|
||||||
|
<x-forms.checkbox label="Is it a SPA (Single Page Application)?"
|
||||||
|
helper="If your application is a SPA, enable this." id="application.settings.is_spa"
|
||||||
|
instantSave></x-forms.checkbox>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
@if ($application->settings->is_container_label_readonly_enabled == false)
|
@if ($application->settings->is_container_label_readonly_enabled == false)
|
||||||
@@ -274,13 +285,6 @@
|
|||||||
label="Use a Build Server?" />
|
label="Use a Build Server?" />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($application->could_set_build_commands())
|
|
||||||
<div class="w-96">
|
|
||||||
<x-forms.checkbox instantSave id="application.settings.is_static"
|
|
||||||
label="Is it a static site?"
|
|
||||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -56,6 +56,7 @@ Route::group([
|
|||||||
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:write,deploy']);
|
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy'])->middleware(['api.ability:write,deploy']);
|
||||||
Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['api.ability:read']);
|
Route::get('/deployments', [DeployController::class, 'deployments'])->middleware(['api.ability:read']);
|
||||||
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['api.ability:read']);
|
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid'])->middleware(['api.ability:read']);
|
||||||
|
Route::get('/deployments/applications/{uuid}', [DeployController::class, 'get_application_deployments'])->middleware(['api.ability:read']);
|
||||||
|
|
||||||
Route::get('/servers', [ServersController::class, 'servers'])->middleware(['api.ability:read']);
|
Route::get('/servers', [ServersController::class, 'servers'])->middleware(['api.ability:read']);
|
||||||
Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid'])->middleware(['api.ability:read']);
|
Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid'])->middleware(['api.ability:read']);
|
||||||
|
@@ -25,16 +25,19 @@ services:
|
|||||||
- ANNOUNCE_PLAYER_ACHIEVEMENTS=${MINECRAFT_ANNOUNCE_PLAYER_ACHIEVEMENTS:-true}
|
- ANNOUNCE_PLAYER_ACHIEVEMENTS=${MINECRAFT_ANNOUNCE_PLAYER_ACHIEVEMENTS:-true}
|
||||||
- GENERATE_STRUCTURES=${MINECRAFT_GENERATE_STRUCTURES:-true}
|
- GENERATE_STRUCTURES=${MINECRAFT_GENERATE_STRUCTURES:-true}
|
||||||
- PVP=${MINECRAFT_PVP:-true}
|
- PVP=${MINECRAFT_PVP:-true}
|
||||||
|
- MODE=${MINECRAFT_GAME_MODE:-survival}
|
||||||
- FORCE_GAMEMODE=${MINECRAFT_FORCE_GAMEMODE:-false}
|
- FORCE_GAMEMODE=${MINECRAFT_FORCE_GAMEMODE:-false}
|
||||||
- HARDCORE=${MINECRAFT_HARDCORE:-false}
|
- HARDCORE=${MINECRAFT_HARDCORE:-false}
|
||||||
- ENABLE_COMMAND_BLOCK=${MINECRAFT_ENABLE_COMMAND_BLOCK:-false}
|
- ENABLE_COMMAND_BLOCK=${MINECRAFT_ENABLE_COMMAND_BLOCK:-false}
|
||||||
- SPAWN_ANIMALS=${MINECRAFT_SPAWN_ANIMALS:-true}
|
- SPAWN_ANIMALS=${MINECRAFT_SPAWN_ANIMALS:-true}
|
||||||
- SPAWN_MONSTERS=${MINECRAFT_SPAWN_MONSTERS:-true}
|
- SPAWN_MONSTERS=${MINECRAFT_SPAWN_MONSTERS:-true}
|
||||||
- SPAWN_NPCS=${MINECRAFT_SPAWN_NPCS:-true}
|
- SPAWN_NPCS=${MINECRAFT_SPAWN_NPCS:-true}
|
||||||
- SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-true}
|
- SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-false}
|
||||||
- ONLINE_MODE=${MINECRAFT_ONLINE_MODE:-true}
|
- ONLINE_MODE=${MINECRAFT_ONLINE_MODE:-true}
|
||||||
- PLAYER_IDLE_TIMEOUT=${MINECRAFT_PLAYER_IDLE_TIMEOUT:-0}
|
- PLAYER_IDLE_TIMEOUT=${MINECRAFT_PLAYER_IDLE_TIMEOUT:-0}
|
||||||
- MEMORY=${MINECRAFT_MEMORY:-1G}
|
- INIT_MEMORY=${MINECRAFT_INIT_MEMORY:-256M}
|
||||||
|
- MAX_MEMORY=${MINECRAFT_MAX_MEMORY:-1G}
|
||||||
|
- GUI=${MINECRAFT_GUI:-false}
|
||||||
- ENABLE_AUTOPAUSE=${MINECRAFT_ENABLE_AUTOPAUSE:-false}
|
- ENABLE_AUTOPAUSE=${MINECRAFT_ENABLE_AUTOPAUSE:-false}
|
||||||
- RCON_PASSWORD=${SERVICE_PASSWORD_RCON}
|
- RCON_PASSWORD=${SERVICE_PASSWORD_RCON}
|
||||||
- PORT=${PORT:-25565}
|
- PORT=${PORT:-25565}
|
||||||
|
Reference in New Issue
Block a user