Compare commits

...

24 Commits

Author SHA1 Message Date
Andras Bacsai
ab57a5d8ef Merge pull request #1331 from coollabsio/next
v4.0.0-beta.87
2023-10-17 12:14:03 +02:00
Andras Bacsai
f5ae222a6e fix: add internal domain names during build process 2023-10-17 11:23:49 +02:00
Andras Bacsai
5d95d8b79a fix: cancel any deployments + queue next 2023-10-17 11:10:33 +02:00
Andras Bacsai
fbb5f2ca2e fix: generate fqdn if you deleted a service app, but it requires fqdn 2023-10-17 10:37:39 +02:00
Andras Bacsai
16cbca36c1 add trademark policy 2023-10-17 10:37:26 +02:00
Andras Bacsai
24a578bedb Merge pull request #1327 from theh2so4/main
[+] Update
2023-10-17 10:31:25 +02:00
Andras Bacsai
36dc479772 fix: service status check is a bit better 2023-10-17 10:17:03 +02:00
Andras Bacsai
83d6e488e4 Merge pull request #1330 from seii/fix/raspbian-support
Add Raspbian support to install.sh
2023-10-17 09:50:57 +02:00
Seii
a4d358d512 Add Raspbian support to install.sh 2023-10-16 22:11:51 -06:00
TheH2SO4
c0c197101d [+] Update
💄 **Styling**:

-> ℹ️ **Alphabetical order**: Changed the order of the templates and set them on a alphabetical order.
-> ℹ️ **More accurate descriptions**: Created more accurate descriptions for the templates: `Appsmith, Appwrite, Fider, Ghost, Umami` and `Uptime Kuma`.
2023-10-15 23:48:33 +02:00
Andras Bacsai
62e39ccc7f Merge pull request #1326 from coollabsio/next
v4.0.0-beta.86
2023-10-15 16:56:21 +02:00
Andras Bacsai
a88a016137 fix: build image before starting dockerfile buildpacks 2023-10-15 16:54:16 +02:00
Andras Bacsai
f4c8986ab3 Merge pull request #1324 from coollabsio/next
v4.0.0-beta.85
2023-10-14 17:40:08 +02:00
Andras Bacsai
e286eae53b Merge pull request #1322 from scmmishra/fix/redis-default-url
fix: generated redis URL string
2023-10-14 17:36:48 +02:00
Andras Bacsai
bc3e59e4ef fix: delete resource if there was an error
fix: do not refresh on containerStatusJob (db view)
2023-10-14 17:26:16 +02:00
Andras Bacsai
17ebc650c9 Merge pull request #1323 from coollabsio/next
v4.0.0-beta.84
2023-10-14 14:24:20 +02:00
Andras Bacsai
0ef386b4a8 fix: stopping a resource is now job based
ui: show status on project
2023-10-14 14:22:07 +02:00
Shivam Mishra
26fce85bb0 fix: redis URL generated 2023-10-14 12:10:12 +05:30
Andras Bacsai
2a079e3365 Merge pull request #1321 from coollabsio/next
v4.0.0-beta.83
2023-10-13 21:54:26 +02:00
Andras Bacsai
5fb5ed75c4 Merge pull request #1315 from scmmishra/patch-1
fix: docker hub URL for redis
2023-10-13 21:46:46 +02:00
Andras Bacsai
a2008fe9d1 typo 2023-10-13 21:44:04 +02:00
Andras Bacsai
3c96485e3d fix: custom dockerfile location for dockerfile buildpack
fix: able to use custom port for git cloning
2023-10-13 21:39:49 +02:00
Andras Bacsai
1c97d47ea0 fix: turn off static deployment if you switch buildpacks 2023-10-13 21:08:59 +02:00
Shivam Mishra
8f8f5878dd fix: docker hub URL 2023-10-13 22:34:46 +05:30
37 changed files with 387 additions and 242 deletions

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Actions\Application;
use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
{
use AsAction;
public function handle(Application $application)
{
$server = $application->destination->server;
$containers = getCurrentApplicationContainerStatus($server, $application->id);
if ($containers->count() > 0) {
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$server
);
}
}
// TODO: make notification for application
// $application->environment->project->team->notify(new StatusChanged($application));
}
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
$internalPort = null;
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
$internalPort = 6379;
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
$internalPort = 5432;
}
$containerName = "{$database->uuid}-proxy";
$configuration_dir = database_proxy_dir($database->uuid);
$nginxconf = <<<EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
stream {
server {
listen $database->public_port;
proxy_pass $database->uuid:$internalPort;
}
}
EOF;
$dockerfile = <<< EOF
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$containerName => [
'build' => [
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'container_name' => $containerName,
'restart' => RESTART_MODE,
'ports' => [
"$database->public_port:$database->public_port",
],
'networks' => [
$database->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'stat /etc/nginx/nginx.conf || exit 1',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 3,
'start_period' => '1s'
],
]
],
'networks' => [
$database->destination->network => [
'external' => true,
'name' => $database->destination->network,
'attachable' => true,
]
]
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
], $database->destination->server);
}
}

