Compare commits

..

64 Commits

Author SHA1 Message Date
Andras Bacsai
b983b23e7e Merge pull request #1766 from coollabsio/next
v4.0.0-beta.221
2024-02-19 13:29:58 +01:00
Andras Bacsai
0b81e77a94 fix: database status 2024-02-19 13:28:14 +01:00
Andras Bacsai
b8cf314bfe fix: submodule cloning 2024-02-19 13:22:09 +01:00
Andras Bacsai
4a3338e59c Update version numbers 2024-02-19 10:44:52 +01:00
Andras Bacsai
5b8538c0f4 Merge pull request #1763 from coollabsio/next
v4.0.0-beta.220
2024-02-19 09:53:54 +01:00
Andras Bacsai
88d6320d08 Re-enable docker container removal in ApplicationDeploymentJob 2024-02-19 09:51:39 +01:00
Andras Bacsai
651c9c2c9b Merge pull request #1756 from victor-teles/fix/log-drain-parsers
fix: fluent bit parsers.conf indentation level
2024-02-19 09:22:36 +01:00
Andras Bacsai
8dd45cd388 Merge pull request #1757 from victor-teles/fix/revalidate-server-button
fix(server): revalidate server button not showing in server's page
2024-02-19 09:21:50 +01:00
Andras Bacsai
126ac354d5 fix: empty build variables 2024-02-19 09:19:50 +01:00
Victor
024769c402 fix(server): revalidate server button not showing in server's page 2024-02-17 12:43:49 -03:00
Victor
5acf141669 fix: fluent bit ident level 2024-02-17 12:25:48 -03:00
Andras Bacsai
92e3e8ab7b Merge branch 'main' into next 2024-02-17 16:17:01 +01:00
Andras Bacsai
187a29c666 Update README.md 2024-02-17 16:16:42 +01:00
Andras Bacsai
4c24631795 Add coolify.managed flag to proxy configuration 2024-02-16 23:17:29 +01:00
Andras Bacsai
7d6bd10cca Add Docker container management methods and update Livewire component 2024-02-16 23:09:35 +01:00
Andras Bacsai
f8c86769a7 fix: resources 2024-02-16 22:15:18 +01:00
Andras Bacsai
e0b0dda382 Remove unused code for displaying server resources 2024-02-16 22:04:26 +01:00
Andras Bacsai
b8708f086e feat: initial api endpoints
feat: server resources are now looks better
2024-02-16 21:56:38 +01:00
Andras Bacsai
3539e4dce9 Update Docker installation error messages 2024-02-16 09:06:28 +01:00
Andras Bacsai
5fdadcf557 fix: add openbsd ssh server check 2024-02-16 09:04:32 +01:00
Andras Bacsai
acb3f01f79 Merge pull request #1752 from ahmedrowaihi/main
👌 IMPORVE(SCRIPT/INSTALL): Support Archlinux
2024-02-16 09:03:51 +01:00
Andras Bacsai
83becdb19d fix: only show redeployment required if status is not exited 2024-02-16 08:34:30 +01:00
=
e3e8fe7895 👌 IMPORVE(SCRIPT/INSTALL): Support Archlinux 2024-02-16 01:56:16 +03:00
Andras Bacsai
6ddff8fae1 Merge pull request #1748 from coollabsio/next
v4.0.0-beta.219
2024-02-15 21:33:50 +01:00
Andras Bacsai
f5cb2dbdcf Fix condition in removeServer method 2024-02-15 21:25:43 +01:00
Andras Bacsai
fe19769d82 fix: do not add the same server twice 2024-02-15 21:22:59 +01:00
Andras Bacsai
45e404b15b feat: disable gzip compression on service applications 2024-02-15 20:44:01 +01:00
Andras Bacsai
5bdaa68368 Add docker-registry service template and update service-templates.json 2024-02-15 15:39:27 +01:00
Andras Bacsai
d903a377bf Update validation and configuration titles 2024-02-15 14:14:11 +01:00
Andras Bacsai
8e7745f4c1 Remove unnecessary debug statement in Server.php 2024-02-15 13:54:18 +01:00
Andras Bacsai
a9ea6330d9 feat: revalidate server 2024-02-15 13:52:54 +01:00
Andras Bacsai
bfb0260550 fix: use ls / command instead ls 2024-02-15 13:52:42 +01:00
Andras Bacsai
bba1cb3832 fix: ec2 does not have uptime command lol
version++
2024-02-15 13:44:40 +01:00
Andras Bacsai
29ad2144b7 Merge pull request #1747 from coollabsio/next
v4.0.0-beta.218
2024-02-15 12:59:51 +01:00
Andras Bacsai
38d367e709 fix: padding left on input boxes 2024-02-15 12:59:25 +01:00
Andras Bacsai
0e81ff970f Merge pull request #1746 from coollabsio/next
v4.0.0-beta.217
2024-02-15 12:54:38 +01:00
Andras Bacsai
00feef40a3 Fix image tag in docker-compose.prod.yml 2024-02-15 12:29:30 +01:00
Andras Bacsai
dfba593072 feat: magic for traefik redirectregex in services 2024-02-15 12:08:48 +01:00
Andras Bacsai
c770c8d988 Add warning icon for configuration not applied 2024-02-15 12:04:52 +01:00
Andras Bacsai
0f071031a9 Refactor application FQDN handling 2024-02-15 12:01:59 +01:00
Andras Bacsai
99efa857f4 feat: add metabase
feat: consistent container names
fix: for services, you only need to add basicauth label, others are added by coolify
fix: label uuids are not randomly generated all the time
fix: changing force https will change the labels
2024-02-15 11:55:43 +01:00
Andras Bacsai
80035395ff Update version numbers + do not cleanup queue on cloud 2024-02-14 15:31:43 +01:00
Andras Bacsai
d3490e1c95 Merge pull request #1743 from coollabsio/next
v4.0.0-beta.216
2024-02-14 15:21:13 +01:00
Andras Bacsai
dab13c92eb Update disk_usage property type in ServerStatusJob 2024-02-14 15:21:03 +01:00
Andras Bacsai
1f18542960 fix: cleanup scheduled tasks 2024-02-14 15:14:06 +01:00
Andras Bacsai
8f21ea9367 Merge pull request #1727 from lxix/fix-scheduled-tasks
fix: Scheduled Tasks won't execute after deleting resource with scheduled task
2024-02-14 15:02:09 +01:00
Andras Bacsai
73e64d9052 fix: file volume creation
fix: network_mode host compose
2024-02-14 15:00:24 +01:00
Andras Bacsai
6cdd87da41 Update Directus image version to 10 2024-02-14 14:59:38 +01:00
Andras Bacsai
2a5d49f9b3 Merge pull request #1731 from notskamr/patch-1
Update directus-with-postgresql.yaml - Version bump
2024-02-14 14:58:43 +01:00
Andras Bacsai
cc7ba9eb9f Merge pull request #1734 from Geczy/fix-mg
fix: only add 'networks' key if 'network_mode' is absent
2024-02-14 14:31:48 +01:00
Andras Bacsai
c4cc42c8d5 Update version and release numbers 2024-02-14 10:35:44 +01:00
Andras Bacsai
2d0838b112 Merge pull request #1742 from coollabsio/next
v4.0.0-beta.215
2024-02-14 10:32:48 +01:00
Andras Bacsai
07b94a8e48 Update filebrowser.yaml and service-templates.json 2024-02-14 10:31:05 +01:00
Andras Bacsai
4b08abc144 Save storage on initial creation 2024-02-14 10:21:53 +01:00
Andras Bacsai
93e4fc2f32 Update filebrowser image and volume bindings 2024-02-14 10:14:14 +01:00
Andras Bacsai
6dd86eec30 Fix directory creation issue in LocalFileVolume.php and parseDockerComposeFile() 2024-02-14 10:13:49 +01:00
Andras Bacsai
a7ab5d55d3 Add syncthing data volumes and update syncthing service template 2024-02-14 10:00:27 +01:00
Andras Bacsai
689547463c Merge pull request #1712 from RayBB/syncthing-template
add Syncthing template
2024-02-14 09:50:50 +01:00
Andras Bacsai
8a0046c571 update packages + fix tests 2024-02-14 09:21:25 +01:00
Andras Bacsai
fb3991321a Update Coolify version and Sentry configuration 2024-02-14 08:53:13 +01:00
Matt
e6ca8cd167 fix: only add 'networks' key if 'network_mode' is absent 2024-02-11 09:22:09 -06:00
Varun Sahni
22d5159d16 Update directus-with-postgresql.yaml 2024-02-09 23:19:19 +05:30
Barnabás Schósz
1cbd30bd9e Fix Scheduled Tasks won't execute after deleting resource with scheduled task 2024-02-09 17:36:47 +01:00
RayBB
f357f40fc7 add syncthing template 2024-02-05 16:45:46 +01:00
84 changed files with 1680 additions and 1026 deletions

View File

@@ -10,6 +10,17 @@ No vendor lock-in, which means that all the configuration for your applications/
For more information, take a look at our landing page [here](https://coolify.io).
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us [here](https://coolify.io/docs/contact).
# Donations
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
@@ -66,16 +77,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
- Better support
- Less maintenance for you
# Installation
```bash
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
```
You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us [here](https://coolify.io/docs/contact).
# Recognitions

View File

@@ -106,7 +106,6 @@ class StartMariadb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -122,7 +122,6 @@ class StartMongodb
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -106,7 +106,6 @@ class StartMysql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -128,7 +128,6 @@ class StartPostgresql
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -117,7 +117,6 @@ class StartRedis
$this->commands[] = "echo 'Pulling {$database->image} image.'";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$database_name = addslashes($database->name);
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}

View File

@@ -128,9 +128,9 @@ class InstallLogDrain
if ($type !== 'custom') {
$parsers = base64_encode("
[PARSER]
Name empty_line_skipper
Format regex
Regex /^(?!\s*$).+/
Name empty_line_skipper
Format regex
Regex /^(?!\s*$).+/
");
}
$compose = base64_encode("

View File

@@ -25,7 +25,6 @@ class UpdateCoolify
CleanupDocker::run($this->server, false);
$this->latestVersion = get_latest_version_of_coolify();
$this->currentVersion = config('version');
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
if ($settings->next_channel) {
ray('next channel enabled');
$this->latestVersion = 'next';
@@ -44,7 +43,7 @@ class UpdateCoolify
}
$this->update();
}
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());

View File

@@ -45,6 +45,9 @@ class DeleteService
foreach ($service->databases()->get() as $database) {
$database->forceDelete();
}
foreach ($service->scheduled_tasks as $task) {
$task->delete();
}
$service->tags()->detach();
}
}