View File

@@ -6,15 +6,18 @@ use App\Models\Server;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
class StartPostgresql class StartPostgresql
{ {
use AsAction;
public StandalonePostgresql $database; public StandalonePostgresql $database;
public array $commands = []; public array $commands = [];
public array $init_scripts = []; public array $init_scripts = [];
public string $configuration_dir; public string $configuration_dir;
public function __invoke(Server $server, StandalonePostgresql $database) public function handle(Server $server, StandalonePostgresql $database)
{ {
$this->database = $database; $this->database = $database;
$container_name = $this->database->uuid; $container_name = $this->database->uuid;

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabase
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
$server = $database->destination->server;
instant_remote_process(
["docker rm -f {$database->uuid}"],
$server
);
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
// TODO: make notification for services
// $database->environment->project->team->notify(new StatusChanged($database));
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Lorisleiva\Actions\Concerns\AsAction;
class StopDatabaseProxy
{
use AsAction;
public function handle(StandaloneRedis|StandalonePostgresql $database)
{
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
$database->is_public = false;
$database->save();
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction; use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service; use App\Models\Service;
use App\Notifications\Application\StatusChanged;
class StopService class StopService
{ {
@@ -22,5 +23,7 @@ class StopService
} }
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false); instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false); instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
} }
} }

View File

@@ -60,12 +60,16 @@ class DeploymentNavbar extends Component
$previous_logs[] = $new_log_entry; $previous_logs[] = $new_log_entry;
$this->application_deployment_queue->update([ $this->application_deployment_queue->update([
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR), 'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]); ]);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally {
$this->application_deployment_queue->update([
'current_process_id' => null,
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
]);
queue_next_deployment($this->application);
} }
} }
} }

View File

@@ -77,6 +77,10 @@ class General extends Component
]; ];
public function updatedApplicationBuildPack(){ public function updatedApplicationBuildPack(){
if ($this->application->build_pack !== 'nixpacks') {
$this->application->settings->is_static = $this->is_static = false;
$this->application->settings->save();
}
$this->submit(); $this->submit();
} }
public function instantSave() public function instantSave()

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Actions\Application\StopApplication;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use Livewire\Component; use Livewire\Component;
@@ -59,22 +60,9 @@ class Heading extends Component
public function stop() public function stop()
{ {
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id); StopApplication::run($this->application);
if ($containers->count() === 0) { $this->application->status = 'exited';
return; $this->application->save();
}
foreach ($containers as $container) {
$containerName = data_get($container, 'Names');
if ($containerName) {
instant_remote_process(
["docker rm -f {$containerName}"],
$this->application->destination->server
);
$this->application->status = 'exited';
$this->application->save();
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
}
$this->application->refresh(); $this->application->refresh();
} }
} }

View File

@@ -4,6 +4,7 @@ namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis; use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use Livewire\Component; use Livewire\Component;
@@ -27,7 +28,6 @@ class Heading extends Component
{ {
dispatch_sync(new ContainerStatusJob($this->database->destination->server)); dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh(); $this->database->refresh();
$this->emit('refresh');
} }
public function mount() public function mount()
@@ -37,24 +37,16 @@ class Heading extends Component
public function stop() public function stop()
{ {
instant_remote_process( StopDatabase::run($this->database);
["docker rm -f {$this->database->uuid}"],
$this->database->destination->server
);
if ($this->database->is_public) {
stopDatabaseProxy($this->database);
$this->database->is_public = false;
}
$this->database->status = 'exited'; $this->database->status = 'exited';
$this->database->save(); $this->database->save();
$this->check_status(); $this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
} }
public function start() public function start()
{ {
if ($this->database->type() === 'standalone-postgresql') { if ($this->database->type() === 'standalone-postgresql') {
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database); $activity = StartPostgresql::run($this->database->destination->server, $this->database);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
if ($this->database->type() === 'standalone-redis') { if ($this->database->type() === 'standalone-redis') {

View File

@@ -2,6 +2,8 @@
namespace App\Http\Livewire\Project\Database\Postgresql; namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Exception; use Exception;
use Livewire\Component; use Livewire\Component;
@@ -67,10 +69,10 @@ class General extends Component
} }
if ($this->database->is_public) { if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...'); $this->emit('success', 'Starting TCP proxy...');
startDatabaseProxy($this->database); StartDatabaseProxy::run($this->database);
$this->emit('success', 'Database is now publicly accessible.'); $this->emit('success', 'Database is now publicly accessible.');
} else { } else {
stopDatabaseProxy($this->database); StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.'); $this->emit('success', 'Database is no longer publicly accessible.');
} }
$this->getDbUrl(); $this->getDbUrl();

View File

@@ -2,6 +2,8 @@
namespace App\Http\Livewire\Project\Database\Redis; namespace App\Http\Livewire\Project\Database\Redis;
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
use App\Models\StandaloneRedis; use App\Models\StandaloneRedis;
use Exception; use Exception;
use Livewire\Component; use Livewire\Component;
@@ -55,10 +57,10 @@ class General extends Component
} }
if ($this->database->is_public) { if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...'); $this->emit('success', 'Starting TCP proxy...');
startDatabaseProxy($this->database); StartDatabaseProxy::run($this->database);
$this->emit('success', 'Database is now publicly accessible.'); $this->emit('success', 'Database is now publicly accessible.');
} else { } else {
stopDatabaseProxy($this->database); StopDatabaseProxy::run($this->database);
$this->emit('success', 'Database is no longer publicly accessible.'); $this->emit('success', 'Database is no longer publicly accessible.');
} }
$this->getDbUrl(); $this->getDbUrl();
@@ -80,9 +82,9 @@ class General extends Component
public function getDbUrl() { public function getDbUrl() {
if ($this->database->is_public) { if ($this->database->is_public) {
$this->db_url = "redis://{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0"; $this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
} else { } else {
$this->db_url = "redis://{$this->database->redis_password}@{$this->database->uuid}:5432/0"; $this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
} }
} }
public function render() public function render()

View File

@@ -98,7 +98,6 @@ class GithubPrivateRepositoryDeployKey extends Component
'name' => generate_random_name(), 'name' => generate_random_name(),
'git_repository' => $this->git_repository, 'git_repository' => $this->git_repository,
'git_branch' => $this->branch, 'git_branch' => $this->branch,
'git_full_url' => $this->git_repository,
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',
'ports_exposes' => $this->port, 'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory, 'publish_directory' => $this->publish_directory,
@@ -112,7 +111,6 @@ class GithubPrivateRepositoryDeployKey extends Component
'name' => generate_random_name(), 'name' => generate_random_name(),
'git_repository' => $this->git_repository, 'git_repository' => $this->git_repository,
'git_branch' => $this->branch, 'git_branch' => $this->branch,
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
'build_pack' => 'nixpacks', 'build_pack' => 'nixpacks',
'ports_exposes' => $this->port, 'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory, 'publish_directory' => $this->publish_directory,
@@ -158,6 +156,8 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->git_host = $this->repository_url_parsed->getHost(); $this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git'); $this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
} else {
$this->git_repository = $this->repository_url;
} }
$this->git_source = 'other'; $this->git_source = 'other';
} }