View File

@@ -13,10 +13,6 @@ class StartService
{
ray('Starting service: ' . $service->name);
$service->saveComposeConfigs();
$service_name = addslashes($service->name);
$server_name = addslashes($service->server->name);
$commands[] = "cd " . $service->workdir();
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
$commands[] = "echo 'Creating Docker network.'";

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ScheduledTask;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
@@ -108,6 +109,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
}
try {
$scheduled_tasks = ScheduledTask::all();
foreach ($scheduled_tasks as $scheduled_task) {
if (!$scheduled_task->service && !$scheduled_task->application) {
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
$scheduled_task->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
}
// Cleanup any resources that are not attached to any environment or destination or server
try {

View File

@@ -125,6 +125,9 @@ class Init extends Command
// Cleanup any failed deployments
try {
if (isCloud()) {
return;
}
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
foreach ($queued_inprogress_deployments as $deployment) {
ray($deployment->id, $deployment->status);

View File

@@ -120,8 +120,8 @@ class Kernel extends ConsoleKernel
return;
}
foreach ($scheduled_tasks as $scheduled_task) {
$service = $scheduled_task->service()->get();
$application = $scheduled_task->application()->get();
$service = $scheduled_task->service;
$application = $scheduled_task->application;
if (!$application && !$service) {
ray('application/service attached to scheduled task does not exist');

View File

@@ -18,8 +18,7 @@ class Deploy extends Controller
{
public function deploy(Request $request)
{
$token = auth()->user()->currentAccessToken();
$teamId = data_get($token, 'team_id');
$teamId = get_team_id_from_token();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project as ModelsProject;
use Illuminate\Http\Request;
class Project extends Controller
{
public function projects(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json($projects);
}
public function project_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
return response()->json($project);
}
public function environment_details(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json($environment);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
class Server extends Controller
{
public function servers(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable;
$server['is_usable'] = $server->settings->is_usable;
return $server;
});
ray($servers);
return response()->json($servers);
}
public function server_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
if (is_null($server)) {
return response()->json(['error' => 'Server not found.'], 404);
}
$server->load(['settings']);
$server['resources'] = $server->definedResources()->map(function ($resource) {
$payload = [
'id' => $resource->id,
'uuid' => $resource->uuid,
'name' => $resource->name,
'type' => $resource->type(),
'created_at' => $resource->created_at,
'updated_at' => $resource->updated_at,
];
if ($resource->type() === 'service') {
$payload['status'] = $resource->status();
} else {
$payload['status'] = $resource->status;
}
return $payload;
});
return response()->json($server);
}
}

View File

@@ -133,6 +133,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
ray('New container name: ', $this->container_name);
savePrivateKeyToFs($this->server);
$this->saved_outputs = collect();
@@ -711,9 +713,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
}
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
}
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
@@ -1015,11 +1022,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->env_nixpacks_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->nixpacks_environment_variables as $env) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
if (!is_null($env->real_value)) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
}
}
} else {
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
if (!is_null($env->real_value)) {
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
}
}
}
@@ -1030,11 +1041,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->env_args = collect([]);
if ($this->pull_request_id === 0) {
foreach ($this->application->build_environment_variables as $env) {
$this->env_args->put($env->key, $env->real_value);
if (!is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
}
}
} else {
foreach ($this->application->build_environment_variables_preview as $env) {
$this->env_args->put($env->key, $env->real_value);
if (!is_null($env->real_value)) {
$this->env_args->put($env->key, $env->real_value);
}
}
}
$this->env_args->put('SOURCE_COMMIT', $this->commit);
@@ -1199,13 +1214,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// ];
// }
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
}
} else {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
}
$this->docker_compose = Yaml::dump($docker_compose, 10);
@@ -1490,6 +1510,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
});
if ($this->application->settings->is_consistent_container_name_enabled) {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
);
}
} else {
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
$this->application_deployment_queue->update([

View File

@@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?int $disk_usage = null;
public int|string|null $disk_usage = null;
public $tries = 4;
public function backoff(): int
{

View File

@@ -206,7 +206,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
try {
config()->set('coolify.mux_enabled', false);
instant_remote_process(['uptime'], $this->createdServer, true);
// EC2 does not have `uptime` command, lol
instant_remote_process(['ls /'], $this->createdServer, true);
$this->createdServer->settings()->update([
'is_reachable' => true,

View File

@@ -8,20 +8,25 @@ use Livewire\Component;
class Advanced extends Component
{
public Application $application;
public bool $is_force_https_enabled;
protected $rules = [
'application.settings.is_git_submodules_enabled' => 'boolean|required',
'application.settings.is_git_lfs_enabled' => 'boolean|required',
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
'application.settings.is_force_https_enabled' => 'boolean|required',
'is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required',
'application.settings.gpu_options' => 'string|required',
];
public function mount() {
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
}
public function instantSave()
{
if ($this->application->isLogDrainEnabled()) {
@@ -31,7 +36,8 @@ class Advanced extends Component
return;
}
}
if ($this->application->settings->is_force_https_enabled) {
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
$this->dispatch('resetDefaultLabels', false);
}
$this->application->settings->save();

View File

@@ -126,7 +126,6 @@ class General extends Component
$this->application->save();
}
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
$this->checkLabelUpdates();
}
public function instantSave()
{
@@ -164,6 +163,7 @@ class General extends Component
}
return $domain;
}
public function updatedApplicationBuildPack()
{
if ($this->application->build_pack !== 'nixpacks') {
@@ -184,15 +184,6 @@ class General extends Component
$this->submit();
$this->dispatch('build_pack_updated');
}
public function checkLabelUpdates()
{
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
$this->labelsChanged = true;
} else {
$this->labelsChanged = false;
}
}
public function getWildcardDomain()
{
$server = data_get($this->application, 'destination.server');
@@ -212,6 +203,13 @@ class General extends Component
public function updatedApplicationFqdn()
{
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
$this->application->save();
$this->resetDefaultLabels(false);
// $this->dispatch('success', 'Labels reset to default!');
}
@@ -238,22 +236,17 @@ class General extends Component
]);
}
if (data_get($this->application, 'fqdn')) {
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
return str($domain)->trim()->lower();
});
$domains = $domains->unique();
$domains = str($this->application->fqdn)->trim()->explode(',');
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.","Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
}
}
}
check_fqdn_usage($this->application);
$this->application->fqdn = $domains->implode(',');
}
if (data_get($this->application, 'custom_docker_run_options')) {
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
}
@@ -279,7 +272,6 @@ class General extends Component
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {
$this->checkLabelUpdates();
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
}
}

View File

@@ -104,7 +104,7 @@ class Index extends Component
'environment_name' => data_get($service, 'environment.name'),
'service_uuid' => data_get($service, 'uuid')
]);
$service->status = serviceStatus($service);
$service->status = $service->status();
}
return $service;
});

View File

@@ -41,7 +41,7 @@ class FileStorage extends Component
$this->fileStorage->content = null;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer($this->service);
$this->fileStorage->saveStorageOnServer();
$this->dispatch('success', 'File updated successfully.');
} catch (\Throwable $e) {
$this->fileStorage->setRawAttributes($original);

View File

@@ -17,6 +17,7 @@ class ServiceApplicationView extends Component
'application.exclude_from_status' => 'required|boolean',
'application.required_fqdn' => 'required|boolean',
'application.is_log_drain_enabled' => 'nullable|boolean',
'application.is_gzip_enabled' => 'nullable|boolean',
];
public function render()
{

View File

@@ -45,8 +45,10 @@ class StackForm extends Component
$this->service->docker_compose_raw = $raw;
$this->submit();
}
public function instantSave() {
public function instantSave()
{
$this->service->save();
$this->dispatch('success', 'Service settings saved successfully.');
}
public function submit()

View File

@@ -37,6 +37,9 @@ class Destination extends Component
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
return $all_networks->pluck('id')->contains($network->id);
});
$this->networks = $this->networks->reject(function ($network) {
return $this->resource->destination->server->id == $network->server->id;
});
}
public function redeploy(int $network_id, int $server_id)
{
@@ -70,7 +73,7 @@ class Destination extends Component
}
public function removeServer(int $network_id, int $server_id)
{
if ($this->resource->destination->server->id == $server_id) {
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
return;
}

View File

@@ -13,8 +13,9 @@ class Form extends Component
public ?string $wildcard_domain = null;
public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
public bool $revalidate = false;
protected $listeners = ['serverInstalled'];
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
protected $rules = [
'server.name' => 'required',
@@ -68,6 +69,10 @@ class Form extends Component
return handleError($e, $this);
}
}
public function revalidate()
{
$this->revalidate = true;
}
public function checkLocalhostConnection()
{
$uptime = $this->server->validateConnection();

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Livewire\Server;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
class Resources extends Component
{
use AuthorizesRequests;
public ?Server $server = null;
public $parameters = [];
public Collection $unmanagedContainers;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'refreshStatus',
];
}
public function startUnmanaged($id) {
$this->server->startUnmanaged($id);
$this->dispatch('success', 'Container started.');
$this->loadUnmanagedContainers();
}
public function restartUnmanaged($id) {
$this->server->restartUnmanaged($id);
$this->dispatch('success', 'Container restarted.');
$this->loadUnmanagedContainers();
}
public function stopUnmanaged($id) {
$this->server->stopUnmanaged($id);
$this->dispatch('success', 'Container stopped.');
$this->loadUnmanagedContainers();
}
public function refreshStatus() {
$this->server->refresh();
$this->loadUnmanagedContainers();
$this->dispatch('success', 'Resource statuses refreshed.');
}
public function loadUnmanagedContainers() {
$this->unmanagedContainers = $this->server->loadUnmanagedContainers();
}
public function mount() {
$this->unmanagedContainers = collect();
$this->parameters = get_route_parameters();
try {
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.index');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->loadUnmanagedContainers();
}
public function render()
{
return view('livewire.server.resources');
}
}