View File

@@ -34,5 +34,6 @@ class Navbar extends Component
StopService::run($this->service); StopService::run($this->service);
$this->service->refresh(); $this->service->refresh();
$this->emit('success', 'Service stopped successfully.'); $this->emit('success', 'Service stopped successfully.');
$this->checkStatus();
} }
} }

View File

@@ -6,6 +6,7 @@ use Livewire\Component;
class StackForm extends Component class StackForm extends Component
{ {
public $service;
protected $listeners = ["saveCompose"]; protected $listeners = ["saveCompose"];
protected $rules = [ protected $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
@@ -13,7 +14,6 @@ class StackForm extends Component
'service.name' => 'required', 'service.name' => 'required',
'service.description' => 'nullable', 'service.description' => 'nullable',
]; ];
public $service;
public function saveCompose($raw) public function saveCompose($raw)
{ {
$this->service->docker_compose_raw = $raw; $this->service->docker_compose_raw = $raw;

View File

@@ -2,7 +2,7 @@
namespace App\Http\Livewire\Project\Shared; namespace App\Http\Livewire\Project\Shared;
use App\Actions\Service\StopService; use App\Jobs\StopResourceJob;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@@ -10,7 +10,7 @@ class Danger extends Component
{ {
public $resource; public $resource;
public array $parameters; public array $parameters;
public string|null $modalId = null; public ?string $modalId = null;
public function mount() public function mount()
{ {
@@ -20,22 +20,8 @@ class Danger extends Component
public function delete() public function delete()
{ {
// Should be queued
try { try {
if ($this->resource->type() === 'service') { StopResourceJob::dispatchSync($this->resource);
$server = $this->resource->server;
StopService::run($this->resource);
} else {
$destination = data_get($this->resource, 'destination');
if ($destination) {
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
$server = $destination->server;
}
if ($this->resource->destination->server->isFunctional()) {
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
}
}
$this->resource->delete();
return redirect()->route('project.resources', [ return redirect()->route('project.resources', [
'project_uuid' => $this->parameters['project_uuid'], 'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'] 'environment_name' => $this->parameters['environment_name']

View File

@@ -67,7 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private $docker_compose; private $docker_compose;
private $docker_compose_base64; private $docker_compose_base64;
private string $dockerfile_location = '/Dockerfile'; private string $dockerfile_location = '/Dockerfile';
private ?string $addHosts = null;
private $log_model; private $log_model;
private Collection $saved_outputs; private Collection $saved_outputs;
@@ -98,7 +98,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
ray($this->basedir, $this->workdir);
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
savePrivateKeyToFs($this->server); savePrivateKeyToFs($this->server);
$this->saved_outputs = collect(); $this->saved_outputs = collect();
@@ -138,6 +137,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->update([ $this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]); ]);
// Generate custom host<->ip mapping
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
$allContainers = format_docker_command_output_to_json($allContainers);
$ips = collect([]);
if (count($allContainers) > 0) {
$allContainers = $allContainers[0];
foreach ($allContainers as $container) {
$containerName = data_get($container, 'Name');
if ($containerName === 'coolify-proxy') {
continue;
}
$containerIp = data_get($container, 'IPv4Address');
if ($containerName && $containerIp) {
$containerIp = str($containerIp)->before('/');
$ips->put($containerName, $containerIp->value());
}
}
}
$this->addHosts = $ips->map(function ($ip, $name) {
return "--add-host $name:$ip";
})->implode(' ');
try { try {
if ($this->application->dockerfile) { if ($this->application->dockerfile) {
$this->deploy_simple_dockerfile(); $this->deploy_simple_dockerfile();
@@ -298,7 +320,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_compose_file(); $this->generate_compose_file();
$this->generate_build_env_variables(); $this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile(); $this->add_build_env_variables_to_dockerfile();
// $this->build_image(); $this->build_image();
$this->rolling_update(); $this->rolling_update();
} }
private function deploy_nixpacks_buildpack() private function deploy_nixpacks_buildpack()
@@ -510,8 +532,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
} }
if ($this->application->deploymentType() === 'deploy_key') { if ($this->application->deploymentType() === 'deploy_key') {
$port = 22;
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
if (count($matches) === 1) {
$port = $matches[0];
}
$private_key = base64_encode($this->application->private_key->private_key); $private_key = base64_encode($this->application->private_key->private_key);
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->basedir}"; $git_clone_command = "GIT_SSH_COMMAND=\"ssh -p $port -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([ $commands = collect([
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"), executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
@@ -663,12 +690,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if ($this->build_pack === 'dockerfile') { // if ($this->build_pack === 'dockerfile') {
$docker_compose['services'][$this->container_name]['build'] = [ // $docker_compose['services'][$this->container_name]['build'] = [
'context' => $this->workdir, // 'context' => $this->workdir,
'dockerfile' => $this->workdir . $this->dockerfile_location, // 'dockerfile' => $this->workdir . $this->dockerfile_location,
]; // ];
} // }
$this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose); $this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
@@ -761,7 +788,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->application->settings->is_static) { if ($this->application->settings->is_static) {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]); ]);
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
@@ -794,12 +821,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
], ],
[ [
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
] ]
); );
} else { } else {
ray("docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}");
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]); ]);
} }
} }