View File

@@ -19,12 +19,12 @@ class ValidateAndInstall extends Component
public $docker_version = null;
public $proxy_started = false;
public $error = null;
public bool $ask = false;
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
public function init(bool $install = true)
{
$this->install = $install;
$this->uptime = null;
$this->supported_os_type = null;
@@ -34,9 +34,14 @@ class ValidateAndInstall extends Component
$this->proxy_started = null;
$this->error = null;
$this->number_of_tries = 0;
$this->dispatch('validateServerNow');
if (!$this->ask) {
$this->dispatch('validateServerNow');
}
}
public function startValidatingAfterAsking() {
$this->ask = false;
$this->init();
}
public function validateServer()
{
try {

View File

@@ -6,6 +6,7 @@ use App\Enums\ApplicationDeploymentStatus;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str;
use RuntimeException;
@@ -48,6 +49,9 @@ class Application extends BaseModel
$application->persistentStorages()->delete();
$application->environment_variables()->delete();
$application->environment_variables_preview()->delete();
foreach ($application->scheduled_tasks as $task) {
$task->delete();
}
$application->tags()->detach();
});
}
@@ -209,6 +213,10 @@ class Application extends BaseModel
);
}
public function isExited()
{
return (bool) str($this->status)->startsWith('exited');
}
public function realStatus()
{
return $this->getRawOriginal('status');
@@ -467,7 +475,7 @@ class Application extends BaseModel
{
return data_get($this, 'settings.is_log_drain_enabled', false);
}
public function isConfigurationChanged($save = false)
public function isConfigurationChanged(bool $save = false)
{
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
@@ -515,17 +523,21 @@ class Application extends BaseModel
{
return "/artifacts/{$uuid}";
}
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
{
$baseDir = $this->generateBaseDir($deployment_uuid);
if ($this->git_commit_sha !== 'HEAD') {
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
}
if ($this->settings->is_git_submodules_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git submodule update --init --recursive";
if ($public) {
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && sed -i \"s#git@\(.*\):#https://\\1/#g\" {$baseDir}/.gitmodules || true";
}
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive";
}
if ($this->settings->is_git_lfs_enabled) {
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git lfs pull";
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull";
}
return $git_clone_command;
}
@@ -553,7 +565,7 @@ class Application extends BaseModel
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
if (!$only_checkout) {
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
}
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
@@ -652,7 +664,7 @@ class Application extends BaseModel
if ($this->deploymentType() === 'other') {
$fullRepoUrl = $customRepository;
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
if ($pull_request_id !== 0) {
if ($git_type === 'gitlab') {

View File

@@ -26,7 +26,6 @@ class Environment extends Model
{
return $this->hasMany(Application::class);
}
public function postgresqls()
{
return $this->hasMany(StandalonePostgresql::class);

View File

@@ -10,20 +10,37 @@ class LocalFileVolume extends BaseModel
use HasFactory;
protected $guarded = [];
protected static function booted()
{
static::created(function (LocalFileVolume $fileVolume) {
$fileVolume->load(['service']);
$fileVolume->saveStorageOnServer();
});
}
public function service()
{
return $this->morphTo('resource');
}
public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service)
public function saveStorageOnServer()
{
$workdir = $service->service->workdir();
$server = $service->service->server;
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
$commands = collect([
"mkdir -p $workdir > /dev/null 2>&1 || true",
"cd $workdir"
]);
$is_directory = $this->is_directory;
if ($is_directory) {
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
}
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
$parent_dir = str($this->fs_path)->beforeLast('/');
if ($parent_dir != '') {
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
}
}
$fileVolume = $this;
$path = Str::of(data_get($fileVolume, 'fs_path'));
$path = str(data_get($fileVolume, 'fs_path'));
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) {
$path = $path->after('.');
@@ -32,17 +49,18 @@ class LocalFileVolume extends BaseModel
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) {
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
throw new \Exception("The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
throw new \Exception("The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.");
}
if (!$fileVolume->is_directory && $isDir == 'NOK') {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d > $path");
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
if ($content) {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d > $path");
}
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}
ray($commands->toArray());
return instant_remote_process($commands, $server);
}
}

View File

@@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\Model;
class ProjectSetting extends Model
{
protected $fillable = [
'project_id'
];
protected $guarded = [];
public function project()
{
return $this->belongsTo(Project::class);
}
}

View File

@@ -225,6 +225,32 @@ class Server extends BaseModel
$services = $this->services();
return $applications->concat($databases)->concat($services->get());
}
public function stopUnmanaged($id)
{
return instant_remote_process(["docker stop -t 0 $id"], $this);
}
public function restartUnmanaged($id)
{
return instant_remote_process(["docker restart $id"], $this);
}
public function startUnmanaged($id)
{
return instant_remote_process(["docker start $id"], $this);
}
public function loadUnmanagedContainers()
{
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
$containers = format_docker_command_output_to_json($containers);
$containers = $containers->map(function ($container) {
$labels = data_get($container, 'Labels');
if (!str($labels)->contains("coolify.managed")) {
return $container;
}
return null;
});
$containers = $containers->filter();
return collect($containers);
}
public function hasDefinedResources()
{
$applications = $this->applications()->count() > 0;
@@ -245,6 +271,8 @@ class Server extends BaseModel
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
})->filter(function ($item) {
return data_get($item, 'name') !== 'coolify-db';
})->flatten();
}
public function applications()
@@ -400,7 +428,9 @@ class Server extends BaseModel
if ($server->skipServer()) {
return false;
}
$uptime = instant_remote_process(['uptime'], $server, false);
// EC2 does not have `uptime` command, lol
$uptime = instant_remote_process(['ls /'], $server, false);
if (!$uptime) {
$server->settings()->update([
'is_reachable' => false,

View File

@@ -28,6 +28,48 @@ class Service extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function status() {
$foundRunning = false;
$isDegraded = false;
$foundRestaring = false;
$applications = $this->applications;
$databases = $this->databases;
foreach ($applications as $application) {
if ($application->exclude_from_status) {
continue;
}
if (Str::of($application->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($application->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
foreach ($databases as $database) {
if ($database->exclude_from_status) {
continue;
}
if (Str::of($database->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($database->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
if ($foundRestaring) {
return 'degraded';
}
if ($foundRunning && !$isDegraded) {
return 'running';
} else if ($foundRunning && $isDegraded) {
return 'degraded';
} else if (!$foundRunning && !$isDegraded) {
return 'exited';
}
return 'exited';
}
public function extraFields()
{
$fields = collect([]);

View File

@@ -23,6 +23,10 @@ class ServiceApplication extends BaseModel
{
return data_get($this, 'is_log_drain_enabled', false);
}
public function isGzipEnabled()
{
return data_get($this, 'is_gzip_enabled', true);
}
public function type()
{
return 'service';

View File

@@ -21,6 +21,10 @@ class ServiceDatabase extends BaseModel
{
return data_get($this, 'is_log_drain_enabled', false);
}
public function isGzipEnabled()
{
return true;
}
public function type()
{
return 'service';

View File

@@ -82,6 +82,9 @@ class StandaloneMariadb extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project() {
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@@ -85,6 +85,9 @@ class StandaloneMongodb extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project() {
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@@ -82,6 +82,9 @@ class StandaloneMysql extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project() {
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@@ -82,6 +82,10 @@ class StandalonePostgresql extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project()
{
return data_get($this, 'environment.project');
}
public function link()
{
if (data_get($this, 'environment.project.uuid')) {

View File

@@ -77,6 +77,10 @@ class StandaloneRedis extends BaseModel
{
return $this->morphToMany(Tag::class, 'taggable');
}
public function project()
{
return data_get($this, 'environment.project');
}
public function team()
{
return data_get($this, 'environment.project.team');

View File

@@ -15,8 +15,9 @@ class Services extends Component
public function __construct(
public Service $service,
public string $complexStatus = 'exited',
public bool $showRefreshButton = true
) {
$this->complexStatus = serviceStatus($service);
$this->complexStatus = $service->status();
}
/**

View File

@@ -0,0 +1,7 @@
<?php
function get_team_id_from_token()
{
$token = auth()->user()->currentAccessToken();
return data_get($token, 'team_id');
}

View File

@@ -7,7 +7,6 @@ use App\Models\ServiceApplication;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
{
@@ -123,10 +122,14 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
$now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $application->uuid . '-pr-' . $pull_request_id;
} else {
if ($consistent_container_name) {
return $application->uuid;
}
return $application->uuid . '-' . $now;
}
}
@@ -209,15 +212,48 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
}
return $payload;
}
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null)
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true)
{
$labels = collect([]);
$labels->push('traefik.enable=true');
$labels->push("traefik.http.middlewares.gzip.compress=true");
$labels->push("traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https");
$basic_auth = false;
$basic_auth_middleware = null;
$redirect = false;
$redirect_middleware = null;
if ($serviceLabels) {
$basic_auth = $serviceLabels->contains(function ($value) {
return str_contains($value, 'basicauth');
});
if ($basic_auth) {
$basic_auth_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
$redirect = $serviceLabels->contains(function ($value) {
return str_contains($value, 'redirectregex');
});
if ($redirect) {
$redirect_middleware = $serviceLabels
->map(function ($item) {
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
return $matches[1];
}
})
->filter()
->first();
}
}
foreach ($domains as $loop => $domain) {
try {
$uuid = new Cuid2(7);
// $uuid = new Cuid2(7);
$url = Url::fromString($domain);
$host = $url->getHost();
$path = $url->getPath();
@@ -239,11 +275,36 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
}
if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix,gzip");
$middlewares = collect(["{$https_label}-stripprefix"]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
}
} else {
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
$middlewares = collect([]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
}
}
$labels->push("traefik.http.routers.{$https_label}.tls=true");
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
@@ -267,16 +328,41 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
}
if ($path !== '/') {
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix,gzip");
$middlewares = collect(["{$http_label}-stripprefix"]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
}
} else {
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
$middlewares = collect([]);
if ($is_gzip_enabled) {
$middlewares->push('gzip');
}
if ($basic_auth && $basic_auth_middleware) {
$middlewares->push($basic_auth_middleware);
}
if ($redirect && $redirect_middleware) {
$middlewares->push($redirect_middleware);
}
if ($middlewares->isNotEmpty()) {
$middlewares = $middlewares->join(',');
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
}
}
}
} catch (\Throwable $e) {
continue;
}
}
return $labels->sort();
}
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array

View File

@@ -103,6 +103,7 @@ function generate_default_proxy_configuration(Server $server)
"traefik.http.routers.traefik.entrypoints=http",
"traefik.http.routers.traefik.service=api@internal",
"traefik.http.services.traefik.loadbalancer.server.port=8080",
"coolify.managed=true",
];
$config = [
"version" => "3.8",

View File

@@ -228,56 +228,6 @@ function refresh_server_connection(?PrivateKey $private_key = null)
}
}
// function validateServer(Server $server, bool $throwError = false)
// {
// try {
// $uptime = instant_remote_process(['uptime'], $server, $throwError);
// if (!$uptime) {
// $server->settings->is_reachable = false;
// $server->team->notify(new Unreachable($server));
// $server->unreachable_notification_sent = true;
// $server->save();
// return [
// "uptime" => null,
// "dockerVersion" => null,
// ];
// }
// $server->settings->is_reachable = true;
// instant_remote_process(["docker ps"], $server, $throwError);
// $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
// if (!$dockerVersion) {
// $dockerVersion = null;
// return [
// "uptime" => $uptime,
// "dockerVersion" => null,
// ];
// }
// $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
// if (is_null($dockerVersion)) {
// $server->settings->is_usable = false;
// } else {
// $server->settings->is_usable = true;
// if (data_get($server, 'unreachable_notification_sent') === true) {
// $server->team->notify(new Revived($server));
// $server->unreachable_notification_sent = false;
// $server->save();
// }
// }
// return [
// "uptime" => $uptime,
// "dockerVersion" => $dockerVersion,
// ];
// } catch (\Throwable $e) {
// $server->settings->is_reachable = false;
// $server->settings->is_usable = false;
// throw $e;
// } finally {
// if (data_get($server, 'settings')) {
// $server->settings->save();
// }
// }
// }
function checkRequiredCommands(Server $server)
{
$commands = collect(["jq", "jc"]);

View File

@@ -21,49 +21,6 @@ function replaceVariables($variable)
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
}
function serviceStatus(Service $service)
{
$foundRunning = false;
$isDegraded = false;
$foundRestaring = false;
$applications = $service->applications;
$databases = $service->databases;
foreach ($applications as $application) {
if ($application->exclude_from_status) {
continue;
}
if (Str::of($application->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($application->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
foreach ($databases as $database) {
if ($database->exclude_from_status) {
continue;
}
if (Str::of($database->status)->startsWith('running')) {
$foundRunning = true;
} else if (Str::of($database->status)->startsWith('restarting')) {
$foundRestaring = true;
} else {
$isDegraded = true;
}
}
if ($foundRestaring) {
return 'degraded';
}
if ($foundRunning && !$isDegraded) {
return 'running';
} else if ($foundRunning && $isDegraded) {
return 'degraded';
} else if (!$foundRunning && !$isDegraded) {
return 'exited';
}
return 'exited';
}
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
{
// TODO: make this async

View File

@@ -125,6 +125,9 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
}
if (isset($livewire)) {
if (str($message)->length() > 20) {
return $livewire->dispatch('error', 'Error occured', $message);
}
return $livewire->dispatch('error', $message);
}
throw new Exception($message);
@@ -527,28 +530,32 @@ function getTopLevelNetworks(Service|Application $resource)
$definedNetwork = collect([$resource->uuid]);
$services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) {
$serviceNetworks = collect(data_get($service, 'networks', []));
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null);
// Only add 'networks' key if 'network_mode' is not 'host'
if (!$hasHostNetworkMode) {
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (!$networkExists) {
$topLevelNetworks->put($networkDetails, null);
}
}
}
}
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
}
}
}
@@ -628,6 +635,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceNetworks = collect(data_get($service, 'networks', []));
$serviceVariables = collect(data_get($service, 'environment', []));
$serviceLabels = collect(data_get($service, 'labels', []));
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
if ($serviceLabels->count() > 0) {
$removedLabels = collect([]);
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
@@ -698,7 +706,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$savedService->image = $image;
$savedService->save();
}
// Collect/create/update networks
if ($serviceNetworks->count() > 0) {
foreach ($serviceNetworks as $networkName => $networkDetails) {
@@ -729,37 +736,39 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$savedService->ports = $collectedPorts->implode(',');
$savedService->save();
// Add Coolify specific networks
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
if (!$hasHostNetworkMode) {
// Add Coolify specific networks
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
return $value == $definedNetwork;
});
if (!$definedNetworkExists) {
foreach ($definedNetwork as $network) {
$topLevelNetworks->put($network, [
'name' => $network,
'external' => true
]);
}
}
}
$networks = collect();
foreach ($serviceNetworks as $key => $serviceNetwork) {
if (gettype($serviceNetwork) === 'string') {
// networks:
// - appwrite
$networks->put($serviceNetwork, null);
} else if (gettype($serviceNetwork) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
// $networks->put($serviceNetwork, null);
ray($key);
$networks->put($key, $serviceNetwork);
$networks = collect();
foreach ($serviceNetworks as $key => $serviceNetwork) {
if (gettype($serviceNetwork) === 'string') {
// networks:
// - appwrite
$networks->put($serviceNetwork, null);
} else if (gettype($serviceNetwork) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
// $networks->put($serviceNetwork, null);
ray($key);
$networks->put($key, $serviceNetwork);
}
}
foreach ($definedNetwork as $key => $network) {
$networks->put($network, null);
}
data_set($service, 'networks', $networks->toArray());
}
foreach ($definedNetwork as $key => $network) {
$networks->put($network, null);
}
data_set($service, 'networks', $networks->toArray());
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
@@ -782,14 +791,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
$isDirectory = (bool) data_get($volume, 'isDirectory', false) || (bool) data_get($volume, 'is_directory', false);
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull = data_get($foundConfig, 'content');
if ($contentNotNull) {
$content = $contentNotNull;
}
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
$isDirectory = (bool) data_get($volume, 'isDirectory', false) || (bool) data_get($volume, 'is_directory', false);
}
}
if ($type->value() === 'bind') {
@@ -1030,7 +1039,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$serviceLabels = $serviceLabels->merge($defaultLabels);
if (!$isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true, serviceLabels: $serviceLabels, is_gzip_enabled: $savedService->isGzipEnabled()));
}
}
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
@@ -1054,6 +1063,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
data_forget($service, 'volumes.*.is_directory');
// Remove unnecessary variables from service.environment
// $withoutServiceEnvs = collect([]);
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
@@ -1469,7 +1480,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $preview_fqdn;
});
}
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns,serviceLabels: $serviceLabels));
}
}
}

803
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.214',
'release' => '4.0.0-beta.221',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.214';
return '4.0.0-beta.221';

View File

@@ -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_consistent_container_name_enabled')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_consistent_container_name_enabled');
});
}
};

View File

@@ -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('service_applications', function (Blueprint $table) {
$table->boolean('is_gzip_enabled')->default(true);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('service_applications', function (Blueprint $table) {
$table->dropColumn('is_gzip_enabled');
});
}
};

View File

@@ -1,7 +1,7 @@
version: '3.8'
services:
coolify:
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:latest}"
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"
volumes:
- type: bind
source: /data/coolify/source/.env

176
package-lock.json generated
View File

@@ -7,7 +7,7 @@
"dependencies": {
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.4.19",
"daisyui": "4.7.2",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
},
@@ -17,11 +17,11 @@
"axios": "1.6.7",
"laravel-echo": "1.15.3",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.33",
"postcss": "8.4.35",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
"vue": "3.4.15"
"vue": "3.4.19"
}
},
"node_modules/@alloc/quick-lru": {
@@ -524,77 +524,77 @@
}
},
"node_modules/@vue/compiler-core": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz",
"integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
"integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.23.6",
"@vue/shared": "3.4.15",
"@babel/parser": "^7.23.9",
"@vue/shared": "3.4.19",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-core/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/compiler-dom": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz",
"integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
"integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
"dev": true,
"dependencies": {
"@vue/compiler-core": "3.4.15",
"@vue/shared": "3.4.15"
"@vue/compiler-core": "3.4.19",
"@vue/shared": "3.4.19"
}
},
"node_modules/@vue/compiler-dom/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/compiler-sfc": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz",
"integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
"integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
"dev": true,
"dependencies": {
"@babel/parser": "^7.23.6",
"@vue/compiler-core": "3.4.15",
"@vue/compiler-dom": "3.4.15",
"@vue/compiler-ssr": "3.4.15",
"@vue/shared": "3.4.15",
"@babel/parser": "^7.23.9",
"@vue/compiler-core": "3.4.19",
"@vue/compiler-dom": "3.4.19",
"@vue/compiler-ssr": "3.4.19",
"@vue/shared": "3.4.19",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.5",
"magic-string": "^0.30.6",
"postcss": "^8.4.33",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-sfc/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/compiler-ssr": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz",
"integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
"integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.15",
"@vue/shared": "3.4.15"
"@vue/compiler-dom": "3.4.19",
"@vue/shared": "3.4.19"
}
},
"node_modules/@vue/compiler-ssr/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/reactivity": {
@@ -606,64 +606,64 @@
}
},
"node_modules/@vue/runtime-core": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz",
"integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
"integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
"dev": true,
"dependencies": {
"@vue/reactivity": "3.4.15",
"@vue/shared": "3.4.15"
"@vue/reactivity": "3.4.19",
"@vue/shared": "3.4.19"
}
},
"node_modules/@vue/runtime-core/node_modules/@vue/reactivity": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz",
"integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
"integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
"dev": true,
"dependencies": {
"@vue/shared": "3.4.15"
"@vue/shared": "3.4.19"
}
},
"node_modules/@vue/runtime-core/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/runtime-dom": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz",
"integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
"integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
"dev": true,
"dependencies": {
"@vue/runtime-core": "3.4.15",
"@vue/shared": "3.4.15",
"@vue/runtime-core": "3.4.19",
"@vue/shared": "3.4.19",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/server-renderer": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz",
"integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
"integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
"dev": true,
"dependencies": {
"@vue/compiler-ssr": "3.4.15",
"@vue/shared": "3.4.15"
"@vue/compiler-ssr": "3.4.19",
"@vue/shared": "3.4.19"
},
"peerDependencies": {
"vue": "3.4.15"
"vue": "3.4.19"
}
},
"node_modules/@vue/server-renderer/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/@vue/shared": {
@@ -953,9 +953,9 @@
}
},
"node_modules/daisyui": {
"version": "4.4.19",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.4.19.tgz",
"integrity": "sha512-IjOLWwnndD4N7Ut5CDxbUsaVtbqXPeVHM92IcgxGFxpuOd3CCKW/PAXZH6JoBTHFRaN57vB9XqEhdWm5yC+bPA==",
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.2.tgz",
"integrity": "sha512-9UCss12Zmyk/22u+JbkVrHHxOzFOyY17HuqP5LeswI4hclbj6qbjJTovdj2zRy8cCH6/n6Wh0lTLjriGnyGh0g==",
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
@@ -1410,9 +1410,9 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
"integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
@@ -1598,9 +1598,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"funding": [
{
"type": "opencollective",
@@ -2106,16 +2106,16 @@
}
},
"node_modules/vue": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz",
"integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
"integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
"dev": true,
"dependencies": {
"@vue/compiler-dom": "3.4.15",
"@vue/compiler-sfc": "3.4.15",
"@vue/runtime-dom": "3.4.15",
"@vue/server-renderer": "3.4.15",
"@vue/shared": "3.4.15"
"@vue/compiler-dom": "3.4.19",
"@vue/compiler-sfc": "3.4.19",
"@vue/runtime-dom": "3.4.19",
"@vue/server-renderer": "3.4.19",
"@vue/shared": "3.4.19"
},
"peerDependencies": {
"typescript": "*"
@@ -2127,9 +2127,9 @@
}
},
"node_modules/vue/node_modules/@vue/shared": {
"version": "3.4.15",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw==",
"dev": true
},
"node_modules/wrappy": {

View File

@@ -11,16 +11,16 @@
"axios": "1.6.7",
"laravel-echo": "1.15.3",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.33",
"postcss": "8.4.35",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
"vue": "3.4.15"
"vue": "3.4.19"
},
"dependencies": {
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.4.19",
"daisyui": "4.7.2",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
}

View File

@@ -22,7 +22,7 @@
</svg>
</div>
@endif
<input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass . ' pl-10']) }}
<input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass]) }}
@required($required) @if ($id !== 'null') wire:model={{ $id }} @endif
wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"

View File

@@ -18,6 +18,12 @@
]) }}">
<button>General</button>
</a>
<a class="{{ request()->routeIs('server.resources') ? 'text-white' : '' }}"
href="{{ route('server.resources', [
'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}">
<button>Resources</button>
</a>
<a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'),

View File

@@ -5,7 +5,7 @@
</a>
<x-services.links />
<div class="flex-1"></div>
@if (serviceStatus($service) === 'degraded')
@if ($service->status() === 'degraded')
<button wire:click='deploy' onclick="startService.showModal()"
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@@ -26,7 +26,7 @@
Stop
</button>
@endif
@if (serviceStatus($service) === 'running')
@if ($service->status() === 'running')
<button wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
@@ -47,7 +47,7 @@
Stop
</button>
@endif
@if (serviceStatus($service) === 'exited')
@if ($service->status() === 'exited')
<button wire:click='stop(true)'
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">

View File

@@ -1,11 +1,15 @@
@props(['status', 'showRefreshButton' => true])
@if (str($status)->startsWith('running'))
<x-status.running :status="$status" />
@elseif(str($status)->startsWith('restarting') || str($status)->startsWith('starting') || str($status)->startsWith('degraded'))
@elseif(str($status)->startsWith('restarting') ||
str($status)->startsWith('starting') ||
str($status)->startsWith('degraded'))
<x-status.restarting :status="$status" />
@else
<x-status.stopped :status="$status" />
<x-status.stopped :status="$status"/>
@endif
@if (!str($status)->contains('exited'))
@if (!str($status)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='check_status(true)' class="mx-1 hover:fill-white fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path

View File

@@ -7,7 +7,7 @@
@else
<x-status.stopped :status="$complexStatus" />
@endif
@if (!str($complexStatus)->contains('exited'))
@if (!str($complexStatus)->contains('exited') && $showRefreshButton)
<button title="Refresh Status" wire:click='check_status(true)' class="mx-1 hover:fill-white fill-warning">
<svg class="w-4 h-4" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path

View File

@@ -241,7 +241,7 @@
</x-slot:question>
<x-slot:actions>
<x-slide-over closeWithX fullScreen>
<x-slot:title>Validating & Configuring</x-slot:title>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$this->createdServer" />
</x-slot:content>

View File

@@ -15,7 +15,11 @@
@endif
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="application.settings.is_force_https_enabled" label="Force Https" />
instantSave id="is_force_https_enabled" label="Force Https" />
<x-forms.checkbox
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold text-warning'>You will lose the rolling update feature!</span>"
instantSave id="application.settings.is_consistent_container_name_enabled"
label="Consistent Container Names" />
<h4>Logs</h4>
@if (!$application->settings->is_raw_compose_deployment_enabled)
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."

View File

@@ -5,9 +5,12 @@
<x-forms.button type="submit">
Save
</x-forms.button>
@if ($isConfigurationChanged && !is_null($application->config_hash))
<div class="font-bold text-warning">Configuration not applied to the running application. You need to
redeploy.</div>
@if ($isConfigurationChanged && !is_null($application->config_hash) && !$application->isExited())
<div title="Configuration not applied to the running application. You need to redeploy.">
<svg class="w-6 h-6 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"/>
</svg>
</div>
@endif
</div>
<div>General configuration for your application.</div>

View File

@@ -32,7 +32,9 @@
</div>
</div>
<h3 class="pt-2">Advanced</h3>
<div class="w-64">
<div class="w-96">
<x-forms.checkbox instantSave id="application.is_gzip_enabled" label="Enable gzip compression"
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this." />
<x-forms.checkbox instantSave label="Exclude from service status"
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
id="application.exclude_from_status"></x-forms.checkbox>

View File

@@ -15,7 +15,8 @@
<x-forms.input id="service.description" label="Description" />
</div>
<div class="w-96">
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network" helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network"
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
</div>
@if ($fields)
<div>

View File

@@ -28,7 +28,7 @@
</form>
<div class="pt-4">
<h3 class="py-4">Recent executions</h3>
<h3 class="py-4">Recent executions <span class="text-xs text-neutral-500">(click to check output)</span></h3>
<livewire:project.shared.scheduled-task.executions key="{{ $task->id }}" selectedKey="" :executions="$task->executions->take(-20)" />
</div>
</div>

View File

@@ -15,44 +15,5 @@
This server will be deleted. It is not reversible. <br>Please think again.
</x-new-modal>
@endif
<div class="flex flex-col">
@forelse ($server->definedResources() as $resource)
@if ($loop->first)
<h3 class="pt-4">Resources</h3>
@endif
@if ($resource->link())
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</a>
@else
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</div>
@endif
@empty
@endforelse
</div>
@else
<div class="flex flex-col">
@forelse ($server->definedResources() as $resource)
@if ($loop->first)
<h3 class="pt-4">Resources</h3>
@endif
@if ($resource->link())
<a class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline" href="{{ $resource->link() }}">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</a>
@else
<div class="flex gap-2 p-1 hover:bg-coolgray-100 hover:no-underline">
<div class="w-64">{{ str($resource->type())->headline() }}</div>
<div>{{ $resource->name }}</div>
</div>
@endif
@empty
@endforelse
</div>
@endif
</div>

View File

@@ -10,16 +10,25 @@
</x-new-modal>
@else
<x-forms.button type="submit">Save</x-forms.button>
<x-slide-over closeWithX fullScreen>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$server" ask />
</x-slot:content>
<x-forms.button @click="slideOverOpen=true" wire:click.prevent='validateServer' isHighlighted>
Revalidate server
</x-forms.button>
</x-slide-over>
@endif
</div>
@if (!$server->isFunctional())
You can't use this server until it is validated.
@else
@if ($server->isFunctional())
Server is reachable and validated.
@else
You can't use this server until it is validated.
@endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0)
<x-slide-over closeWithX fullScreen>
<x-slot:title>Validating & Configuring</x-slot:title>
<x-slot:title>Validate & configure</x-slot:title>
<x-slot:content>
<livewire:server.validate-and-install :server="$server" />
</x-slot:content>

View File

@@ -0,0 +1,143 @@
<div>
<x-server.navbar :server="$server" :parameters="$parameters" />
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex h-full">
<div class="flex flex-col gap-4 xl:w-32">
<a :class="activeTab === 'managed' && 'text-white'"
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
<a :class="activeTab === 'unmanaged' && 'text-white'"
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
</div>
<div class="w-full pl-8">
<div x-cloak x-show="activeTab === 'managed'" class="h-full">
<div class="flex flex-col">
<div class="flex gap-2">
<h2>Resources</h2>
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
</div>
<div class="pb-4 title">Here you can find all resources that are managed by Coolify.</div>
</div>
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-coolgray-400">
<thead>
<tr class="text-neutral-500">
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Project
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">
Environment</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Type</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Status
</th>
</tr>
</thead>
<tbody class="divide-y divide-coolgray-400">
@forelse ($server->definedResources()->sortBy('name',SORT_NATURAL) as $resource)
<tr class="text-white bg-coolblack hover:bg-coolgray-100">
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource->project(), 'name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'environment.name') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap"><a class=""
href="{{ $resource->link() }}">{{ $resource->name }} </a>
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ str($resource->type())->headline() }}</td>
<td class="px-5 py-4 text-sm font-medium whitespace-nowrap">
@if ($resource->type() === 'service')
<x-status.services :service="$resource" :showRefreshButton="false" />
@else
<x-status.index :status="$resource->status" :showRefreshButton="false" />
@endif
</td>
</tr>
@empty
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<div x-cloak x-show="activeTab === 'unmanaged'" class="h-full">
<div class="flex flex-col">
<div class="flex gap-2">
<h2>Resources</h2>
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
</div>
<div class="pb-4 title">Here you can find all other containers running on the server.</div>
</div>
@if ($unmanagedContainers->count() > 0)
<div class="flex flex-col">
<div class="flex flex-col">
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
<div class="overflow-hidden">
<table class="min-w-full divide-y divide-coolgray-400">
<thead>
<tr class="text-neutral-500">
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Name
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Image
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Status
</th>
<th class="px-5 py-3 text-xs font-medium text-left uppercase">Action
</th>
</tr>
</thead>
<tbody class="divide-y divide-coolgray-400">
@forelse ($unmanagedContainers->sortBy('name',SORT_NATURAL) as $resource)
<tr class="text-white bg-coolblack hover:bg-coolgray-100">
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'Names') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'Image') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
{{ data_get($resource, 'State') }}
</td>
<td class="px-5 py-4 text-sm whitespace-nowrap">
@if (data_get($resource, 'State') === 'running')
<x-forms.button
wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
<x-forms.button isError
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@elseif (data_get($resource, 'State') === 'exited')
<x-forms.button
wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
@elseif (data_get($resource, 'State') === 'restarting')
<x-forms.button
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@endif
</td>
</tr>
@empty
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
</div>
</div>
</div>

View File

@@ -1,29 +1,12 @@
<div class="flex flex-col gap-2">
@if ($uptime)
<div class="flex w-64 gap-2">Server is reachable: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@if ($ask)
This will revalidate the server, install / update Docker Engine, Docker Compose and all related
configuration. It will also restart Docker Engine, so your running containers will be unreachable
for the time being.
<x-forms.button isHighlighted wire:click='startValidatingAfterAsking '>Continue</x-forms.button>
@else
@if ($error)
<div class="flex w-64 gap-2">Server is reachable: <svg class="w-5 h-5 text-error" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Server is reachable: " /></div>
@endif
@endif
@if ($uptime)
@if ($supported_os_type)
<div class="flex w-64 gap-2">Supported OS type: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
@if ($uptime)
<div class="flex w-64 gap-2">Server is reachable: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
@@ -41,46 +24,12 @@
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Server is reachable:" /></div>
<div class="w-64"><x-loading text="Server is reachable: " /></div>
@endif
@endif
@endif
@if ($uptime && $supported_os_type)
@if ($docker_installed)
<div class="flex w-64 gap-2">Docker is installed: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@else
@if ($error)
<div class="flex w-64 gap-2">Docker is installed: <svg class="w-5 h-5 text-error" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Docker is installed:" /></div>
@endif
@endif
@if ($docker_compose_installed)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@if ($proxy_started)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
@if ($uptime)
@if ($supported_os_type)
<div class="flex w-64 gap-2">Supported OS type: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
@@ -92,43 +41,101 @@
</svg></div>
@else
@if ($error)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-error" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<div class="flex w-64 gap-2">Server is reachable: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Proxy Started:" /></div>
<div class="w-64"><x-loading text="Server is reachable:" /></div>
@endif
@endif
@else
@if ($error)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-error"
@endif
@if ($uptime && $supported_os_type)
@if ($docker_installed)
<div class="flex w-64 gap-2">Docker is installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@else
<div class="w-64"><x-loading text="Docker Compose is installed:" /></div>
@if ($error)
<div class="flex w-64 gap-2">Docker is installed: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Docker is installed:" /></div>
@endif
@endif
@if ($docker_compose_installed)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@if ($proxy_started)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-success" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@else
@if ($error)
<div class="flex w-64 gap-2">Proxy Started: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Proxy Started:" /></div>
@endif
@endif
@else
@if ($error)
<div class="flex w-64 gap-2">Docker Compose is installed: <svg class="w-5 h-5 text-error"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M208.49 191.51a12 12 0 0 1-17 17L128 145l-63.51 63.49a12 12 0 0 1-17-17L111 128L47.51 64.49a12 12 0 0 1 17-17L128 111l63.51-63.52a12 12 0 0 1 17 17L145 128Z" />
</svg></div>
@else
<div class="w-64"><x-loading text="Docker Compose is installed:" /></div>
@endif
@endif
@endif
@isset($docker_version)
<div class="flex w-64 gap-2">Minimum Docker version installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@endisset
<livewire:new-activity-monitor header="Logs" />
@isset($error)
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>
@endisset
@endif
@isset($docker_version)
<div class="flex w-64 gap-2">Minimum Docker version installed: <svg class="w-5 h-5 text-success"
viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<g fill="currentColor">
<path
d="m237.66 85.26l-128.4 128.4a8 8 0 0 1-11.32 0l-71.6-72a8 8 0 0 1 0-11.31l24-24a8 8 0 0 1 11.32 0l36.68 35.32a8 8 0 0 0 11.32 0l92.68-91.32a8 8 0 0 1 11.32 0l24 23.6a8 8 0 0 1 0 11.31"
opacity=".2" />
<path
d="m243.28 68.24l-24-23.56a16 16 0 0 0-22.58 0L104 136l-.11-.11l-36.64-35.27a16 16 0 0 0-22.57.06l-24 24a16 16 0 0 0 0 22.61l71.62 72a16 16 0 0 0 22.63 0l128.4-128.38a16 16 0 0 0-.05-22.67M103.62 208L32 136l24-24l.11.11l36.64 35.27a16 16 0 0 0 22.52 0L208.06 56L232 79.6Z" />
</g>
</svg></div>
@endisset
<livewire:new-activity-monitor header="Logs" />
@isset($error)
<pre class="font-bold whitespace-pre-line text-error">{!! $error !!}</pre>
@endisset
</div>

View File

@@ -1,36 +1,11 @@
<?php
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Service\StartService;
use App\Http\Controllers\Api\Deploy;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag;
use App\Models\User;
use App\Providers\RouteServiceProvider;
use App\Http\Controllers\Api\Project;
use App\Http\Controllers\Api\Server;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Visus\Cuid2\Cuid2;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/
$middlewares = ['auth:sanctum'];
if (isDev()) {
$middlewares = [];
}
Route::get('/health', function () {
return 'OK';
@@ -45,44 +20,41 @@ Route::post('/feedback', function (Request $request) {
}
return response()->json(['message' => 'Feedback sent.'], 200);
});
// Route::group([
// 'middleware' => $middlewares,
// 'prefix' => 'v1'
// ], function () {
// Route::get('/deployments', function () {
// return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([
// "id",
// "server_id",
// "status"
// ])->groupBy("server_id")->map(function ($item) {
// return $item;
// })->toArray();
// });
// });
Route::group([
'middleware' => ['auth:sanctum'],
'prefix' => 'v1'
], function () {
Route::get('/version', function () {
return response(config('version'));
});
Route::get('/deploy', [Deploy::class, 'deploy']);
Route::get('/servers', [Server::class, 'servers']);
Route::get('/server/{uuid}', [Server::class, 'server_by_uuid']);
Route::get('/projects', [Project::class, 'projects']);
Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
});
Route::middleware(['throttle:5'])->group(function () {
Route::get('/unsubscribe/{token}', function () {
try {
$token = request()->token;
$email = decrypt($token);
if (!User::whereEmail($email)->exists()) {
return redirect(RouteServiceProvider::HOME);
}
if (User::whereEmail($email)->first()->marketing_emails === false) {
return 'You have already unsubscribed from marketing emails.';
}
User::whereEmail($email)->update(['marketing_emails' => false]);
return 'You have been unsubscribed from marketing emails.';
} catch (\Throwable $e) {
return 'Something went wrong. Please try again or contact support.';
}
})->name('unsubscribe.marketing.emails');
});
Route::get('/{any}', function () {
return response()->json(['error' => 'Not found.'], 404);
})->where('any', '.*');
// Route::middleware(['throttle:5'])->group(function () {
// Route::get('/unsubscribe/{token}', function () {
// try {
// $token = request()->token;
// $email = decrypt($token);
// if (!User::whereEmail($email)->exists()) {
// return redirect(RouteServiceProvider::HOME);
// }
// if (User::whereEmail($email)->first()->marketing_emails === false) {
// return 'You have already unsubscribed from marketing emails.';
// }
// User::whereEmail($email)->update(['marketing_emails' => false]);
// return 'You have been unsubscribed from marketing emails.';
// } catch (\Throwable $e) {
// return 'Something went wrong. Please try again or contact support.';
// }
// })->name('unsubscribe.marketing.emails');
// });

View File

@@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\Api\Server as ApiServer;
use App\Models\GitlabApp;
use App\Models\PrivateKey;
use App\Models\Server;
@@ -61,12 +62,13 @@ use App\Livewire\Security\PrivateKey\Show as SecurityPrivateKeyShow;
use App\Livewire\Server\Index as ServerIndex;
use App\Livewire\Server\Create as ServerCreate;
use App\Livewire\Server\Show as ServerShow;
use App\Livewire\Server\Resources as ResourcesShow;
use App\Livewire\Server\Destination\Show as DestinationShow;
use App\Livewire\Server\LogDrains;
use App\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
use App\Livewire\Server\Proxy\Show as ProxyShow;
use App\Livewire\Server\Proxy\Logs as ProxyLogs;
use App\Livewire\Source\Github\Change as GitHubChange;
use App\Livewire\Subscription\Index as SubscriptionIndex;
@@ -173,6 +175,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::prefix('server/{server_uuid}')->group(function () {
Route::get('/', ServerShow::class)->name('server.show');
Route::get('/resources', ResourcesShow::class)->name('server.resources');
Route::get('/proxy', ProxyShow::class)->name('server.proxy');
Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs');
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');

View File

@@ -11,7 +11,13 @@ DOCKER_VERSION="24.0"
CDN="https://cdn.coollabs.io/coolify"
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
if [ "$OS_TYPE" = "arch" ]; then
OS_VERSION="rolling"
else
OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
fi
LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',')
DATE=$(date +"%Y%m%d-%H%M%S")
@@ -21,11 +27,11 @@ if [ $EUID != 0 ]; then
fi
case "$OS_TYPE" in
ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed) ;;
*)
echo "This script only supports Debian, Redhat or Sles based operating systems for now."
exit
;;
arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed) ;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit
;;
esac
# Overwrite LATEST_VERSION if user pass a version number
@@ -48,33 +54,47 @@ echo -e "-------------"
echo "Installing required packages..."
case "$OS_TYPE" in
ubuntu | debian | raspbian)
apt update -y >/dev/null 2>&1
apt install -y curl wget git jq >/dev/null 2>&1
;;
centos | fedora | rhel | ol | rocky)
dnf install -y curl wget git jq >/dev/null 2>&1
;;
sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null 2>&1
zypper install -y curl wget git jq >/dev/null 2>&1
;;
*)
echo "This script only supports Debian, Redhat or Sles based operating systems for now."
exit
;;
arch)
pacman -Sy >/dev/null 2>&1 || true
if ! pacman -Q curl wget git jq >/dev/null 2>&1; then
pacman -S --noconfirm curl wget git jq >/dev/null 2>&1 || true
fi
;;
ubuntu | debian | raspbian)
apt update -y >/dev/null 2>&1
apt install -y curl wget git jq >/dev/null 2>&1
;;
centos | fedora | rhel | ol | rocky)
dnf install -y curl wget git jq >/dev/null 2>&1
;;
sles | opensuse-leap | opensuse-tumbleweed)
zypper refresh >/dev/null 2>&1
zypper install -y curl wget git jq >/dev/null 2>&1
;;
*)
echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now."
exit
;;
esac
# Detect OpenSSH server
SSH_DETECTED=false
if [ -x "$(command -v systemctl)" ]; then
if systemctl status sshd >/dev/null 2>&1; then
echo "OpenSSH server is installed and running."
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
if systemctl status ssh >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
elif [ -x "$(command -v service)" ]; then
if service sshd status >/dev/null 2>&1; then
echo "OpenSSH server is installed and running."
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
if service ssh status >/dev/null 2>&1; then
echo "OpenSSH server is installed."
SSH_DETECTED=true
fi
fi
@@ -105,22 +125,35 @@ fi
if ! [ -x "$(command -v docker)" ]; then
echo "Docker is not installed. Installing Docker."
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with Rancher script. Trying with official script."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
if [ "$OS_TYPE" = "arch" ]; then
pacman -Sy docker docker-compose --noconfirm
systemctl enable docker.service
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported."
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
echo "Failed to install Docker with pacman. Try to install it manually."
echo "Please visit https://wiki.archlinux.org/title/docker for more information."
exit
fi
else
curl https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with Rancher script. Trying with official script."
curl https://get.docker.com | sh -s -- --version ${DOCKER_VERSION}
if [ -x "$(command -v docker)" ]; then
echo "Docker installed successfully."
else
echo "Docker installation failed with official script."
echo "Maybe your OS is not supported?"
echo "Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue."
exit 1
fi
fi
fi
fi
echo -e "-------------"
echo -e "Check Docker Configuration..."
mkdir -p /etc/docker

View File

@@ -4,7 +4,7 @@
services:
directus:
image: directus/directus:10.7
image: directus/directus:10
volumes:
- directus-uploads:/directus/uploads
- directus-extensions:/directus/extensions

View File

@@ -0,0 +1,48 @@
# documentation: https://docs.docker.com/registry/
# slogan: The Docker Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images.
# tags: registry,images,docker
services:
registry:
image: registry:2
environment:
- SERVICE_FQDN_REGISTRY
- REGISTRY_AUTH=htpasswd
- REGISTRY_AUTH_HTPASSWD_REALM=Registry
- REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry.password
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/data
volumes:
- type: bind
source: ./auth/registry.password
target: /auth/registry.password
isDirectory: false
content: >-
testuser:$2y$05$/o2JvmI2bhExXIt6Oqxa7ekYB7v3scj1wFEf6tBslJvJOMoPQL.Gy
- type: bind
source: ./config/config.yml
target: /etc/docker/registry/config.yml
isDirectory: false
content: >-
version: 0.1
log:
fields:
service: registry
storage:
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
- type: bind
source: ./data
target: /data
isDirectory: true

View File

@@ -4,15 +4,20 @@
services:
filebrowser:
image: filebrowser/filebrowser:s6
image: filebrowser/filebrowser:latest
environment:
- SERVICE_FQDN_FILEBROWSER
- PUID=1000
- PGID=1000
volumes:
- filebrowser-srv:/srv
- filebrowser-database:/database/filebrowser.db
- filebrowser-config:/config/settings.json
- type: bind
source: ./srv
target: /srv
isDirectory: true
- ./database.db:/database.db
- type: bind
source: ./filebrowser.json
target: /.filebrowser.json
read_only: true
content: "{}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80"]
interval: 2s

View File

@@ -0,0 +1,35 @@
# documentation: https://www.metabase.com/docs/latest/installation-and-operation/running-metabase-on-docker
# slogan: Fast analytics with the friendly UX and integrated tooling to let your company explore data on their own.
# tags: analytics,bi,business,intelligence
services:
metabase:
image: metabase/metabase:latest
volumes:
- /dev/urandom:/dev/random:ro
environment:
- SERVICE_FQDN_METABASE
- MB_DB_TYPE=postgres
- MB_DB_HOST=postgresql
- MB_DB_PORT=5432
- MB_DB_DBNAME=${POSTGRESQL_DATABASE:-metabase}
- MB_DB_USER=$SERVICE_USER_POSTGRESQL
- MB_DB_PASS=$SERVICE_PASSWORD_POSTGRESQL
healthcheck:
test: curl --fail -I http://localhost:3000/api/health || exit 1
interval: 5s
timeout: 20s
retries: 10
postgresql:
image: postgres:16-alpine
volumes:
- metabase-postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- POSTGRES_DB=${POSTGRESQL_DATABASE:-metabase}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10

View File

@@ -0,0 +1,20 @@
# documentation: https://syncthing.net/
# slogan: Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how its transmitted over the internet.
# tags: filestorage, data, synchronization
services:
syncthing:
image: 'lscr.io/linuxserver/syncthing:latest'
environment:
- SERVICE_FQDN_SYNCTHING
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
volumes:
- 'syncthing-config:/config'
- 'syncthing-data1:/data1'
- 'syncthing-data2:/data2'
ports:
- '22000:22000/tcp'
- '22000:22000/udp'
- '21027:21027/udp'

View File

@@ -68,7 +68,7 @@
"directus-with-postgresql": {
"documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html",
"slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.",
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy11cGxvYWRzOi9kaXJlY3R1cy91cGxvYWRzJwogICAgICAtICdkaXJlY3R1cy1leHRlbnNpb25zOi9kaXJlY3R1cy9leHRlbnNpb25zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RJUkVDVFVTCiAgICAgIC0gS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9LRVkKICAgICAgLSBTRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X1NFQ1JFVAogICAgICAtICdBRE1JTl9FTUFJTD0ke0FETUlOX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIC0gQURNSU5fUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBEQl9DTElFTlQ9cG9zdGdyZXMKICAgICAgLSBEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1JUPTU0MzIKICAgICAgLSAnREJfREFUQUJBU0U9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1kaXJlY3R1c30nCiAgICAgIC0gREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUwKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtIFdFQlNPQ0tFVFNfRU5BQkxFRD10cnVlCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTYtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo3LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtdXBsb2FkczovZGlyZWN0dXMvdXBsb2FkcycKICAgICAgLSAnZGlyZWN0dXMtZXh0ZW5zaW9uczovZGlyZWN0dXMvZXh0ZW5zaW9ucycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9ESVJFQ1RVUwogICAgICAtIEtFWT0kU0VSVklDRV9CQVNFNjRfNjRfS0VZCiAgICAgIC0gU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF82NF9TRUNSRVQKICAgICAgLSAnQURNSU5fRU1BSUw9JHtBRE1JTl9FTUFJTDotYWRtaW5AZXhhbXBsZS5jb219JwogICAgICAtIEFETUlOX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX0FETUlOCiAgICAgIC0gREJfQ0xJRU5UPXBvc3RncmVzCiAgICAgIC0gREJfSE9TVD1wb3N0Z3Jlc3FsCiAgICAgIC0gREJfUE9SVD01NDMyCiAgICAgIC0gJ0RCX0RBVEFCQVNFPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgICAtIERCX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gREJfUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTAogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBSRURJU19QT1JUPTYzNzkKICAgICAgLSBXRUJTT0NLRVRTX0VOQUJMRUQ9dHJ1ZQogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LWRpcmVjdHVzfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6Ny1hbHBpbmUnCiAgICBjb21tYW5kOiAncmVkaXMtc2VydmVyIC0tYXBwZW5kb25seSB5ZXMnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy1yZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
"tags": [
"directus",
"cms",
@@ -87,6 +87,16 @@
"sql"
]
},
"docker-registry": {
"documentation": "https:\/\/docs.docker.com\/registry\/",
"slogan": "The Docker Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images.",
"compose": "c2VydmljZXM6CiAgcmVnaXN0cnk6CiAgICBpbWFnZTogJ3JlZ2lzdHJ5OjInCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fUkVHSVNUUlkKICAgICAgLSBSRUdJU1RSWV9BVVRIPWh0cGFzc3dkCiAgICAgIC0gUkVHSVNUUllfQVVUSF9IVFBBU1NXRF9SRUFMTT1SZWdpc3RyeQogICAgICAtIFJFR0lTVFJZX0FVVEhfSFRQQVNTV0RfUEFUSD0vYXV0aC9yZWdpc3RyeS5wYXNzd29yZAogICAgICAtIFJFR0lTVFJZX1NUT1JBR0VfRklMRVNZU1RFTV9ST09URElSRUNUT1JZPS9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9hdXRoL3JlZ2lzdHJ5LnBhc3N3b3JkCiAgICAgICAgdGFyZ2V0OiAvYXV0aC9yZWdpc3RyeS5wYXNzd29yZAogICAgICAgIGlzRGlyZWN0b3J5OiBmYWxzZQogICAgICAgIGNvbnRlbnQ6ICd0ZXN0dXNlcjokMnkkMDUkL28ySnZtSTJiaEV4WEl0Nk9xeGE3ZWtZQjd2M3NjajF3RkVmNnRCc2xKdkpPTW9QUUwuR3knCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NvbmZpZy9jb25maWcueW1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2RvY2tlci9yZWdpc3RyeS9jb25maWcueW1sCiAgICAgICAgaXNEaXJlY3Rvcnk6IGZhbHNlCiAgICAgICAgY29udGVudDogInZlcnNpb246IDAuMVxubG9nOlxuICBmaWVsZHM6XG4gICAgc2VydmljZTogcmVnaXN0cnlcbnN0b3JhZ2U6XG4gIGNhY2hlOlxuICAgIGJsb2JkZXNjcmlwdG9yOiBpbm1lbW9yeVxuICBmaWxlc3lzdGVtOlxuICAgIHJvb3RkaXJlY3Rvcnk6IC92YXIvbGliL3JlZ2lzdHJ5XG5odHRwOlxuICBhZGRyOiA6NTAwMFxuICBoZWFkZXJzOlxuICAgIFgtQ29udGVudC1UeXBlLU9wdGlvbnM6IFtub3NuaWZmXVxuaGVhbHRoOlxuICBzdG9yYWdlZHJpdmVyOlxuICAgIGVuYWJsZWQ6IHRydWVcbiAgICBpbnRlcnZhbDogMTBzXG4gICAgdGhyZXNob2xkOiAzIgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9kYXRhCiAgICAgICAgdGFyZ2V0OiAvZGF0YQogICAgICAgIGlzRGlyZWN0b3J5OiB0cnVlCg==",
"tags": [
"registry",
"images",
"docker"
]
},
"dokuwiki": {
"documentation": "https:\/\/www.dokuwiki.org\/faq",
"slogan": "A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.",
@@ -143,7 +153,7 @@
"filebrowser": {
"documentation": "https:\/\/filebrowser.org\/configuration",
"slogan": "FileBrowser is a self-hosted, web-based file manager and file explorer with a user-friendly interface. It allows you to manage and organize your files and directories directly from your web browser.",
"compose": "c2VydmljZXM6CiAgZmlsZWJyb3dzZXI6CiAgICBpbWFnZTogJ2ZpbGVicm93c2VyL2ZpbGVicm93c2VyOnM2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0ZJTEVCUk9XU0VSCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICB2b2x1bWVzOgogICAgICAtICdmaWxlYnJvd3Nlci1zcnY6L3NydicKICAgICAgLSAnZmlsZWJyb3dzZXItZGF0YWJhc2U6L2RhdGFiYXNlL2ZpbGVicm93c2VyLmRiJwogICAgICAtICdmaWxlYnJvd3Nlci1jb25maWc6L2NvbmZpZy9zZXR0aW5ncy5qc29uJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgwJwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
"compose": "c2VydmljZXM6CiAgZmlsZWJyb3dzZXI6CiAgICBpbWFnZTogJ2ZpbGVicm93c2VyL2ZpbGVicm93c2VyOmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9GSUxFQlJPV1NFUgogICAgdm9sdW1lczoKICAgICAgLQogICAgICAgIHR5cGU6IGJpbmQKICAgICAgICBzb3VyY2U6IC4vc3J2CiAgICAgICAgdGFyZ2V0OiAvc3J2CiAgICAgICAgaXNEaXJlY3Rvcnk6IHRydWUKICAgICAgLSAnLi9kYXRhYmFzZS5kYjovZGF0YWJhc2UuZGInCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2ZpbGVicm93c2VyLmpzb24KICAgICAgICB0YXJnZXQ6IC8uZmlsZWJyb3dzZXIuanNvbgogICAgICAgIHJlYWRfb25seTogdHJ1ZQogICAgICAgIGNvbnRlbnQ6ICd7fScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovL2xvY2FsaG9zdDo4MCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQo=",
"tags": [
"file-management",
"storage-access",
@@ -329,6 +339,17 @@
"meilisearch"
]
},
"metabase": {
"documentation": "https:\/\/www.metabase.com\/docs\/latest\/installation-and-operation\/running-metabase-on-docker",
"slogan": "Fast analytics with the friendly UX and integrated tooling to let your company explore data on their own.",
"compose": "c2VydmljZXM6CiAgbWV0YWJhc2U6CiAgICBpbWFnZTogJ21ldGFiYXNlL21ldGFiYXNlOmxhdGVzdCcKICAgIHZvbHVtZXM6CiAgICAgIC0gJy9kZXYvdXJhbmRvbTovZGV2L3JhbmRvbTpybycKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9NRVRBQkFTRQogICAgICAtIE1CX0RCX1RZUEU9cG9zdGdyZXMKICAgICAgLSBNQl9EQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBNQl9EQl9QT1JUPTU0MzIKICAgICAgLSAnTUJfREJfREJOQU1FPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotbWV0YWJhc2V9JwogICAgICAtIE1CX0RCX1VTRVI9JFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMCiAgICAgIC0gTUJfREJfUEFTUz0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogJ2N1cmwgLS1mYWlsIC1JIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9hcGkvaGVhbHRoIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHBvc3RncmVzcWw6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ21ldGFiYXNlLXBvc3RncmVzcWwtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1BPU1RHUkVTUUxfREFUQUJBU0U6LW1ldGFiYXNlfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAncGdfaXNyZWFkeSAtVSAkJHtQT1NUR1JFU19VU0VSfSAtZCAkJHtQT1NUR1JFU19EQn0nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
"tags": [
"analytics",
"bi",
"business",
"intelligence"
]
},
"metube": {
"documentation": "https:\/\/github.com\/alexta69\/metube",
"slogan": "A web GUI for youtube-dl with playlist support. It enables you to effortlessly download videos from YouTube and dozens of other sites.",
@@ -492,6 +513,16 @@
"tool"
]
},
"syncthing": {
"documentation": "https:\/\/syncthing.net\/",
"slogan": "Syncthing is a continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it\u2019s transmitted over the internet.",
"compose": "c2VydmljZXM6CiAgc3luY3RoaW5nOgogICAgaW1hZ2U6ICdsc2NyLmlvL2xpbnV4c2VydmVyL3N5bmN0aGluZzpsYXRlc3QnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fU1lOQ1RISU5HCiAgICAgIC0gUFVJRD0xMDAwCiAgICAgIC0gUEdJRD0xMDAwCiAgICAgIC0gVFo9RXRjL1VUQwogICAgdm9sdW1lczoKICAgICAgLSAnc3luY3RoaW5nLWNvbmZpZzovY29uZmlnJwogICAgICAtICdzeW5jdGhpbmctZGF0YTE6L2RhdGExJwogICAgICAtICdzeW5jdGhpbmctZGF0YTI6L2RhdGEyJwogICAgcG9ydHM6CiAgICAgIC0gJzIyMDAwOjIyMDAwL3RjcCcKICAgICAgLSAnMjIwMDA6MjIwMDAvdWRwJwogICAgICAtICcyMTAyNzoyMTAyNy91ZHAnCg==",
"tags": [
"filestorage",
"data",
"synchronization"
]
},
"trigger-with-external-database": {
"documentation": "https:\/\/trigger.dev\/docs\/documentation\/guides\/self-hosting",
"slogan": "The open source Background Jobs framework for TypeScript",

View File

@@ -1,6 +1,6 @@
<?php
it('ConvertCapAdd', function () {
test('ConvertCapAdd', function () {
$input = '--cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add SYS_ADMIN';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([
@@ -8,7 +8,7 @@ it('ConvertCapAdd', function () {
])->ray();
});
it('ConvertPrivilegedAndInit', function () {
test('ConvertPrivilegedAndInit', function () {
$input = '---privileged --init';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([
@@ -17,7 +17,7 @@ it('ConvertPrivilegedAndInit', function () {
])->ray();
});
it('ConvertUlimit', function () {
test('ConvertUlimit', function () {
$input = '--ulimit nofile=262144:262144';
$output = convert_docker_run_to_compose($input);
expect($output)->toBe([

View File

@@ -1,5 +1,4 @@
<?php
/*
|--------------------------------------------------------------------------
| Test Case
@@ -10,11 +9,7 @@
| need to change it using the "uses()" function to bind a different classes or traits.
|
*/
uses(
// Tests\TestCase::class,
// Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');
uses(Tests\TestCase::class)->in('Feature');
/*
|--------------------------------------------------------------------------
@@ -27,9 +22,9 @@ uses(
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
// expect()->extend('toBeOne', function () {
// return $this->toBe(1);
// });
/*
|--------------------------------------------------------------------------
@@ -42,7 +37,7 @@ expect()->extend('toBeOne', function () {
|
*/
function something()
{
// ..
}
// function something()
// {
// // ..
// }

View File

@@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.214"
"version": "4.0.0-beta.221"
}
}
}