View File

@@ -47,7 +47,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function handle() public function handle()
{ {
try { try {
ray("checking server status for {$this->server->id}"); // ray("checking server status for {$this->server->id}");
// ray()->clearAll(); // ray()->clearAll();
$serverUptimeCheckNumber = $this->server->unreachable_count; $serverUptimeCheckNumber = $this->server->unreachable_count;
$serverUptimeCheckNumberMax = 3; $serverUptimeCheckNumberMax = 3;

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Jobs;
use App\Actions\Application\StopApplication;
use App\Actions\Database\StopDatabase;
use App\Actions\Service\StopService;
use App\Models\Application;
use App\Models\Service;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
{
}
public function handle()
{
try {
$server = $this->resource->destination->server;
if (!$server->isFunctional()) {
return 'Server is not functional';
}
switch ($this->resource->type()) {
case 'application':
StopApplication::run($this->resource);
break;
case 'standalone-postgresql':
StopDatabase::run($this->resource);
break;
case 'standalone-redis':
StopDatabase::run($this->resource);
break;
case 'service':
StopService::run($this->resource);
break;
}
} catch (\Throwable $e) {
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
throw $e;
} finally {
$this->resource->delete();
}
}
}

View File

@@ -32,16 +32,8 @@ class Application extends BaseModel
]); ]);
}); });
static::deleting(function ($application) { static::deleting(function ($application) {
// Stop Container
if ($application->destination->server->isFunctional()) {
instant_remote_process(
["docker rm -f {$application->uuid}"],
$application->destination->server,
false
);
}
$application->settings()->delete(); $application->settings()->delete();
$storages = $application->persistentStorages()->get(); $storages = $application->persistentStorages()->get();
foreach ($storages as $storage) { foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false); instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false);
} }

View File

@@ -19,7 +19,6 @@ class Service extends BaseModel
static::deleting(function ($service) { static::deleting(function ($service) {
$storagesToDelete = collect([]); $storagesToDelete = collect([]);
foreach ($service->applications()->get() as $application) { foreach ($service->applications()->get() as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
$storages = $application->persistentStorages()->get(); $storages = $application->persistentStorages()->get();
foreach ($storages as $storage) { foreach ($storages as $storage) {
$storagesToDelete->push($storage); $storagesToDelete->push($storage);
@@ -27,7 +26,6 @@ class Service extends BaseModel
$application->persistentStorages()->delete(); $application->persistentStorages()->delete();
} }
foreach ($service->databases()->get() as $database) { foreach ($service->databases()->get() as $database) {
instant_remote_process(["docker rm -f {$database->name}-{$service->uuid}"], $service->server, false);
$storages = $database->persistentStorages()->get(); $storages = $database->persistentStorages()->get();
foreach ($storages as $storage) { foreach ($storages as $storage) {
$storagesToDelete->push($storage); $storagesToDelete->push($storage);
@@ -384,7 +382,7 @@ class Service extends BaseModel
$value = Str::of($variable); $value = Str::of($variable);
} }
if ($key->startsWith('SERVICE_FQDN')) { if ($key->startsWith('SERVICE_FQDN')) {
if ($isNew) { if ($isNew || $savedService->fqdn === null) {
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}"); $fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}");
if (substr_count($key->value(), '_') === 3) { if (substr_count($key->value(), '_') === 3) {

View File

@@ -29,21 +29,13 @@ class StandalonePostgresql extends BaseModel
]); ]);
}); });
static::deleting(function ($database) { static::deleting(function ($database) {
// Stop Container $storages = $database->persistentStorages()->get();
instant_remote_process( foreach ($storages as $storage) {
["docker rm -f {$database->uuid}"], instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
$database->destination->server,
false
);
// Stop TCP Proxy
if ($database->is_public) {
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false);
} }
$database->scheduledBackups()->delete(); $database->scheduledBackups()->delete();
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
// Remove Volume
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
}); });
} }

View File

@@ -24,21 +24,13 @@ class StandaloneRedis extends BaseModel
]); ]);
}); });
static::deleting(function ($database) { static::deleting(function ($database) {
// Stop Container
instant_remote_process(
["docker rm -f {$database->uuid}"],
$database->destination->server,
false
);
// Stop TCP Proxy
if ($database->is_public) {
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false);
}
$database->scheduledBackups()->delete(); $database->scheduledBackups()->delete();
$storages = $database->persistentStorages()->get();
foreach ($storages as $storage) {
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
}
$database->persistentStorages()->delete(); $database->persistentStorages()->delete();
$database->environment_variables()->delete(); $database->environment_variables()->delete();
// Remove Volume
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
}); });
} }

View File

@@ -15,25 +15,23 @@ class StatusChanged extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public Application $application; public string $resource_name;
public string $application_name;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public ?string $application_url = null; public ?string $resource_url = null;
public ?string $fqdn; public ?string $fqdn;
public function __construct($application) public function __construct(public Application $resource)
{ {
$this->application = $application; $this->resource_name = data_get($resource, 'name');
$this->application_name = data_get($application, 'name'); $this->project_uuid = data_get($resource, 'environment.project.uuid');
$this->project_uuid = data_get($application, 'environment.project.uuid'); $this->environment_name = data_get($resource, 'environment.name');
$this->environment_name = data_get($application, 'environment.name'); $this->fqdn = data_get($resource, 'fqdn', null);
$this->fqdn = data_get($application, 'fqdn', null);
if (Str::of($this->fqdn)->explode(',')->count() > 1) { if (Str::of($this->fqdn)->explode(',')->count() > 1) {
$this->fqdn = Str::of($this->fqdn)->explode(',')->first(); $this->fqdn = Str::of($this->fqdn)->explode(',')->first();
} }
$this->application_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}"; $this->resource_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->resource->uuid}";
} }
public function via(object $notifiable): array public function via(object $notifiable): array
@@ -45,32 +43,32 @@ class StatusChanged extends Notification implements ShouldQueue
{ {
$mail = new MailMessage(); $mail = new MailMessage();
$fqdn = $this->fqdn; $fqdn = $this->fqdn;
$mail->subject("Coolify: {$this->application_name} has been stopped"); $mail->subject("Coolify: {$this->resource_name} has been stopped");
$mail->view('emails.application-status-changes', [ $mail->view('emails.application-status-changes', [
'name' => $this->application_name, 'name' => $this->resource_name,
'fqdn' => $fqdn, 'fqdn' => $fqdn,
'application_url' => $this->application_url, 'resource_url' => $this->resource_url,
]); ]);
return $mail; return $mail;
} }
public function toDiscord(): string public function toDiscord(): string
{ {
$message = 'Coolify: ' . $this->application_name . ' has been stopped. $message = 'Coolify: ' . $this->resource_name . ' has been stopped.
'; ';
$message .= '[Open Application in Coolify](' . $this->application_url . ')'; $message .= '[Open Application in Coolify](' . $this->resource_url . ')';
return $message; return $message;
} }
public function toTelegram(): array public function toTelegram(): array
{ {
$message = 'Coolify: ' . $this->application_name . ' has been stopped.'; $message = 'Coolify: ' . $this->resource_name . ' has been stopped.';
return [ return [
"message" => $message, "message" => $message,
"buttons" => [ "buttons" => [
[ [
"text" => "Open Application in Coolify", "text" => "Open Application in Coolify",
"url" => $this->application_url "url" => $this->resource_url
] ]
], ],
]; ];

View File

@@ -2,8 +2,6 @@
use App\Actions\Proxy\SaveConfiguration; use App\Actions\Proxy\SaveConfiguration;
use App\Models\Server; use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
function get_proxy_path() function get_proxy_path()
@@ -187,87 +185,3 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
} }
} }
} }
function startDatabaseProxy(StandalonePostgresql|StandaloneRedis $database)
{
$internalPort = null;
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
$internalPort = 6379;
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
$internalPort = 5432;
}
$containerName = "{$database->uuid}-proxy";
$configuration_dir = database_proxy_dir($database->uuid);
$nginxconf = <<<EOF
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
events {
worker_connections 1024;
}
stream {
server {
listen $database->public_port;
proxy_pass $database->uuid:$internalPort;
}
}
EOF;
$dockerfile = <<< EOF
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
EOF;
$docker_compose = [
'version' => '3.8',
'services' => [
$containerName => [
'build' => [
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'container_name' => $containerName,
'restart' => RESTART_MODE,
'ports' => [
"$database->public_port:$database->public_port",
],
'networks' => [
$database->destination->network,
],
'healthcheck' => [
'test' => [
'CMD-SHELL',
'stat /etc/nginx/nginx.conf || exit 1',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 3,
'start_period' => '1s'
],
]
],
'networks' => [
$database->destination->network => [
'external' => true,
'name' => $database->destination->network,
'attachable' => true,
]
]
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
], $database->destination->server);
}
function stopDatabaseProxy(StandalonePostgresql|StandaloneRedis $database)
{
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
}

View File

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

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.82'; return '4.0.0-beta.87';

View File

@@ -23,6 +23,7 @@ return new class extends Migration
$table->string('git_repository'); $table->string('git_repository');
$table->string('git_branch'); $table->string('git_branch');
$table->string('git_commit_sha')->default('HEAD'); $table->string('git_commit_sha')->default('HEAD');
// TODO: remove this column, it is not used
$table->string('git_full_url')->nullable(); $table->string('git_full_url')->nullable();
$table->string('docker_registry_image_name')->nullable(); $table->string('docker_registry_image_name')->nullable();

View File

@@ -7,6 +7,6 @@
@endif @endif
@if (data_get($application_deployment_queue, 'status') === 'in_progress' || @if (data_get($application_deployment_queue, 'status') === 'in_progress' ||
data_get($application_deployment_queue, 'status') === 'queued') data_get($application_deployment_queue, 'status') === 'queued')
<x-forms.button wire:click.prevent="cancel">Cancel deployment</x-forms.button> <x-forms.button wire:click.prevent="cancel">Cancel Deployment</x-forms.button>
@endif @endif
</div> </div>

View File

@@ -36,7 +36,7 @@
</div> </div>
@isset($application->private_key_id) @isset($application->private_key_id)
<h3 class="pt-4">Deploy Key</h3> <h3 class="pt-4">Deploy Key</h3>
<div class="py-2 pt-4">Currently attache Private Key: <span <div class="py-2 pt-4">Currently attached Private Key: <span
class="text-warning">{{ $application->private_key->name }}</span> class="text-warning">{{ $application->private_key->name }}</span>
</div> </div>

View File

@@ -10,7 +10,7 @@
<x-forms.input label="Name" id="database.name" /> <x-forms.input label="Name" id="database.name" />
<x-forms.input label="Description" id="database.description" /> <x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required <x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/postgres'>https://hub.docker.com/_/postgres</a>" /> helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>

View File

@@ -152,6 +152,9 @@
@endforeach @endforeach
@endif @endif
</div> </div>
<div class="py-4 pb-10">Trademarks Policy: The respective trademarks mentioned here are owned by the
respective
companies, and use of them does not imply any affiliation or endorsement.</div>
@endif @endif
@if ($current_step === 'servers') @if ($current_step === 'servers')
<ul class="pb-10 steps"> <ul class="pb-10 steps">

View File

@@ -1,4 +1,4 @@
<div x-init="$wire.checkStatus"> <div x-init="$wire.checkStatus" wire:poll.2500ms='checkStatus'>
<livewire:project.service.modal /> <livewire:project.service.modal />
<h1>Configuration</h1> <h1>Configuration</h1>
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />

View File

@@ -38,30 +38,48 @@
@endif @endif
<div class="grid gap-2 lg:grid-cols-2"> <div class="grid gap-2 lg:grid-cols-2">
@foreach ($environment->applications->sortBy('name') as $application) @foreach ($environment->applications->sortBy('name') as $application)
<a class="box group" <a class="relative box group"
href="{{ route('project.application.configuration', [$project->uuid, $environment->name, $application->uuid]) }}"> href="{{ route('project.application.configuration', [$project->uuid, $environment->name, $application->uuid]) }}">
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $application->name }}</div> <div class="font-bold text-white">{{ $application->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $application->description }}</div> <div class="text-xs text-gray-400 group-hover:text-white">{{ $application->description }}</div>
</div> </div>
@if (Str::of(data_get($application, 'status'))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($application, 'status'))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@endif
</a> </a>
@endforeach @endforeach
@foreach ($environment->databases()->sortBy('name') as $databases) @foreach ($environment->databases()->sortBy('name') as $database)
<a class="box group" <a class="relative box group"
href="{{ route('project.database.configuration', [$project->uuid, $environment->name, $databases->uuid]) }}"> href="{{ route('project.database.configuration', [$project->uuid, $environment->name, $database->uuid]) }}">
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $databases->name }}</div> <div class="font-bold text-white">{{ $database->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $databases->description }}</div> <div class="text-xs text-gray-400 group-hover:text-white">{{ $database->description }}</div>
</div> </div>
@if (Str::of(data_get($database, 'status'))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(data_get($database, 'status'))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@endif
</a> </a>
@endforeach @endforeach
@foreach ($environment->services->sortBy('name') as $service) @foreach ($environment->services->sortBy('name') as $service)
<a class="box group" <a class="relative box group"
href="{{ route('project.service', [$project->uuid, $environment->name, $service->uuid]) }}"> href="{{ route('project.service', [$project->uuid, $environment->name, $service->uuid]) }}">
<div class="flex flex-col mx-6"> <div class="flex flex-col mx-6">
<div class="font-bold text-white">{{ $service->name }}</div> <div class="font-bold text-white">{{ $service->name }}</div>
<div class="text-xs text-gray-400 group-hover:text-white">{{ $service->description }}</div> <div class="text-xs text-gray-400 group-hover:text-white">{{ $service->description }}</div>
</div> </div>
@if (Str::of(serviceStatus($service))->startsWith('running'))
<div class="absolute bg-green-400 -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(serviceStatus($service))->startsWith('degraded'))
<div class="absolute bg-yellow-400 -top-1 -left-1 badge badge-xs"></div>
@elseif (Str::of(serviceStatus($service))->startsWith('exited'))
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
@endif
</a> </a>
@endforeach @endforeach
</div> </div>

View File

@@ -18,7 +18,7 @@ if [ $EUID != 0 ]; then
echo "Please run as root" echo "Please run as root"
exit exit
fi fi
if [ $OS_TYPE != "ubuntu" ] && [ $OS_TYPE != "debian" ]; then if [ $OS_TYPE != "ubuntu" ] && [ $OS_TYPE != "debian" ] && [ $OS_TYPE != "raspbian" ]; then
echo "This script only supports Ubuntu and Debian for now." echo "This script only supports Ubuntu and Debian for now."
exit exit
fi fi

File diff suppressed because one or more lines are too long

View File

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