@@ -21,6 +21,7 @@ class StartPostgresql
|
|||||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
|
"echo '####### Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
|
||||||
];
|
];
|
||||||
@@ -96,6 +97,7 @@ class StartPostgresql
|
|||||||
$readme = generate_readme_file($this->database->name, now());
|
$readme = generate_readme_file($this->database->name, now());
|
||||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
|
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||||
return remote_process($this->commands, $server);
|
return remote_process($this->commands, $server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class CheckConfiguration
|
|||||||
{
|
{
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$proxy_configuration = instant_remote_process([
|
$proxy_configuration = instant_remote_process([
|
||||||
|
"mkdir -p $proxy_path",
|
||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
], $server, false);
|
], $server, false);
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ class SaveConfiguration
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server, ?string $proxy_settings = null)
|
||||||
{
|
{
|
||||||
|
if (is_null($proxy_settings)) {
|
||||||
$proxy_settings = CheckConfiguration::run($server, true);
|
$proxy_settings = CheckConfiguration::run($server, true);
|
||||||
|
}
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Proxy;
|
namespace App\Actions\Proxy;
|
||||||
|
|
||||||
use App\Enums\ProxyStatus;
|
|
||||||
use App\Enums\ProxyTypes;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
@@ -14,26 +12,12 @@ class StartProxy
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Server $server, bool $async = true): Activity|string
|
public function handle(Server $server, bool $async = true): Activity|string
|
||||||
{
|
{
|
||||||
$proxyType = data_get($server,'proxy.type');
|
$commands = collect([]);
|
||||||
|
$proxyType = $server->proxyType();
|
||||||
if ($proxyType === 'none') {
|
if ($proxyType === 'none') {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
if (is_null($proxyType)) {
|
|
||||||
$server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
|
||||||
$server->proxy->status = ProxyStatus::EXITED->value;
|
|
||||||
$server->save();
|
|
||||||
}
|
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
|
||||||
return $docker['network'];
|
|
||||||
})->unique();
|
|
||||||
if ($networks->count() === 0) {
|
|
||||||
$networks = collect(['coolify']);
|
|
||||||
}
|
|
||||||
$create_networks_command = $networks->map(function ($network) {
|
|
||||||
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
|
|
||||||
});
|
|
||||||
|
|
||||||
$configuration = CheckConfiguration::run($server);
|
$configuration = CheckConfiguration::run($server);
|
||||||
if (!$configuration) {
|
if (!$configuration) {
|
||||||
throw new \Exception("Configuration is not synced");
|
throw new \Exception("Configuration is not synced");
|
||||||
@@ -41,19 +25,18 @@ class StartProxy
|
|||||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||||
$server->save();
|
$server->save();
|
||||||
$commands = [
|
|
||||||
"command -v lsof >/dev/null || echo '####### Installing lsof...'",
|
$commands = $commands->merge([
|
||||||
"command -v lsof >/dev/null || apt-get update",
|
"apt-get update > /dev/null 2>&1 || true",
|
||||||
|
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
||||||
"command -v lsof >/dev/null || apt install -y lsof",
|
"command -v lsof >/dev/null || apt install -y lsof",
|
||||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
||||||
"echo '####### Creating required Docker networks...'",
|
"mkdir -p $proxy_path && cd $proxy_path",
|
||||||
...$create_networks_command,
|
"echo '####### Creating Docker Compose file.'",
|
||||||
"cd $proxy_path",
|
"echo '####### Pulling docker image.'",
|
||||||
"echo '####### Creating Docker Compose file...'",
|
'docker compose pull',
|
||||||
"echo '####### Pulling docker image...'",
|
"echo '####### Stopping existing coolify-proxy.'",
|
||||||
'docker compose pull || docker-compose pull',
|
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||||
"echo '####### Stopping existing coolify-proxy...'",
|
|
||||||
"docker compose down -v --remove-orphans > /dev/null 2>&1 || docker-compose down -v --remove-orphans > /dev/null 2>&1 || true",
|
|
||||||
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
||||||
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
||||||
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
||||||
@@ -62,10 +45,11 @@ class StartProxy
|
|||||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
"systemctl disable nginx > /dev/null 2>&1 || true",
|
||||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
||||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||||
"echo '####### Starting coolify-proxy...'",
|
"echo '####### Starting coolify-proxy.'",
|
||||||
'docker compose up -d --remove-orphans || docker-compose up -d --remove-orphans',
|
'docker compose up -d --remove-orphans',
|
||||||
"echo '####### Proxy installed successfully...'"
|
"echo '####### Proxy installed successfully.'"
|
||||||
];
|
]);
|
||||||
|
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||||
if (!$async) {
|
if (!$async) {
|
||||||
instant_remote_process($commands, $server);
|
instant_remote_process($commands, $server);
|
||||||
return 'OK';
|
return 'OK';
|
||||||
|
|||||||
33
app/Actions/Service/StartService.php
Normal file
33
app/Actions/Service/StartService.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
|
class StartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
$workdir = service_configuration_dir() . "/{$service->uuid}";
|
||||||
|
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
||||||
|
$commands[] = "echo '####### Pulling images.'";
|
||||||
|
$commands[] = "mkdir -p $workdir";
|
||||||
|
$commands[] = "cd $workdir";
|
||||||
|
|
||||||
|
$docker_compose_base64 = base64_encode($service->docker_compose);
|
||||||
|
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||||
|
$envs = $service->environment_variables()->get();
|
||||||
|
$commands[] = "rm -f .env || true";
|
||||||
|
foreach ($envs as $env) {
|
||||||
|
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||||
|
}
|
||||||
|
$commands[] = "docker compose pull --quiet";
|
||||||
|
$commands[] = "echo '####### Starting containers.'";
|
||||||
|
$commands[] = "docker compose up -d >/dev/null 2>&1";
|
||||||
|
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
||||||
|
$activity = remote_process($commands, $service->server);
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Actions/Service/StopService.php
Normal file
25
app/Actions/Service/StopService.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Service;
|
||||||
|
|
||||||
|
class StopService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
$applications = $service->applications()->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ class Form extends Component
|
|||||||
$this->destination->delete();
|
$this->destination->delete();
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class StandaloneDocker extends Component
|
|||||||
|
|
||||||
private function createNetworkAndAttachToProxy()
|
private function createNetworkAndAttachToProxy()
|
||||||
{
|
{
|
||||||
instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false);
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false);
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ namespace App\Http\Livewire\Project\Application;
|
|||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
public string $applicationId;
|
public string $applicationId;
|
||||||
|
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
public Collection $services;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string|null $fqdn;
|
public string|null $fqdn;
|
||||||
public string $git_repository;
|
public string $git_repository;
|
||||||
@@ -31,6 +34,7 @@ class General extends Component
|
|||||||
public bool $is_auto_deploy_enabled;
|
public bool $is_auto_deploy_enabled;
|
||||||
public bool $is_force_https_enabled;
|
public bool $is_force_https_enabled;
|
||||||
|
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'application.name' => 'required',
|
'application.name' => 'required',
|
||||||
'application.description' => 'nullable',
|
'application.description' => 'nullable',
|
||||||
@@ -66,6 +70,7 @@ class General extends Component
|
|||||||
'application.ports_exposes' => 'Ports exposes',
|
'application.ports_exposes' => 'Ports exposes',
|
||||||
'application.ports_mappings' => 'Ports mappings',
|
'application.ports_mappings' => 'Ports mappings',
|
||||||
'application.dockerfile' => 'Dockerfile',
|
'application.dockerfile' => 'Dockerfile',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
@@ -86,8 +91,8 @@ class General extends Component
|
|||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
$this->emit('success', 'Application settings updated!');
|
|
||||||
$this->checkWildCardDomain();
|
$this->checkWildCardDomain();
|
||||||
|
$this->emit('success', 'Application settings updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function checkWildCardDomain()
|
protected function checkWildCardDomain()
|
||||||
@@ -136,7 +141,6 @@ class General extends Component
|
|||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
ray($this->application);
|
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if (data_get($this->application, 'fqdn')) {
|
if (data_get($this->application, 'fqdn')) {
|
||||||
@@ -145,7 +149,7 @@ class General extends Component
|
|||||||
});
|
});
|
||||||
$this->application->fqdn = $domains->implode(',');
|
$this->application->fqdn = $domains->implode(',');
|
||||||
}
|
}
|
||||||
if ($this->application->dockerfile) {
|
if (data_get($this->application, 'dockerfile')) {
|
||||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||||
if ($port) {
|
if ($port) {
|
||||||
$this->application->ports_exposes = $port;
|
$this->application->ports_exposes = $port;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function check_status()
|
public function check_status()
|
||||||
{
|
{
|
||||||
dispatch_sync(new ContainerStatusJob($this->application->destination->server));
|
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
$this->application->previews->each(function ($preview) {
|
$this->application->previews->each(function ($preview) {
|
||||||
$preview->refresh();
|
$preview->refresh();
|
||||||
@@ -64,7 +64,7 @@ class Heading extends Component
|
|||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
if ($containerName) {
|
if ($containerName) {
|
||||||
remote_process(
|
instant_remote_process(
|
||||||
["docker rm -f {$containerName}"],
|
["docker rm -f {$containerName}"],
|
||||||
$this->application->destination->server
|
$this->application->destination->server
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class Previews extends Component
|
|||||||
public function stop(int $pull_request_id)
|
public function stop(int $pull_request_id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$container_name = generateApplicationContainerName($this->application->uuid, $pull_request_id);
|
$container_name = generateApplicationContainerName($this->application);
|
||||||
ray('Stopping container: ' . $container_name);
|
ray('Stopping container: ' . $container_name);
|
||||||
|
|
||||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||||
|
|||||||
74
app/Http/Livewire/Project/New/DockerCompose.php
Normal file
74
app/Http/Livewire/Project/New/DockerCompose.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\New;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class DockerCompose extends Component
|
||||||
|
{
|
||||||
|
public string $dockercompose = '';
|
||||||
|
public array $parameters;
|
||||||
|
public array $query;
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->query = request()->query();
|
||||||
|
if (isDev()) {
|
||||||
|
$this->dockercompose = 'services:
|
||||||
|
ghost:
|
||||||
|
documentation: https://ghost.org/docs/config
|
||||||
|
image: ghost:5
|
||||||
|
volumes:
|
||||||
|
- ghost-content-data:/var/lib/ghost/content
|
||||||
|
environment:
|
||||||
|
- url=$SERVICE_FQDN_GHOST
|
||||||
|
- database__client=mysql
|
||||||
|
- database__connection__host=mysql
|
||||||
|
- database__connection__user=$SERVICE_USER_MYSQL
|
||||||
|
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||||
|
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
documentation: https://hub.docker.com/_/mysql
|
||||||
|
image: mysql:8.0
|
||||||
|
volumes:
|
||||||
|
- ghost-mysql-data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||||
|
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||||
|
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||||
|
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||||
|
';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'dockercompose' => 'required'
|
||||||
|
]);
|
||||||
|
$server_id = $this->query['server_id'];
|
||||||
|
|
||||||
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
|
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||||
|
|
||||||
|
$service = Service::create([
|
||||||
|
'name' => 'service' . Str::random(10),
|
||||||
|
'docker_compose_raw' => $this->dockercompose,
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'server_id' => (int) $server_id,
|
||||||
|
]);
|
||||||
|
$service->name = "service-$service->uuid";
|
||||||
|
|
||||||
|
$service->parse(isNew: true);
|
||||||
|
|
||||||
|
return redirect()->route('project.service', [
|
||||||
|
'service_uuid' => $service->uuid,
|
||||||
|
'environment_name' => $environment->name,
|
||||||
|
'project_uuid' => $project->uuid,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ use App\Models\StandaloneDocker;
|
|||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use App\Traits\SaveFromRedirect;
|
use App\Traits\SaveFromRedirect;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Route;
|
|
||||||
|
|
||||||
class GithubPrivateRepository extends Component
|
class GithubPrivateRepository extends Component
|
||||||
{
|
{
|
||||||
@@ -40,21 +40,6 @@ class GithubPrivateRepository extends Component
|
|||||||
public string|null $publish_directory = null;
|
public string|null $publish_directory = null;
|
||||||
protected int $page = 1;
|
protected int $page = 1;
|
||||||
|
|
||||||
// public function saveFromRedirect(string $route, ?Collection $parameters = null){
|
|
||||||
// session()->forget('from');
|
|
||||||
// if (!$parameters || $parameters->count() === 0) {
|
|
||||||
// $parameters = $this->parameters;
|
|
||||||
// }
|
|
||||||
// $parameters = collect($parameters) ?? collect([]);
|
|
||||||
// $queries = collect($this->query) ?? collect([]);
|
|
||||||
// $parameters = $parameters->merge($queries);
|
|
||||||
// session(['from'=> [
|
|
||||||
// 'back'=> $this->currentRoute,
|
|
||||||
// 'route' => $route,
|
|
||||||
// 'parameters' => $parameters
|
|
||||||
// ]]);
|
|
||||||
// return redirect()->route($route);
|
|
||||||
// }
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@@ -159,6 +144,13 @@ class GithubPrivateRepository extends Component
|
|||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|
||||||
|
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
|
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
|
||||||
|
$application->save();
|
||||||
|
|
||||||
redirect()->route('project.application.configuration', [
|
redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|
||||||
|
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
|
$application->name = generate_random_name($application->uuid);
|
||||||
|
$application->save();
|
||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ class PublicGitRepository extends Component
|
|||||||
$project = Project::where('uuid', $project_uuid)->first();
|
$project = Project::where('uuid', $project_uuid)->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
||||||
|
|
||||||
|
|
||||||
$application_init = [
|
$application_init = [
|
||||||
'name' => generate_application_name($this->git_repository, $this->git_branch),
|
'name' => generate_application_name($this->git_repository, $this->git_branch),
|
||||||
'git_repository' => $this->git_repository,
|
'git_repository' => $this->git_repository,
|
||||||
@@ -153,9 +152,17 @@ class PublicGitRepository extends Component
|
|||||||
];
|
];
|
||||||
|
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
|
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|
||||||
|
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
|
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
|
||||||
|
$application->save();
|
||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'project_uuid' => $project->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class Select extends Component
|
|||||||
public string $type;
|
public string $type;
|
||||||
public string $server_id;
|
public string $server_id;
|
||||||
public string $destination_uuid;
|
public string $destination_uuid;
|
||||||
public Countable|array|Server $servers;
|
public Countable|array|Server $servers = [];
|
||||||
public Collection|array $standaloneDockers = [];
|
public Collection|array $standaloneDockers = [];
|
||||||
public Collection|array $swarmDockers = [];
|
public Collection|array $swarmDockers = [];
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
@@ -83,6 +83,7 @@ class Select extends Component
|
|||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_name' => $this->parameters['environment_name'],
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'destination' => $this->destination_uuid,
|
'destination' => $this->destination_uuid,
|
||||||
|
'server_id' => $this->server_id,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,8 +59,14 @@ CMD ["nginx", "-g", "daemon off;"]
|
|||||||
'source_id' => 0,
|
'source_id' => 0,
|
||||||
'source_type' => GithubApp::class
|
'source_type' => GithubApp::class
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
$application->update([
|
$application->update([
|
||||||
'name' => 'dockerfile-' . $application->id
|
'name' => 'dockerfile-' . $application->uuid,
|
||||||
|
'fqdn' => $fqdn
|
||||||
]);
|
]);
|
||||||
|
|
||||||
redirect()->route('project.application.configuration', [
|
redirect()->route('project.application.configuration', [
|
||||||
|
|||||||
32
app/Http/Livewire/Project/Service/Application.php
Normal file
32
app/Http/Livewire/Project/Service/Application.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Application extends Component
|
||||||
|
{
|
||||||
|
public ServiceApplication $application;
|
||||||
|
protected $rules = [
|
||||||
|
'application.human_name' => 'nullable',
|
||||||
|
'application.description' => 'nullable',
|
||||||
|
'application.fqdn' => 'nullable',
|
||||||
|
];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
ray($this->application->fileStorages()->get());
|
||||||
|
return view('livewire.project.service.application');
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->application->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
} finally {
|
||||||
|
$this->emit('generateDockerCompose');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
app/Http/Livewire/Project/Service/Database.php
Normal file
31
app/Http/Livewire/Project/Service/Database.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Database extends Component
|
||||||
|
{
|
||||||
|
public ServiceDatabase $database;
|
||||||
|
protected $rules = [
|
||||||
|
'database.human_name' => 'nullable',
|
||||||
|
'database.description' => 'nullable',
|
||||||
|
];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.database');
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
} finally {
|
||||||
|
$this->emit('generateDockerCompose');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/Http/Livewire/Project/Service/FileStorage.php
Normal file
21
app/Http/Livewire/Project/Service/FileStorage.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\LocalFileVolume;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class FileStorage extends Component
|
||||||
|
{
|
||||||
|
public LocalFileVolume $fileStorage;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'fileStorage.fs_path' => 'required',
|
||||||
|
'fileStorage.mount_path' => 'required',
|
||||||
|
'fileStorage.content' => 'nullable',
|
||||||
|
];
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.file-storage');
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/Http/Livewire/Project/Service/Index.php
Normal file
46
app/Http/Livewire/Project/Service/Index.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public Service $service;
|
||||||
|
|
||||||
|
public array $parameters;
|
||||||
|
public array $query;
|
||||||
|
protected $rules = [
|
||||||
|
'service.docker_compose_raw' => 'required',
|
||||||
|
'service.docker_compose' => 'required',
|
||||||
|
'service.name' => 'required',
|
||||||
|
'service.description' => 'required',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->query = request()->query();
|
||||||
|
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.index');
|
||||||
|
}
|
||||||
|
public function save() {
|
||||||
|
$this->service->save();
|
||||||
|
$this->service->parse();
|
||||||
|
$this->service->refresh();
|
||||||
|
$this->emit('refreshEnvs');
|
||||||
|
}
|
||||||
|
public function submit() {
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->service->save();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
app/Http/Livewire/Project/Service/Modal.php
Normal file
16
app/Http/Livewire/Project/Service/Modal.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Modal extends Component
|
||||||
|
{
|
||||||
|
public function serviceStatusUpdated() {
|
||||||
|
$this->emit('serviceStatusUpdated');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.modal');
|
||||||
|
}
|
||||||
|
}
|
||||||
43
app/Http/Livewire/Project/Service/Navbar.php
Normal file
43
app/Http/Livewire/Project/Service/Navbar.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Jobs\ContainerStatusJob;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Navbar extends Component
|
||||||
|
{
|
||||||
|
public Service $service;
|
||||||
|
public array $parameters;
|
||||||
|
public array $query;
|
||||||
|
protected $listeners = ['serviceStatusUpdated'];
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.navbar');
|
||||||
|
}
|
||||||
|
public function serviceStatusUpdated()
|
||||||
|
{
|
||||||
|
ray('serviceStatusUpdated');
|
||||||
|
$this->check_status();
|
||||||
|
}
|
||||||
|
public function check_status()
|
||||||
|
{
|
||||||
|
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||||
|
$this->service->refresh();
|
||||||
|
}
|
||||||
|
public function deploy()
|
||||||
|
{
|
||||||
|
$this->service->parse();
|
||||||
|
$activity = StartService::run($this->service);
|
||||||
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
|
}
|
||||||
|
public function stop()
|
||||||
|
{
|
||||||
|
StopService::run($this->service);
|
||||||
|
$this->service->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
42
app/Http/Livewire/Project/Service/Show.php
Normal file
42
app/Http/Livewire/Project/Service/Show.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public Service $service;
|
||||||
|
public ServiceApplication $serviceApplication;
|
||||||
|
public ServiceDatabase $serviceDatabase;
|
||||||
|
public array $parameters;
|
||||||
|
public array $query;
|
||||||
|
public Collection $services;
|
||||||
|
protected $listeners = ['generateDockerCompose'];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->services = collect([]);
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
$this->query = request()->query();
|
||||||
|
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||||
|
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
|
||||||
|
if ($service) {
|
||||||
|
$this->serviceApplication = $service;
|
||||||
|
} else {
|
||||||
|
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function generateDockerCompose()
|
||||||
|
{
|
||||||
|
$this->service->parse();
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.service.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared;
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -19,13 +20,25 @@ class Danger extends Component
|
|||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
if ($this->resource->type() === 'service') {
|
||||||
|
$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();
|
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
||||||
|
$server = $destination->server;
|
||||||
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server);
|
}
|
||||||
|
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
|
||||||
|
}
|
||||||
$this->resource->delete();
|
$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']
|
||||||
]);
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ class Add extends Component
|
|||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
ray('submitting');
|
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->emitUp('submit', [
|
$this->emitUp('submit', [
|
||||||
'key' => $this->key,
|
'key' => $this->key,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class All extends Component
|
|||||||
$this->resource->environment_variables_preview()->delete();
|
$this->resource->environment_variables_preview()->delete();
|
||||||
} else {
|
} else {
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
|
ray($variables);
|
||||||
$existingVariables = $this->resource->environment_variables();
|
$existingVariables = $this->resource->environment_variables();
|
||||||
$this->resource->environment_variables()->delete();
|
$this->resource->environment_variables()->delete();
|
||||||
}
|
}
|
||||||
@@ -68,11 +69,16 @@ class All extends Component
|
|||||||
$environment->value = $variable;
|
$environment->value = $variable;
|
||||||
$environment->is_build_time = false;
|
$environment->is_build_time = false;
|
||||||
$environment->is_preview = $isPreview ? true : false;
|
$environment->is_preview = $isPreview ? true : false;
|
||||||
if ($this->resource->type() === 'application') {
|
switch ($this->resource->type()) {
|
||||||
|
case 'application':
|
||||||
$environment->application_id = $this->resource->id;
|
$environment->application_id = $this->resource->id;
|
||||||
}
|
break;
|
||||||
if ($this->resource->type() === 'standalone-postgresql') {
|
case 'standalone-postgresql':
|
||||||
$environment->standalone_postgresql_id = $this->resource->id;
|
$environment->standalone_postgresql_id = $this->resource->id;
|
||||||
|
break;
|
||||||
|
case 'service':
|
||||||
|
$environment->service_id = $this->resource->id;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
$environment->save();
|
$environment->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public ModelsEnvironmentVariable $env;
|
public ModelsEnvironmentVariable $env;
|
||||||
public string|null $modalId = null;
|
public ?string $modalId = null;
|
||||||
|
public string $type;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'env.key' => 'required|string',
|
'env.key' => 'required|string',
|
||||||
'env.value' => 'required|string',
|
'env.value' => 'required|string',
|
||||||
@@ -37,6 +39,7 @@ class Show extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
$this->emit('success', 'Environment variable updated successfully.');
|
$this->emit('success', 'Environment variable updated successfully.');
|
||||||
|
$this->emit('refreshEnvs');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
|
|||||||
39
app/Http/Livewire/Project/Shared/HealthChecks.php
Normal file
39
app/Http/Livewire/Project/Shared/HealthChecks.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class HealthChecks extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
public $resource;
|
||||||
|
protected $rules = [
|
||||||
|
'resource.health_check_path' => 'string',
|
||||||
|
'resource.health_check_port' => 'nullable|string',
|
||||||
|
'resource.health_check_host' => 'string',
|
||||||
|
'resource.health_check_method' => 'string',
|
||||||
|
'resource.health_check_return_code' => 'integer',
|
||||||
|
'resource.health_check_scheme' => 'string',
|
||||||
|
'resource.health_check_response_text' => 'nullable|string',
|
||||||
|
'resource.health_check_interval' => 'integer',
|
||||||
|
'resource.health_check_timeout' => 'integer',
|
||||||
|
'resource.health_check_retries' => 'integer',
|
||||||
|
'resource.health_check_start_period' => 'integer',
|
||||||
|
|
||||||
|
];
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->resource->save();
|
||||||
|
$this->emit('saved');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.health-checks');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Shared\Storages;
|
namespace App\Http\Livewire\Project\Shared\Storages;
|
||||||
|
|
||||||
|
use App\Models\LocalPersistentVolume;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
public $storage;
|
public LocalPersistentVolume $storage;
|
||||||
public string|null $modalId = null;
|
public bool $isReadOnly = false;
|
||||||
|
public ?string $modalId = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'storage.name' => 'required|string',
|
'storage.name' => 'required|string',
|
||||||
'storage.mount_path' => 'required|string',
|
'storage.mount_path' => 'required|string',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class Form extends Component
|
|||||||
'server.ip' => 'required',
|
'server.ip' => 'required',
|
||||||
'server.user' => 'required',
|
'server.user' => 'required',
|
||||||
'server.port' => 'required',
|
'server.port' => 'required',
|
||||||
|
'server.settings.is_cloudflare_tunnel' => 'required',
|
||||||
'server.settings.is_reachable' => 'required',
|
'server.settings.is_reachable' => 'required',
|
||||||
'server.settings.is_part_of_swarm' => 'required',
|
'server.settings.is_part_of_swarm' => 'required',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
@@ -33,6 +34,7 @@ class Form extends Component
|
|||||||
'server.ip' => 'ip',
|
'server.ip' => 'ip',
|
||||||
'server.user' => 'user',
|
'server.user' => 'user',
|
||||||
'server.port' => 'port',
|
'server.port' => 'port',
|
||||||
|
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||||
'server.settings.is_reachable' => 'is reachable',
|
'server.settings.is_reachable' => 'is reachable',
|
||||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||||
];
|
];
|
||||||
@@ -42,7 +44,11 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||||
}
|
}
|
||||||
|
public function instantSave() {
|
||||||
|
refresh_server_connection($this->server->privateKey);
|
||||||
|
$this->validateServer();
|
||||||
|
$this->server->settings->save();
|
||||||
|
}
|
||||||
public function installDocker()
|
public function installDocker()
|
||||||
{
|
{
|
||||||
$this->dockerInstallationStarted = true;
|
$this->dockerInstallationStarted = true;
|
||||||
@@ -58,21 +64,19 @@ class Form extends Component
|
|||||||
$this->uptime = $uptime;
|
$this->uptime = $uptime;
|
||||||
$this->emit('success', 'Server is reachable.');
|
$this->emit('success', 'Server is reachable.');
|
||||||
} else {
|
} else {
|
||||||
ray($this->uptime);
|
|
||||||
|
|
||||||
$this->emit('error', 'Server is not reachable.');
|
$this->emit('error', 'Server is not reachable.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($dockerVersion) {
|
if ($dockerVersion) {
|
||||||
$this->dockerVersion = $dockerVersion;
|
$this->dockerVersion = $dockerVersion;
|
||||||
$this->emit('proxyStatusUpdated');
|
|
||||||
$this->emit('success', 'Docker Engine 23+ is installed!');
|
$this->emit('success', 'Docker Engine 23+ is installed!');
|
||||||
} else {
|
} else {
|
||||||
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
|
$this->emit('error', 'No Docker Engine or older than 23 version installed.');
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
|
return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
|
||||||
|
} finally {
|
||||||
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class ByIp extends Component
|
|||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
return redirect()->route('server.show', $server->uuid);
|
return redirect()->route('server.show', $server->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Actions\Proxy\CheckConfiguration;
|
|||||||
use App\Actions\Proxy\SaveConfiguration;
|
use App\Actions\Proxy\SaveConfiguration;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class Proxy extends Component
|
class Proxy extends Component
|
||||||
{
|
{
|
||||||
@@ -47,14 +48,14 @@ class Proxy extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
SaveConfiguration::run($this->server);
|
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
|
||||||
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
||||||
$this->emit('success', 'Proxy configuration saved.');
|
$this->emit('success', 'Proxy configuration saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ class Proxy extends Component
|
|||||||
try {
|
try {
|
||||||
$this->proxy_settings = CheckConfiguration::run($this->server, true);
|
$this->proxy_settings = CheckConfiguration::run($this->server, true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,8 +72,14 @@ class Proxy extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->proxy_settings = CheckConfiguration::run($this->server);
|
$this->proxy_settings = CheckConfiguration::run($this->server);
|
||||||
|
if (Str::of($this->proxy_settings)->contains('--api.dashboard=true') && Str::of($this->proxy_settings)->contains('--api.insecure=true')) {
|
||||||
|
$this->emit('traefikDashboardAvailable', true);
|
||||||
|
} else {
|
||||||
|
$this->emit('traefikDashboardAvailable', false);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Server\Proxy;
|
namespace App\Http\Livewire\Server\Proxy;
|
||||||
|
|
||||||
use App\Actions\Proxy\SaveConfiguration;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -10,9 +9,16 @@ use Livewire\Component;
|
|||||||
class Deploy extends Component
|
class Deploy extends Component
|
||||||
{
|
{
|
||||||
public Server $server;
|
public Server $server;
|
||||||
public $proxy_settings = null;
|
public bool $traefikDashboardAvailable = false;
|
||||||
protected $listeners = ['proxyStatusUpdated'];
|
public ?string $currentRoute = null;
|
||||||
|
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable'];
|
||||||
|
|
||||||
|
public function mount() {
|
||||||
|
$this->currentRoute = request()->route()->getName();
|
||||||
|
}
|
||||||
|
public function traefikDashboardAvailable(bool $data) {
|
||||||
|
$this->traefikDashboardAvailable = $data;
|
||||||
|
}
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
{
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
@@ -20,17 +26,10 @@ class Deploy extends Component
|
|||||||
public function startProxy()
|
public function startProxy()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (
|
|
||||||
$this->server->proxy->last_applied_settings &&
|
|
||||||
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
|
|
||||||
) {
|
|
||||||
SaveConfiguration::run($this->server);
|
|
||||||
}
|
|
||||||
|
|
||||||
$activity = StartProxy::run($this->server);
|
$activity = StartProxy::run($this->server);
|
||||||
$this->emit('newMonitorActivity', $activity->id);
|
$this->emit('newMonitorActivity', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Status extends Component
|
|||||||
$this->emit('proxyStatusUpdated');
|
$this->emit('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function getProxyStatusWithNoti()
|
public function getProxyStatusWithNoti()
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private int $application_deployment_queue_id;
|
private int $application_deployment_queue_id;
|
||||||
|
|
||||||
|
private bool $newVersionIsHealthy = false;
|
||||||
private ApplicationDeploymentQueue $application_deployment_queue;
|
private ApplicationDeploymentQueue $application_deployment_queue;
|
||||||
private Application $application;
|
private Application $application;
|
||||||
private string $deployment_uuid;
|
private string $deployment_uuid;
|
||||||
@@ -88,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application->uuid, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application);
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
|
|
||||||
@@ -96,7 +97,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||||
if ($this->application->fqdn) {
|
if ($this->application->fqdn) {
|
||||||
$preview_fqdn = data_get($this->preview, 'fqdn');
|
$preview_fqdn = getOnlyFqdn(data_get($this->preview, 'fqdn'));
|
||||||
$template = $this->application->preview_url_template;
|
$template = $this->application->preview_url_template;
|
||||||
$url = Url::fromString($this->application->fqdn);
|
$url = Url::fromString($this->application->fqdn);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
@@ -166,6 +167,54 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function deploy_docker_compose()
|
||||||
|
{
|
||||||
|
$dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->application->name}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
|
$this->save_environment_variables();
|
||||||
|
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerName = data_get($container, 'Names');
|
||||||
|
if ($containerName) {
|
||||||
|
instant_remote_process(
|
||||||
|
["docker rm -f {$containerName}"],
|
||||||
|
$this->application->destination->server
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'Starting services (could take a while)...'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private function save_environment_variables()
|
||||||
|
{
|
||||||
|
$envs = collect([]);
|
||||||
|
foreach ($this->application->environment_variables as $env) {
|
||||||
|
$envs->push($env->key . '=' . $env->value);
|
||||||
|
}
|
||||||
|
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
private function deploy_simple_dockerfile()
|
private function deploy_simple_dockerfile()
|
||||||
{
|
{
|
||||||
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
||||||
@@ -267,7 +316,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
|
$this->newVersionIsHealthy = true;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'New version of your application is healthy.'"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"echo 'Rolling update completed.'"
|
"echo 'Rolling update completed.'"
|
||||||
],
|
],
|
||||||
@@ -475,8 +528,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'container_name' => $this->container_name,
|
'container_name' => $this->container_name,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'environment' => $environment_variables,
|
'environment' => $environment_variables,
|
||||||
'labels' => $this->set_labels_for_applications(),
|
'labels' => generateLabelsApplication($this->application, $this->preview),
|
||||||
'expose' => $ports,
|
// 'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network,
|
||||||
],
|
],
|
||||||
@@ -577,75 +630,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function set_labels_for_applications()
|
|
||||||
{
|
|
||||||
|
|
||||||
$appId = $this->application->id;
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$appId = $appId . '-pr-' . $this->pull_request_id;
|
|
||||||
}
|
|
||||||
$labels = [];
|
|
||||||
$labels[] = 'coolify.managed=true';
|
|
||||||
$labels[] = 'coolify.version=' . config('version');
|
|
||||||
$labels[] = 'coolify.applicationId=' . $appId;
|
|
||||||
$labels[] = 'coolify.type=application';
|
|
||||||
$labels[] = 'coolify.name=' . $this->application->name;
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$labels[] = 'coolify.pullRequestId=' . $this->pull_request_id;
|
|
||||||
}
|
|
||||||
if ($this->application->fqdn) {
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$domains = Str::of(data_get($this->preview, 'fqdn'))->explode(',');
|
|
||||||
} else {
|
|
||||||
$domains = Str::of(data_get($this->application, 'fqdn'))->explode(',');
|
|
||||||
}
|
|
||||||
if ($this->application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) {
|
|
||||||
$labels[] = 'traefik.enable=true';
|
|
||||||
foreach ($domains as $domain) {
|
|
||||||
$url = Url::fromString($domain);
|
|
||||||
$host = $url->getHost();
|
|
||||||
$path = $url->getPath();
|
|
||||||
$schema = $url->getScheme();
|
|
||||||
$slug = Str::slug($host . $path);
|
|
||||||
|
|
||||||
$http_label = "{$this->container_name}-{$slug}-http";
|
|
||||||
$https_label = "{$this->container_name}-{$slug}-https";
|
|
||||||
|
|
||||||
if ($schema === 'https') {
|
|
||||||
// Set labels for https
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.entryPoints=https";
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.middlewares=gzip";
|
|
||||||
if ($path !== '/') {
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix";
|
|
||||||
$labels[] = "traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}";
|
|
||||||
}
|
|
||||||
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.tls=true";
|
|
||||||
$labels[] = "traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt";
|
|
||||||
|
|
||||||
// Set labels for http (redirect to https)
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.entryPoints=http";
|
|
||||||
if ($this->application->settings->is_force_https_enabled) {
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares=redirect-to-https";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Set labels for http
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)";
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.entryPoints=http";
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares=gzip";
|
|
||||||
if ($path !== '/') {
|
|
||||||
$labels[] = "traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix";
|
|
||||||
$labels[] = "traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generate_healthcheck_commands()
|
private function generate_healthcheck_commands()
|
||||||
{
|
{
|
||||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
||||||
@@ -653,15 +637,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return 'exit 0';
|
return 'exit 0';
|
||||||
}
|
}
|
||||||
if (!$this->application->health_check_port) {
|
if (!$this->application->health_check_port) {
|
||||||
$this->application->health_check_port = $this->application->ports_exposes_array[0];
|
$health_check_port = $this->application->ports_exposes_array[0];
|
||||||
|
} else {
|
||||||
|
$health_check_port = $this->application->health_check_port;
|
||||||
}
|
}
|
||||||
if ($this->application->health_check_path) {
|
if ($this->application->health_check_path) {
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
return implode(' ', $generated_healthchecks_commands);
|
||||||
@@ -721,10 +707,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
private function stop_running_container()
|
private function stop_running_container()
|
||||||
{
|
{
|
||||||
if ($this->currently_running_container_name) {
|
if ($this->currently_running_container_name) {
|
||||||
|
if ($this->newVersionIsHealthy) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Removing old version of your application.'"],
|
["echo -n 'Removing old version of your application.'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use App\Models\Server;
|
|||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use Arr;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
@@ -17,6 +16,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
@@ -74,6 +74,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$containers = format_docker_command_output_to_json($containers);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
$applications = $this->server->applications();
|
$applications = $this->server->applications();
|
||||||
$databases = $this->server->databases();
|
$databases = $this->server->databases();
|
||||||
|
$services = $this->server->services();
|
||||||
$previews = $this->server->previews();
|
$previews = $this->server->previews();
|
||||||
|
|
||||||
/// Check if proxy is running
|
/// Check if proxy is running
|
||||||
@@ -88,12 +89,18 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} else {
|
} else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
$foundApplications = [];
|
$foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
$foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerStatus = data_get($container, 'State.Status');
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status','unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = data_get($container, 'Config.Labels');
|
$labels = data_get($container, 'Config.Labels');
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$labelId = data_get($labels, 'coolify.applicationId');
|
$labelId = data_get($labels, 'coolify.applicationId');
|
||||||
@@ -138,7 +145,60 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$coolifyName = data_get($labels, 'coolify.name');
|
||||||
|
$serviceName = Str::of($coolifyName)->before('-');
|
||||||
|
$serviceUuid = Str::of($coolifyName)->after('-');
|
||||||
|
$service = $services->where('uuid', $serviceUuid)->first();
|
||||||
|
if ($service) {
|
||||||
|
$foundService = $service->byName($serviceName);
|
||||||
|
if ($foundService) {
|
||||||
|
$foundServices[] = "$foundService->id-$serviceName";
|
||||||
|
$statusFromDb = $foundService->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$foundService->update(['status' => $containerStatus]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($services->get() as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if ($exitedService->status === 'exited') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
$project = data_get($service, 'environment.project');
|
||||||
|
$environment = data_get($service, 'environment');
|
||||||
|
|
||||||
|
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/service/" . $service->uuid;
|
||||||
|
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
$application = $applications->where('id', $applicationId)->first();
|
$application = $applications->where('id', $applicationId)->first();
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ class Application extends BaseModel
|
|||||||
});
|
});
|
||||||
static::deleting(function ($application) {
|
static::deleting(function ($application) {
|
||||||
$application->settings()->delete();
|
$application->settings()->delete();
|
||||||
|
$storages = $application->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server);
|
||||||
|
}
|
||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
|
$application->environment_variables()->delete();
|
||||||
|
$application->environment_variables_preview()->delete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +230,7 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
public function git_based(): bool
|
public function git_based(): bool
|
||||||
{
|
{
|
||||||
if ($this->dockerfile || $this->build_pack === 'dockerfile') {
|
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->dockercompose || $this->build_pack === 'dockercompose') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -33,18 +33,23 @@ class EnvironmentVariable extends Model
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function service() {
|
||||||
|
return $this->belongsTo(Service::class);
|
||||||
|
}
|
||||||
protected function value(): Attribute
|
protected function value(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn (string $value) => $this->get_environment_variables($value),
|
get: fn (?string $value = null) => $this->get_environment_variables($value),
|
||||||
set: fn (string $value) => $this->set_environment_variables($value),
|
set: fn (?string $value = null) => $this->set_environment_variables($value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_environment_variables(string $environment_variable): string|null
|
private function get_environment_variables(?string $environment_variable = null): string|null
|
||||||
{
|
{
|
||||||
// $team_id = currentTeam()->id;
|
// $team_id = currentTeam()->id;
|
||||||
|
if (!$environment_variable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$environment_variable = trim(decrypt($environment_variable));
|
$environment_variable = trim(decrypt($environment_variable));
|
||||||
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
|
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
|
||||||
$variable = Str::after($environment_variable, 'global.');
|
$variable = Str::after($environment_variable, 'global.');
|
||||||
@@ -57,8 +62,11 @@ class EnvironmentVariable extends Model
|
|||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function set_environment_variables(string $environment_variable): string|null
|
private function set_environment_variables(?string $environment_variable = null): string|null
|
||||||
{
|
{
|
||||||
|
if (is_null($environment_variable) && $environment_variable == '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$environment_variable = trim($environment_variable);
|
$environment_variable = trim($environment_variable);
|
||||||
return encrypt($environment_variable);
|
return encrypt($environment_variable);
|
||||||
}
|
}
|
||||||
@@ -69,4 +77,5 @@ class EnvironmentVariable extends Model
|
|||||||
set: fn (string $value) => Str::of($value)->trim(),
|
set: fn (string $value) => Str::of($value)->trim(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
app/Models/LocalFileVolume.php
Normal file
16
app/Models/LocalFileVolume.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
|
||||||
|
class LocalFileVolume extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
public function service()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ class LocalPersistentVolume extends Model
|
|||||||
{
|
{
|
||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
public function service()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
public function standalone_postgresql()
|
public function standalone_postgresql()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
@@ -76,6 +78,15 @@ class Server extends BaseModel
|
|||||||
return $this->hasOne(ServerSetting::class);
|
return $this->hasOne(ServerSetting::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function proxyType() {
|
||||||
|
$type = $this->proxy->get('type');
|
||||||
|
if (is_null($type)) {
|
||||||
|
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||||
|
$this->proxy->status = ProxyStatus::EXITED->value;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
return $this->proxy->get('type');
|
||||||
|
}
|
||||||
public function scopeWithProxy(): Builder
|
public function scopeWithProxy(): Builder
|
||||||
{
|
{
|
||||||
return $this->proxy->modelScope();
|
return $this->proxy->modelScope();
|
||||||
@@ -104,6 +115,9 @@ class Server extends BaseModel
|
|||||||
return $standaloneDocker->applications;
|
return $standaloneDocker->applications;
|
||||||
})->flatten();
|
})->flatten();
|
||||||
}
|
}
|
||||||
|
public function services() {
|
||||||
|
return $this->hasMany(Service::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function previews() {
|
public function previews() {
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
return $this->destinations()->map(function ($standaloneDocker) {
|
||||||
|
|||||||
447
app/Models/Service.php
Normal file
447
app/Models/Service.php
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
|
class Service extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::deleted(function ($service) {
|
||||||
|
$storagesToDelete = collect([]);
|
||||||
|
foreach ($service->applications()->get() as $application) {
|
||||||
|
$storages = $application->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
$application->persistentStorages()->delete();
|
||||||
|
}
|
||||||
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
$database->persistentStorages()->delete();
|
||||||
|
}
|
||||||
|
$service->environment_variables()->delete();
|
||||||
|
$service->applications()->delete();
|
||||||
|
$service->databases()->delete();
|
||||||
|
if ($storagesToDelete->count() > 0) {
|
||||||
|
$storagesToDelete->each(function ($storage) use ($service) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function type()
|
||||||
|
{
|
||||||
|
return 'service';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applications()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ServiceApplication::class);
|
||||||
|
}
|
||||||
|
public function databases()
|
||||||
|
{
|
||||||
|
return $this->hasMany(ServiceDatabase::class);
|
||||||
|
}
|
||||||
|
public function environment()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Environment::class);
|
||||||
|
}
|
||||||
|
public function server()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Server::class);
|
||||||
|
}
|
||||||
|
public function byName(string $name)
|
||||||
|
{
|
||||||
|
$app = $this->applications()->whereName($name)->first();
|
||||||
|
if ($app) {
|
||||||
|
return $app;
|
||||||
|
}
|
||||||
|
$db = $this->databases()->whereName($name)->first();
|
||||||
|
if ($db) {
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public function environment_variables(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
||||||
|
}
|
||||||
|
public function parse(bool $isNew = false): Collection
|
||||||
|
{
|
||||||
|
ray('parsing');
|
||||||
|
// ray()->clearAll();
|
||||||
|
if ($this->docker_compose_raw) {
|
||||||
|
try {
|
||||||
|
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$composeVolumes = collect(data_get($yaml, 'volumes', []));
|
||||||
|
$composeNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
|
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||||
|
$services = data_get($yaml, 'services');
|
||||||
|
$definedNetwork = $this->uuid;
|
||||||
|
|
||||||
|
$volumes = collect([]);
|
||||||
|
$envs = collect([]);
|
||||||
|
$ports = collect([]);
|
||||||
|
|
||||||
|
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) {
|
||||||
|
$container_name = "$serviceName-{$this->uuid}";
|
||||||
|
$isDatabase = false;
|
||||||
|
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||||
|
|
||||||
|
// Decide if the service is a database
|
||||||
|
$image = data_get($service, 'image');
|
||||||
|
if ($image) {
|
||||||
|
$imageName = Str::of($image)->before(':');
|
||||||
|
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
|
||||||
|
$isDatabase = true;
|
||||||
|
data_set($service, 'is_database', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($isNew) {
|
||||||
|
if ($isDatabase) {
|
||||||
|
$savedService = ServiceDatabase::create([
|
||||||
|
'name' => $serviceName,
|
||||||
|
'service_id' => $this->id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
|
$savedService = ServiceApplication::create([
|
||||||
|
'name' => $serviceName,
|
||||||
|
'fqdn' => $defaultUsableFqdn,
|
||||||
|
'service_id' => $this->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($isDatabase) {
|
||||||
|
$savedService = $this->databases()->whereName($serviceName)->first();
|
||||||
|
} else {
|
||||||
|
$savedService = $this->applications()->whereName($serviceName)->first();
|
||||||
|
if (data_get($savedService, 'fqdn')) {
|
||||||
|
$defaultUsableFqdn = data_get($savedService, 'fqdn', null);
|
||||||
|
} else {
|
||||||
|
if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) {
|
||||||
|
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
|
||||||
|
if (isDev()) {
|
||||||
|
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$savedService->fqdn = $defaultUsableFqdn;
|
||||||
|
$savedService->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$fqdns = data_get($savedService, 'fqdn');
|
||||||
|
if ($fqdns) {
|
||||||
|
$fqdns = collect(Str::of($fqdns)->explode(','));
|
||||||
|
}
|
||||||
|
// Collect ports
|
||||||
|
$servicePorts = collect(data_get($service, 'ports', []));
|
||||||
|
$ports->put($serviceName, $servicePorts);
|
||||||
|
$collectedPorts = collect([]);
|
||||||
|
if ($servicePorts->count() > 0) {
|
||||||
|
foreach ($servicePorts as $sport) {
|
||||||
|
if (is_string($sport) || is_numeric($sport)) {
|
||||||
|
$collectedPorts->push($sport);
|
||||||
|
}
|
||||||
|
if (is_array($sport)) {
|
||||||
|
$target = data_get($sport, 'target');
|
||||||
|
$published = data_get($sport, 'published');
|
||||||
|
$collectedPorts->push("$target:$published");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$savedService->ports = $collectedPorts->implode(',');
|
||||||
|
$savedService->save();
|
||||||
|
// Collect volumes
|
||||||
|
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||||
|
if ($serviceVolumes->count() > 0) {
|
||||||
|
LocalPersistentVolume::whereResourceId($savedService->id)->whereResourceType(get_class($savedService))->delete();
|
||||||
|
foreach ($serviceVolumes as $volume) {
|
||||||
|
if (is_string($volume) && Str::startsWith($volume, './')) {
|
||||||
|
// Local file
|
||||||
|
$fsPath = Str::before($volume, ':');
|
||||||
|
$volumePath = Str::of($volume)->after(':')->beforeLast(':');
|
||||||
|
ray($fsPath, $volumePath);
|
||||||
|
LocalFileVolume::updateOrCreate(
|
||||||
|
[
|
||||||
|
'mount_path' => $volumePath,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fs_path' => $fsPath,
|
||||||
|
'mount_path' => $volumePath,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$volumeName = Str::before($volume, ':');
|
||||||
|
$volumePath = Str::after($volume, ':');
|
||||||
|
}
|
||||||
|
if (is_array($volume)) {
|
||||||
|
$volumeName = data_get($volume, 'source');
|
||||||
|
$volumePath = data_get($volume, 'target');
|
||||||
|
}
|
||||||
|
|
||||||
|
$volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) {
|
||||||
|
return $key == $volumeName;
|
||||||
|
});
|
||||||
|
if (!$volumeExists) {
|
||||||
|
if (Str::startsWith($volumeName, '/')) {
|
||||||
|
$volumes->put($volumeName, $volumePath);
|
||||||
|
LocalPersistentVolume::updateOrCreate(
|
||||||
|
[
|
||||||
|
'mount_path' => $volumePath,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => Str::slug($volumeName, '-'),
|
||||||
|
'mount_path' => $volumePath,
|
||||||
|
'host_path' => $volumeName,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$composeVolumes->put($volumeName, null);
|
||||||
|
LocalPersistentVolume::updateOrCreate(
|
||||||
|
[
|
||||||
|
'name' => $volumeName,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => $volumeName,
|
||||||
|
'mount_path' => $volumePath,
|
||||||
|
'host_path' => null,
|
||||||
|
'resource_id' => $savedService->id,
|
||||||
|
'resource_type' => get_class($savedService)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect and add networks
|
||||||
|
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||||
|
if ($serviceNetworks->count() > 0) {
|
||||||
|
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||||
|
$networkExists = $composeNetworks->contains(function ($value, $key) use ($networkName) {
|
||||||
|
return $value == $networkName || $key == $networkName;
|
||||||
|
});
|
||||||
|
if (!$networkExists) {
|
||||||
|
$composeNetworks->put($networkDetails, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add Coolify specific networks
|
||||||
|
$definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||||
|
return $value == $definedNetwork;
|
||||||
|
});
|
||||||
|
if (!$definedNetworkExists) {
|
||||||
|
$composeNetworks->put($definedNetwork, [
|
||||||
|
'name' => $definedNetwork,
|
||||||
|
'external' => false
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$networks = $serviceNetworks->toArray();
|
||||||
|
$networks = array_merge($networks, [$definedNetwork]);
|
||||||
|
data_set($service, 'networks', $networks);
|
||||||
|
|
||||||
|
|
||||||
|
// Get variables from the service
|
||||||
|
foreach ($serviceVariables as $variable) {
|
||||||
|
$value = Str::after($variable, '=');
|
||||||
|
if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) {
|
||||||
|
$value = Str::of(replaceVariables(Str::of($value)));
|
||||||
|
if ($value->contains(':')) {
|
||||||
|
$nakedName = $value->before(':');
|
||||||
|
$nakedValue = $value->after(':');
|
||||||
|
} else if ($value->contains('-')) {
|
||||||
|
$nakedName = $value->before('-');
|
||||||
|
$nakedValue = $value->after('-');
|
||||||
|
} else if ($value->contains('+')) {
|
||||||
|
$nakedName = $value->before('+');
|
||||||
|
$nakedValue = $value->after('+');
|
||||||
|
} else {
|
||||||
|
$nakedName = $value;
|
||||||
|
}
|
||||||
|
if (isset($nakedName)) {
|
||||||
|
if (isset($nakedValue)) {
|
||||||
|
if ($nakedValue->startsWith('-')) {
|
||||||
|
$nakedValue = Str::of($nakedValue)->after('-');
|
||||||
|
}
|
||||||
|
if ($nakedValue->startsWith('+')) {
|
||||||
|
$nakedValue = Str::of($nakedValue)->after('+');
|
||||||
|
}
|
||||||
|
if (!$envs->has($nakedName->value())) {
|
||||||
|
$envs->put($nakedName->value(), $nakedValue->value());
|
||||||
|
EnvironmentVariable::updateOrCreate([
|
||||||
|
'key' => $nakedName->value(),
|
||||||
|
'service_id' => $this->id,
|
||||||
|
], [
|
||||||
|
'value' => $nakedValue->value(),
|
||||||
|
'is_build_time' => false,
|
||||||
|
'service_id' => $this->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!$envs->has($nakedName->value())) {
|
||||||
|
$envs->put($nakedName->value(), null);
|
||||||
|
$envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists();
|
||||||
|
if (!$envExists) {
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => $nakedName->value(),
|
||||||
|
'value' => null,
|
||||||
|
'service_id' => $this->id,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$variableName = Str::of(replaceVariables(Str::of($value)));
|
||||||
|
$generatedValue = null;
|
||||||
|
if ($variableName->startsWith('SERVICE_USER')) {
|
||||||
|
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
|
||||||
|
if (!$variableDefined) {
|
||||||
|
$generatedValue = Str::random(10);
|
||||||
|
} else {
|
||||||
|
$generatedValue = $variableDefined->value;
|
||||||
|
}
|
||||||
|
if (!$envs->has($variableName->value())) {
|
||||||
|
$envs->put($variableName->value(), $generatedValue);
|
||||||
|
EnvironmentVariable::updateOrCreate([
|
||||||
|
'key' => $variableName->value(),
|
||||||
|
'service_id' => $this->id,
|
||||||
|
], [
|
||||||
|
'value' => $generatedValue,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'service_id' => $this->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else if ($variableName->startsWith('SERVICE_PASSWORD')) {
|
||||||
|
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
|
||||||
|
if (!$variableDefined) {
|
||||||
|
$generatedValue = Str::password(symbols: false);
|
||||||
|
} else {
|
||||||
|
$generatedValue = $variableDefined->value;
|
||||||
|
}
|
||||||
|
if (!$envs->has($variableName->value())) {
|
||||||
|
$envs->put($variableName->value(), $generatedValue);
|
||||||
|
EnvironmentVariable::updateOrCreate([
|
||||||
|
'key' => $variableName->value(),
|
||||||
|
'service_id' => $this->id,
|
||||||
|
], [
|
||||||
|
'value' => $generatedValue,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'service_id' => $this->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else if ($variableName->startsWith('SERVICE_FQDN')) {
|
||||||
|
if ($fqdns) {
|
||||||
|
$number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value();
|
||||||
|
if (is_numeric($number)) {
|
||||||
|
$number = (int) $number - 1;
|
||||||
|
} else {
|
||||||
|
$number = 0;
|
||||||
|
}
|
||||||
|
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
|
||||||
|
$environments = collect(data_get($service, 'environment'));
|
||||||
|
$environments = $environments->map(function ($envValue) use ($value, $fqdn) {
|
||||||
|
$envValue = Str::of($envValue)->replace($value, $fqdn);
|
||||||
|
return $envValue->value();
|
||||||
|
});
|
||||||
|
$service['environment'] = $environments->toArray();
|
||||||
|
}
|
||||||
|
} else if ($variableName->startsWith('SERVICE_URL')) {
|
||||||
|
if ($fqdns) {
|
||||||
|
$number = Str::of($variableName)->after('SERVICE_URL')->afterLast('_')->value();
|
||||||
|
if (is_numeric($number)) {
|
||||||
|
$number = (int) $number - 1;
|
||||||
|
} else {
|
||||||
|
$number = 0;
|
||||||
|
}
|
||||||
|
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
|
||||||
|
$url = Url::fromString($fqdn)->getHost();
|
||||||
|
$environments = collect(data_get($service, 'environment'));
|
||||||
|
$environments = $environments->map(function ($envValue) use ($value, $url) {
|
||||||
|
$envValue = Str::of($envValue)->replace($value, $url);
|
||||||
|
return $envValue->value();
|
||||||
|
});
|
||||||
|
$service['environment'] = $environments->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->server->proxyType() === ProxyTypes::TRAEFIK_V2->value) {
|
||||||
|
$labels = collect(data_get($service, 'labels', []));
|
||||||
|
$labels = collect([]);
|
||||||
|
$labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service'));
|
||||||
|
if (!$isDatabase) {
|
||||||
|
if ($fqdns) {
|
||||||
|
$labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data_set($service, 'labels', $labels->toArray());
|
||||||
|
}
|
||||||
|
data_forget($service, 'is_database');
|
||||||
|
data_set($service, 'restart', RESTART_MODE);
|
||||||
|
data_set($service, 'container_name', $container_name);
|
||||||
|
data_forget($service, 'documentation');
|
||||||
|
return $service;
|
||||||
|
});
|
||||||
|
$finalServices = [
|
||||||
|
'version' => $dockerComposeVersion,
|
||||||
|
'services' => $services->toArray(),
|
||||||
|
'volumes' => $composeVolumes->toArray(),
|
||||||
|
'networks' => $composeNetworks->toArray(),
|
||||||
|
];
|
||||||
|
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||||
|
$this->save();
|
||||||
|
$shouldBeDefined = collect([
|
||||||
|
'envs' => $envs,
|
||||||
|
'volumes' => $volumes,
|
||||||
|
'ports' => $ports
|
||||||
|
]);
|
||||||
|
$parsedCompose = collect([
|
||||||
|
'dockerCompose' => $finalServices,
|
||||||
|
'shouldBeDefined' => $shouldBeDefined
|
||||||
|
]);
|
||||||
|
return $parsedCompose;
|
||||||
|
} else {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/Models/ServiceApplication.php
Normal file
34
app/Models/ServiceApplication.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ServiceApplication extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
public function type()
|
||||||
|
{
|
||||||
|
return 'service';
|
||||||
|
}
|
||||||
|
public function documentation()
|
||||||
|
{
|
||||||
|
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
|
||||||
|
}
|
||||||
|
public function service()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Service::class);
|
||||||
|
}
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
public function fileStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/Models/ServiceDatabase.php
Normal file
29
app/Models/ServiceDatabase.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class ServiceDatabase extends BaseModel
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
public function type()
|
||||||
|
{
|
||||||
|
return 'service';
|
||||||
|
}
|
||||||
|
public function documentation()
|
||||||
|
{
|
||||||
|
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
|
||||||
|
}
|
||||||
|
public function service()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Service::class);
|
||||||
|
}
|
||||||
|
public function persistentStorages()
|
||||||
|
{
|
||||||
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,11 @@ class StandaloneDocker extends BaseModel
|
|||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function services()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Service::class, 'destination');
|
||||||
|
}
|
||||||
|
|
||||||
public function attachedTo()
|
public function attachedTo()
|
||||||
{
|
{
|
||||||
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class StandalonePostgresql extends BaseModel
|
|||||||
static::deleted(function ($database) {
|
static::deleted(function ($database) {
|
||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->environment_variables()->delete();
|
||||||
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Textarea extends Component
|
|||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $realtimeValidation = false,
|
public bool $realtimeValidation = false,
|
||||||
public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
public string $defaultClass = "textarea leading-normal bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
26
app/View/Components/Services/Explanation.php
Normal file
26
app/View/Components/Services/Explanation.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components\Services;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Explanation extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.services.explanation');
|
||||||
|
}
|
||||||
|
}
|
||||||
46
app/View/Components/Services/Links.php
Normal file
46
app/View/Components/Services/Links.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components\Services;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class Links extends Component
|
||||||
|
{
|
||||||
|
public Collection $links;
|
||||||
|
public function __construct(public Service $service)
|
||||||
|
{
|
||||||
|
$this->links = collect([]);
|
||||||
|
$service->applications()->get()->map(function ($application) {
|
||||||
|
if ($application->fqdn) {
|
||||||
|
$fqdns = collect(Str::of($application->fqdn)->explode(','));
|
||||||
|
$fqdns->map(function ($fqdn) {
|
||||||
|
$this->links->push(getOnlyFqdn($fqdn));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($application->ports) {
|
||||||
|
$portsCollection = collect(Str::of($application->ports)->explode(','));
|
||||||
|
$portsCollection->map(function ($port) {
|
||||||
|
if (Str::of($port)->contains(':')) {
|
||||||
|
$hostPort = Str::of($port)->before(':');
|
||||||
|
} else {
|
||||||
|
$hostPort = $port;
|
||||||
|
}
|
||||||
|
$this->links->push(base_url(withPort:false) . ":{$hostPort}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.services.links');
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/View/Components/Status/Index.php
Normal file
28
app/View/Components/Status/Index.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components\Status;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public string $status = 'exited',
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.status.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/View/Components/Status/Services.php
Normal file
29
app/View/Components/Status/Services.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\View\Components\Status;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\View\Component;
|
||||||
|
|
||||||
|
class Services extends Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a new component instance.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
public Service $service,
|
||||||
|
public string $complexStatus = 'exited',
|
||||||
|
) {
|
||||||
|
$this->complexStatus = serviceStatus($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view / contents that represent the component.
|
||||||
|
*/
|
||||||
|
public function render(): View|Closure|string
|
||||||
|
{
|
||||||
|
return view('components.status.services');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,3 +10,16 @@ const VALID_CRON_STRINGS = [
|
|||||||
'yearly' => '0 0 1 1 *',
|
'yearly' => '0 0 1 1 *',
|
||||||
];
|
];
|
||||||
const RESTART_MODE = 'unless-stopped';
|
const RESTART_MODE = 'unless-stopped';
|
||||||
|
|
||||||
|
const DATABASE_DOCKER_IMAGES = [
|
||||||
|
'mysql',
|
||||||
|
'mariadb',
|
||||||
|
'postgres',
|
||||||
|
'mongo',
|
||||||
|
'redis',
|
||||||
|
'memcached',
|
||||||
|
'couchdb',
|
||||||
|
'neo4j',
|
||||||
|
'influxdb',
|
||||||
|
'clickhouse'
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
function getCurrentApplicationContainerStatus(Server $server, int $id): Collection {
|
function getCurrentApplicationContainerStatus(Server $server, int $id): Collection
|
||||||
|
{
|
||||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||||
if (!$containers) {
|
if (!$containers) {
|
||||||
return collect([]);
|
return collect([]);
|
||||||
@@ -26,7 +30,7 @@ function format_docker_command_output_to_json($rawOutput): Collection
|
|||||||
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
|
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_docker_labels_to_json(string|Array $rawOutput): Collection
|
function format_docker_labels_to_json(string|array $rawOutput): Collection
|
||||||
{
|
{
|
||||||
if (is_array($rawOutput)) {
|
if (is_array($rawOutput)) {
|
||||||
return collect($rawOutput);
|
return collect($rawOutput);
|
||||||
@@ -59,7 +63,8 @@ function format_docker_envs_to_json($rawOutput)
|
|||||||
return collect([]);
|
return collect([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function checkMinimumDockerEngineVersion($dockerVersion) {
|
function checkMinimumDockerEngineVersion($dockerVersion)
|
||||||
|
{
|
||||||
$majorDockerVersion = Str::of($dockerVersion)->before('.')->value();
|
$majorDockerVersion = Str::of($dockerVersion)->before('.')->value();
|
||||||
if ($majorDockerVersion <= 22) {
|
if ($majorDockerVersion <= 22) {
|
||||||
$dockerVersion = null;
|
$dockerVersion = null;
|
||||||
@@ -72,7 +77,8 @@ function executeInDocker(string $containerId, string $command)
|
|||||||
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
|
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApplicationContainerStatus(Application $application) {
|
function getApplicationContainerStatus(Application $application)
|
||||||
|
{
|
||||||
$server = data_get($application, 'destination.server');
|
$server = data_get($application, 'destination.server');
|
||||||
$id = $application->id;
|
$id = $application->id;
|
||||||
if (!$server) {
|
if (!$server) {
|
||||||
@@ -98,13 +104,13 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
return data_get($container[0], 'State.Status', 'exited');
|
return data_get($container[0], 'State.Status', 'exited');
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateApplicationContainerName(string $uuid, int $pull_request_id = 0)
|
function generateApplicationContainerName(Application $application)
|
||||||
{
|
{
|
||||||
$now = now()->format('Hisu');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($application->pull_request_id !== 0 && $application->pull_request_id !== null) {
|
||||||
return $uuid . '-pr-' . $pull_request_id;
|
return $application->uuid . '-pr-' . $application->pull_request_id;
|
||||||
} else {
|
} else {
|
||||||
return $uuid . '-' . $now;
|
return $application->uuid . '-' . $now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function get_port_from_dockerfile($dockerfile): int
|
function get_port_from_dockerfile($dockerfile): int
|
||||||
@@ -123,3 +129,96 @@ function get_port_from_dockerfile($dockerfile): int
|
|||||||
}
|
}
|
||||||
return 80;
|
return 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application')
|
||||||
|
{
|
||||||
|
$labels = collect([]);
|
||||||
|
$labels->push('coolify.managed=true');
|
||||||
|
$labels->push('coolify.version=' . config('version'));
|
||||||
|
$labels->push("coolify." . $type . "Id=" . $id);
|
||||||
|
$labels->push("coolify.type=$type");
|
||||||
|
$labels->push('coolify.name=' . $name);
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$labels->push('coolify.pullRequestId=' . $pull_request_id);
|
||||||
|
}
|
||||||
|
return $labels;
|
||||||
|
}
|
||||||
|
function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled)
|
||||||
|
{
|
||||||
|
$labels = collect([]);
|
||||||
|
$labels->push('traefik.enable=true');
|
||||||
|
foreach($domains as $domain) {
|
||||||
|
$url = Url::fromString($domain);
|
||||||
|
$host = $url->getHost();
|
||||||
|
$path = $url->getPath();
|
||||||
|
$schema = $url->getScheme();
|
||||||
|
$port = $url->getPort();
|
||||||
|
$slug = Str::slug($host . $path);
|
||||||
|
|
||||||
|
$http_label = "{$container_name}-{$slug}-http";
|
||||||
|
$https_label = "{$container_name}-{$slug}-https";
|
||||||
|
|
||||||
|
if ($schema === 'https') {
|
||||||
|
// Set labels for https
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.entryPoints=https");
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
|
||||||
|
if ($port) {
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.service={$https_label}");
|
||||||
|
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
|
||||||
|
}
|
||||||
|
if ($path !== '/') {
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix");
|
||||||
|
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
|
||||||
|
|
||||||
|
// Set labels for http (redirect to https)
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||||
|
if ($is_force_https_enabled) {
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Set labels for http
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
|
||||||
|
if ($port) {
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
|
||||||
|
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
|
||||||
|
}
|
||||||
|
if ($path !== '/') {
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix");
|
||||||
|
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $labels;
|
||||||
|
}
|
||||||
|
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
||||||
|
{
|
||||||
|
|
||||||
|
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||||
|
$container_name = generateApplicationContainerName($application);
|
||||||
|
$appId = $application->id;
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$appId = $appId . '-pr-' . $application->pull_request_id;
|
||||||
|
}
|
||||||
|
$labels = collect([]);
|
||||||
|
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
|
||||||
|
if ($application->fqdn) {
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
|
||||||
|
} else {
|
||||||
|
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||||
|
}
|
||||||
|
if ($application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) {
|
||||||
|
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $labels->all();
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,22 @@ function get_proxy_path()
|
|||||||
$proxy_path = "$base_path/proxy";
|
$proxy_path = "$base_path/proxy";
|
||||||
return $proxy_path;
|
return $proxy_path;
|
||||||
}
|
}
|
||||||
|
function connectProxyToNetworks(Server $server) {
|
||||||
|
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||||
|
return $docker['network'];
|
||||||
|
})->unique();
|
||||||
|
if ($networks->count() === 0) {
|
||||||
|
$networks = collect(['coolify']);
|
||||||
|
}
|
||||||
|
$commands = $networks->map(function ($network) {
|
||||||
|
return [
|
||||||
|
"echo '####### Connecting coolify-proxy to $network network...'",
|
||||||
|
"docker network ls --format '{{.Name}}' | grep '^$network$' || docker network create --attachable $network >/dev/null",
|
||||||
|
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return $commands->flatten();
|
||||||
|
}
|
||||||
function generate_default_proxy_configuration(Server $server)
|
function generate_default_proxy_configuration(Server $server)
|
||||||
{
|
{
|
||||||
$proxy_path = get_proxy_path();
|
$proxy_path = get_proxy_path();
|
||||||
@@ -91,12 +106,11 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
|
|
||||||
function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||||
{
|
{
|
||||||
ray('called');
|
|
||||||
$traefik_dynamic_conf_path = get_proxy_path() . "/dynamic";
|
$traefik_dynamic_conf_path = get_proxy_path() . "/dynamic";
|
||||||
$traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml";
|
$traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml";
|
||||||
ray($redirect_url);
|
|
||||||
if (empty($redirect_url)) {
|
if (empty($redirect_url)) {
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
|
"mkdir -p $traefik_dynamic_conf_path",
|
||||||
"rm -f $traefik_default_redirect_file",
|
"rm -f $traefik_default_redirect_file",
|
||||||
], $server);
|
], $server);
|
||||||
} else {
|
} else {
|
||||||
@@ -156,7 +170,6 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
|||||||
$yaml;
|
$yaml;
|
||||||
|
|
||||||
$base64 = base64_encode($yaml);
|
$base64 = base64_encode($yaml);
|
||||||
ray("mkdir -p $traefik_dynamic_conf_path");
|
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"mkdir -p $traefik_dynamic_conf_path",
|
"mkdir -p $traefik_dynamic_conf_path",
|
||||||
"echo '$base64' | base64 -d > $traefik_default_redirect_file",
|
"echo '$base64' | base64 -d > $traefik_default_redirect_file",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use Illuminate\Support\Str;
|
|||||||
use Spatie\Activitylog\Contracts\Activity;
|
use Spatie\Activitylog\Contracts\Activity;
|
||||||
|
|
||||||
function remote_process(
|
function remote_process(
|
||||||
array $command,
|
Collection|array $command,
|
||||||
Server $server,
|
Server $server,
|
||||||
?string $type = null,
|
?string $type = null,
|
||||||
?string $type_uuid = null,
|
?string $type_uuid = null,
|
||||||
@@ -26,6 +26,9 @@ function remote_process(
|
|||||||
if (is_null($type)) {
|
if (is_null($type)) {
|
||||||
$type = ActivityTypes::INLINE->value;
|
$type = ActivityTypes::INLINE->value;
|
||||||
}
|
}
|
||||||
|
if ($command instanceof Collection) {
|
||||||
|
$command = $command->toArray();
|
||||||
|
}
|
||||||
$command_string = implode("\n", $command);
|
$command_string = implode("\n", $command);
|
||||||
if (auth()->user()) {
|
if (auth()->user()) {
|
||||||
$teams = auth()->user()->teams->pluck('id');
|
$teams = auth()->user()->teams->pluck('id');
|
||||||
@@ -33,7 +36,6 @@ function remote_process(
|
|||||||
throw new \Exception("User is not part of the team that owns this server");
|
throw new \Exception("User is not part of the team that owns this server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(PrepareCoolifyTask::class, [
|
return resolve(PrepareCoolifyTask::class, [
|
||||||
'remoteProcessArgs' => new CoolifyTaskArgs(
|
'remoteProcessArgs' => new CoolifyTaskArgs(
|
||||||
server_uuid: $server->uuid,
|
server_uuid: $server->uuid,
|
||||||
@@ -83,6 +85,9 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
|||||||
if ($isMux && config('coolify.mux_enabled')) {
|
if ($isMux && config('coolify.mux_enabled')) {
|
||||||
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
||||||
}
|
}
|
||||||
|
if (data_get($server,'settings.is_cloudflare_tunnel')) {
|
||||||
|
$ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
|
||||||
$ssh_command .= "-i {$privateKeyLocation} "
|
$ssh_command .= "-i {$privateKeyLocation} "
|
||||||
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
|
||||||
@@ -99,8 +104,11 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
|||||||
// ray($ssh_command);
|
// ray($ssh_command);
|
||||||
return $ssh_command;
|
return $ssh_command;
|
||||||
}
|
}
|
||||||
function instant_remote_process(array $command, Server $server, $throwError = true)
|
function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
|
||||||
{
|
{
|
||||||
|
if ($command instanceof Collection) {
|
||||||
|
$command = $command->toArray();
|
||||||
|
}
|
||||||
$command_string = implode("\n", $command);
|
$command_string = implode("\n", $command);
|
||||||
$ssh_command = generateSshCommand($server, $command_string);
|
$ssh_command = generateSshCommand($server, $command_string);
|
||||||
$process = Process::run($ssh_command);
|
$process = Process::run($ssh_command);
|
||||||
|
|||||||
46
bootstrap/helpers/services.php
Normal file
46
bootstrap/helpers/services.php
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
function replaceRegex(?string $name = null)
|
||||||
|
{
|
||||||
|
return "/\\\${?{$name}[^}]*}?|\\\${$name}\w+/";
|
||||||
|
}
|
||||||
|
function collectRegex(string $name)
|
||||||
|
{
|
||||||
|
return "/{$name}\w+/";
|
||||||
|
}
|
||||||
|
function replaceVariables($variable)
|
||||||
|
{
|
||||||
|
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function serviceStatus(Service $service)
|
||||||
|
{
|
||||||
|
$foundRunning = false;
|
||||||
|
$isDegraded = false;
|
||||||
|
$applications = $service->applications;
|
||||||
|
$databases = $service->databases;
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (Str::of($application->status)->startsWith('running')) {
|
||||||
|
$foundRunning = true;
|
||||||
|
} else {
|
||||||
|
$isDegraded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($databases as $database) {
|
||||||
|
if (Str::of($database->status)->startsWith('running')) {
|
||||||
|
$foundRunning = true;
|
||||||
|
} else {
|
||||||
|
$isDegraded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($foundRunning && !$isDegraded) {
|
||||||
|
return 'running';
|
||||||
|
} else if ($foundRunning && $isDegraded) {
|
||||||
|
return 'degraded';
|
||||||
|
} else if (!$foundRunning && $isDegraded) {
|
||||||
|
return 'exited';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,24 +20,31 @@ use Nubs\RandomNameGenerator\All;
|
|||||||
use Poliander\Cron\CronExpression;
|
use Poliander\Cron\CronExpression;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
use phpseclib3\Crypt\RSA;
|
use phpseclib3\Crypt\RSA;
|
||||||
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
|
function base_configuration_dir(): string
|
||||||
|
{
|
||||||
|
return '/data/coolify';
|
||||||
|
}
|
||||||
function application_configuration_dir(): string
|
function application_configuration_dir(): string
|
||||||
{
|
{
|
||||||
return '/data/coolify/applications';
|
return base_configuration_dir() . "/applications";
|
||||||
|
}
|
||||||
|
function service_configuration_dir(): string
|
||||||
|
{
|
||||||
|
return base_configuration_dir() . "/services";
|
||||||
}
|
}
|
||||||
|
|
||||||
function database_configuration_dir(): string
|
function database_configuration_dir(): string
|
||||||
{
|
{
|
||||||
return '/data/coolify/databases';
|
return base_configuration_dir() . "/databases";
|
||||||
}
|
}
|
||||||
function database_proxy_dir($uuid): string
|
function database_proxy_dir($uuid): string
|
||||||
{
|
{
|
||||||
return "/data/coolify/databases/$uuid/proxy";
|
return base_configuration_dir() . "/databases/$uuid/proxy";
|
||||||
}
|
}
|
||||||
|
|
||||||
function backup_dir(): string
|
function backup_dir(): string
|
||||||
{
|
{
|
||||||
return '/data/coolify/backups';
|
return base_configuration_dir() . "/backups";
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_readme_file(string $name, string $updated_at): string
|
function generate_readme_file(string $name, string $updated_at): string
|
||||||
@@ -77,6 +84,7 @@ function refreshSession(?Team $team = null): void
|
|||||||
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
||||||
{
|
{
|
||||||
ray('handleError');
|
ray('handleError');
|
||||||
|
ray($error);
|
||||||
if ($error instanceof Throwable) {
|
if ($error instanceof Throwable) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
} else {
|
} else {
|
||||||
@@ -94,6 +102,7 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
if (isset($livewire)) {
|
if (isset($livewire)) {
|
||||||
return $livewire->emit('error', $message);
|
return $livewire->emit('error', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException($message);
|
throw new RuntimeException($message);
|
||||||
}
|
}
|
||||||
function general_error_handler(Throwable $err, Livewire\Component $that = null, $isJson = false, $customErrorMessage = null): mixed
|
function general_error_handler(Throwable $err, Livewire\Component $that = null, $isJson = false, $customErrorMessage = null): mixed
|
||||||
@@ -151,10 +160,12 @@ function get_latest_version_of_coolify(): string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generate_random_name(): string
|
function generate_random_name(?string $cuid = null): string
|
||||||
{
|
{
|
||||||
$generator = All::create();
|
$generator = All::create();
|
||||||
|
if (is_null($cuid)) {
|
||||||
$cuid = new Cuid2(7);
|
$cuid = new Cuid2(7);
|
||||||
|
}
|
||||||
return Str::kebab("{$generator->getName()}-$cuid");
|
return Str::kebab("{$generator->getName()}-$cuid");
|
||||||
}
|
}
|
||||||
function generateSSHKey()
|
function generateSSHKey()
|
||||||
@@ -173,9 +184,11 @@ function formatPrivateKey(string $privateKey)
|
|||||||
}
|
}
|
||||||
return $privateKey;
|
return $privateKey;
|
||||||
}
|
}
|
||||||
function generate_application_name(string $git_repository, string $git_branch): string
|
function generate_application_name(string $git_repository, string $git_branch, ?string $cuid = null): string
|
||||||
{
|
{
|
||||||
|
if (is_null($cuid)) {
|
||||||
$cuid = new Cuid2(7);
|
$cuid = new Cuid2(7);
|
||||||
|
}
|
||||||
return Str::kebab("$git_repository:$git_branch-$cuid");
|
return Str::kebab("$git_repository:$git_branch-$cuid");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,7 +240,12 @@ function base_ip(): string
|
|||||||
}
|
}
|
||||||
return "localhost";
|
return "localhost";
|
||||||
}
|
}
|
||||||
|
function getOnlyFqdn(String $fqdn) {
|
||||||
|
$url = Url::fromString($fqdn);
|
||||||
|
$host = $url->getHost();
|
||||||
|
$scheme = $url->getScheme();
|
||||||
|
return "$scheme://$host";
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* If fqdn is set, return it, otherwise return public ip.
|
* If fqdn is set, return it, otherwise return public ip.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'limits' => [
|
'limits' => [
|
||||||
'trial_period'=> 14,
|
'trial_period'=> 7,
|
||||||
'server' => [
|
'server' => [
|
||||||
'zero' => 0,
|
'zero' => 0,
|
||||||
'self-hosted' => 999999999999,
|
'self-hosted' => 999999999999,
|
||||||
|
|||||||
@@ -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.43',
|
'release' => '4.0.0-beta.44',
|
||||||
// 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'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.43';
|
return '4.0.0-beta.44';
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ return new class extends Migration
|
|||||||
$table->string('uuid')->unique();
|
$table->string('uuid')->unique();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
|
|
||||||
$table->morphs('destination');
|
$table->foreignId('server_id')->nullable();
|
||||||
|
|
||||||
$table->foreignId('environment_id');
|
$table->foreignId('environment_id');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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('services', function (Blueprint $table) {
|
||||||
|
$table->longText('description')->nullable();
|
||||||
|
$table->longText('docker_compose_raw');
|
||||||
|
$table->longText('docker_compose')->nullable();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('services', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('description');
|
||||||
|
$table->dropColumn('docker_compose_raw');
|
||||||
|
$table->dropColumn('docker_compose');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?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::create('service_databases', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('human_name')->nullable();
|
||||||
|
$table->longText('description')->nullable();
|
||||||
|
|
||||||
|
$table->longText('ports')->nullable();
|
||||||
|
$table->longText('exposes')->nullable();
|
||||||
|
|
||||||
|
$table->string('status')->default('exited');
|
||||||
|
|
||||||
|
$table->foreignId('service_id');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('service_databases');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?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::create('service_applications', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('human_name')->nullable();
|
||||||
|
$table->longText('description')->nullable();
|
||||||
|
|
||||||
|
$table->string('fqdn')->unique()->nullable();
|
||||||
|
$table->longText('ports')->nullable();
|
||||||
|
$table->longText('exposes')->nullable();
|
||||||
|
|
||||||
|
$table->string('status')->default('exited');
|
||||||
|
|
||||||
|
$table->foreignId('service_id');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('service_applications');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?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('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('service_id')->nullable();
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('service_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?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::create('local_file_volumes', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid');
|
||||||
|
$table->mediumText('fs_path');
|
||||||
|
$table->string('mount_path');
|
||||||
|
$table->mediumText('content')->nullable();
|
||||||
|
$table->nullableMorphs('resource');
|
||||||
|
|
||||||
|
$table->unique(['mount_path', 'resource_id', 'resource_type']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('local_file_volumes');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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('server_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_cloudflare_tunnel')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_cloudflare_tunnel');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
17
database/seeders/LocalFileVolumeSeeder.php
Normal file
17
database/seeders/LocalFileVolumeSeeder.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class LocalFileVolumeSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
17
database/seeders/ServiceApplicationSeeder.php
Normal file
17
database/seeders/ServiceApplicationSeeder.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class ServiceApplicationSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
17
database/seeders/ServiceDatabaseSeeder.php
Normal file
17
database/seeders/ServiceDatabaseSeeder.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class ServiceDatabaseSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
17
database/seeders/ServiceSeeder.php
Normal file
17
database/seeders/ServiceSeeder.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class ServiceSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ ARG DOCKER_BUILDX_VERSION=0.11.2
|
|||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.30.0
|
ARG PACK_VERSION=0.30.0
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.14.0
|
ARG NIXPACKS_VERSION=1.16.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /artifacts
|
WORKDIR /artifacts
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
FROM serversideup/php:8.2-fpm-nginx
|
FROM serversideup/php:8.2-fpm-nginx
|
||||||
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
|
ARG CLOUDFLARED_VERSION=2023.8.2
|
||||||
|
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
# Postgres version requirements
|
# Postgres version requirements
|
||||||
@@ -13,15 +17,23 @@ RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
|
|||||||
|
|
||||||
# Coolify requirements
|
# Coolify requirements
|
||||||
RUN apt-get install -y php-pgsql openssh-client git git-lfs jq lsof
|
RUN apt-get install -y php-pgsql openssh-client git git-lfs jq lsof
|
||||||
|
|
||||||
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
||||||
|
COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/
|
||||||
|
|
||||||
COPY docker/dev-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
|
COPY docker/dev-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||||
|
|
||||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
||||||
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
||||||
RUN echo "alias mfs='php artisan migrate:fresh --seed'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias cda='composer dump-autoload'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias run='./scripts/run'" >>/etc/bash.bashrc
|
|
||||||
|
|
||||||
COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/
|
RUN mkdir -p /usr/local/bin
|
||||||
|
|
||||||
|
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
||||||
|
echo 'amd64' && \
|
||||||
|
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
|
;fi"
|
||||||
|
|
||||||
|
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||||
|
echo 'arm64' && \
|
||||||
|
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
|
;fi"
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,14 @@ RUN npm install
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM serversideup/php:8.2-fpm-nginx
|
FROM serversideup/php:8.2-fpm-nginx
|
||||||
WORKDIR /var/www/html
|
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
|
ARG CLOUDFLARED_VERSION=2023.8.2
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
# Postgres version requirements
|
# Postgres version requirements
|
||||||
RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y
|
RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y
|
||||||
@@ -44,7 +49,16 @@ RUN php artisan view:cache
|
|||||||
|
|
||||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
||||||
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
||||||
RUN echo "alias mfs='php artisan migrate:fresh --seed'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias cda='composer dump-autoload'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias run='./scripts/run'" >>/etc/bash.bashrc
|
|
||||||
RUN echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/bash.bashrc
|
RUN echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/bash.bashrc
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/local/bin
|
||||||
|
|
||||||
|
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
||||||
|
echo 'amd64' && \
|
||||||
|
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
|
;fi"
|
||||||
|
|
||||||
|
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||||
|
echo 'arm64' && \
|
||||||
|
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
|
;fi"
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ ARG DOCKER_VERSION=24.0.5
|
|||||||
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.11.2
|
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||||
# https://github.com/buildpacks/pack/releases
|
|
||||||
ARG PACK_VERSION=0.30.0
|
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
|
||||||
ARG NIXPACKS_VERSION=1.14.0
|
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
|||||||
51
examples/docker-compose-ghost.yaml
Normal file
51
examples/docker-compose-ghost.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
services:
|
||||||
|
ghost:
|
||||||
|
documentation: https://ghost.org/docs/config
|
||||||
|
image: ghost:5
|
||||||
|
volumes:
|
||||||
|
- ghost-content-data:/var/lib/ghost/content
|
||||||
|
- type: volume
|
||||||
|
source: /data/g
|
||||||
|
target: /data
|
||||||
|
volume:
|
||||||
|
nocopy: true
|
||||||
|
environment:
|
||||||
|
- url=$SERVICE_FQDN_GHOST
|
||||||
|
- database__client=mysql
|
||||||
|
- database__connection__host=mysql
|
||||||
|
- database__connection__user=$SERVICE_USER_MYSQL
|
||||||
|
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||||
|
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- alias1
|
||||||
|
- alias3
|
||||||
|
ipv4_address: 172.16.238.10
|
||||||
|
ipv6_address: 2001:3984:3989::10
|
||||||
|
ports:
|
||||||
|
- "2368"
|
||||||
|
- 1234:2368
|
||||||
|
- target: 2368
|
||||||
|
published: 1234
|
||||||
|
protocol: tcp
|
||||||
|
mode: host
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
documentation: https://hub.docker.com/_/mysql
|
||||||
|
image: mysql:8.0
|
||||||
|
volumes:
|
||||||
|
- ghost-mysql-data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||||
|
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||||
|
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||||
|
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: "172.16.238.0/24"
|
||||||
|
- subnet: "2001:3984:3989::/64"
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
<div class="group">
|
<div class="group">
|
||||||
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Links
|
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
|
||||||
<x-chevron-down />
|
<x-chevron-down />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="absolute hidden group-hover:block">
|
<div class="absolute hidden group-hover:block">
|
||||||
<ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
|
<ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
|
||||||
|
@if (data_get($application, 'gitBrancLocation'))
|
||||||
<li>
|
<li>
|
||||||
<a target="_blank"
|
<a target="_blank"
|
||||||
class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||||
@@ -13,11 +14,12 @@
|
|||||||
Git Repository
|
Git Repository
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@endif
|
||||||
@if (data_get($application, 'fqdn'))
|
@if (data_get($application, 'fqdn'))
|
||||||
@foreach (Str::of(data_get($application, 'fqdn'))->explode(',') as $fqdn)
|
@foreach (Str::of(data_get($application, 'fqdn'))->explode(',') as $fqdn)
|
||||||
<li>
|
<li>
|
||||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||||
target="_blank" href="{{ $fqdn }}">
|
target="_blank" href="{{ getOnlyFqdn($fqdn) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
@@ -26,17 +28,17 @@
|
|||||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||||
<path
|
<path
|
||||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||||
</svg>{{ $fqdn }}
|
</svg>{{ getOnlyFqdn($fqdn) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
@if (data_get($application, 'previews')->count() > 0)
|
@if (data_get($application, 'previews', collect([]))->count() > 0)
|
||||||
@foreach (data_get($application, 'previews') as $preview)
|
@foreach (data_get($application, 'previews') as $preview)
|
||||||
@if (data_get($preview, 'fqdn'))
|
@if (data_get($preview, 'fqdn'))
|
||||||
<li>
|
<li>
|
||||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||||
target="_blank" href="{{ data_get($preview, 'fqdn') }}">
|
target="_blank" href="{{ getOnlyFqdn(data_get($preview, 'fqdn')) }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
|
|||||||
@@ -1,32 +1,12 @@
|
|||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label class="flex items-center gap-1 mb-1 text-sm font-medium">
|
<label for="small-input" class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
|
||||||
<span>
|
|
||||||
@if ($label)
|
|
||||||
{{ $label }}
|
|
||||||
@else
|
|
||||||
{{ $id }}
|
|
||||||
@endif
|
|
||||||
@if ($required)
|
@if ($required)
|
||||||
<x-highlighted text="*" />
|
<x-highlighted text="*" />
|
||||||
@endif
|
@endif
|
||||||
@if ($helper)
|
@if ($helper)
|
||||||
<div class="group">
|
<x-helper :helper="$helper" />
|
||||||
<div class="cursor-pointer text-warning">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
|
||||||
class="w-4 h-4 stroke-current">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="absolute hidden text-xs group-hover:block border-coolgray-400 bg-coolgray-500">
|
|
||||||
<div class="p-4 card-body">
|
|
||||||
{!! $helper !!}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
@endif
|
@endif
|
||||||
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||||
|
|||||||
@@ -38,12 +38,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@if ($resource->status === 'running')
|
@if ($resource->getMorphClass() == 'App\Models\Service')
|
||||||
<x-status.running />
|
<x-status.services :service="$resource" />
|
||||||
@elseif($resource->status === 'restarting')
|
|
||||||
<x-status.restarting />
|
|
||||||
@else
|
@else
|
||||||
<x-status.stopped />
|
<x-status.index :status="$resource->status" />
|
||||||
@endif
|
@endif
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
<livewire:server.proxy.modal :server="$server" />
|
<livewire:server.proxy.modal :server="$server" />
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Server</h1>
|
<h1>Server</h1>
|
||||||
@if ($server->settings->is_reachable)
|
|
||||||
<livewire:server.proxy.status :server="$server" />
|
<livewire:server.proxy.status :server="$server" />
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle ">{{ data_get($server, 'name') }}</div>
|
<div class="subtitle ">{{ data_get($server, 'name') }}</div>
|
||||||
<nav class="navbar-main">
|
<nav class="navbar-main">
|
||||||
|
|||||||
28
resources/views/components/services/links.blade.php
Normal file
28
resources/views/components/services/links.blade.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="group">
|
||||||
|
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
|
||||||
|
<x-chevron-down />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="absolute hidden group-hover:block">
|
||||||
|
<ul tabindex="0" class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
|
||||||
|
@if ($links->count() > 0)
|
||||||
|
@foreach ($links as $link)
|
||||||
|
<li>
|
||||||
|
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||||
|
target="_blank" href="{{ $link }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M9 15l6 -6" />
|
||||||
|
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||||
|
<path
|
||||||
|
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||||
|
</svg>{{ $link }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
45
resources/views/components/services/navbar.blade.php
Normal file
45
resources/views/components/services/navbar.blade.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<div class="navbar-main">
|
||||||
|
<x-services.links :service="$service" />
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
@if (serviceStatus($service) === 'running')
|
||||||
|
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||||
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||||
|
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</button>
|
||||||
|
@elseif(serviceStatus($service) === 'exited')
|
||||||
|
<button wire:click='deploy' onclick="startService.showModal()"
|
||||||
|
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M7 4v16l13 -8z" />
|
||||||
|
</svg>
|
||||||
|
Deploy
|
||||||
|
</button>
|
||||||
|
@elseif (serviceStatus($service) === '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">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||||
|
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||||
|
<path d="M20 4v5h-5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Restart Degraded Services
|
||||||
|
</button>
|
||||||
|
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||||
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||||
|
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
8
resources/views/components/status/degraded.blade.php
Normal file
8
resources/views/components/status/degraded.blade.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
@props([
|
||||||
|
'status' => 'Degraded',
|
||||||
|
])
|
||||||
|
<x-loading wire:loading.delay />
|
||||||
|
<div class="flex items-center gap-2" wire:loading.remove.delay.longer>
|
||||||
|
<div class="badge badge-warning badge-xs"></div>
|
||||||
|
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>
|
||||||
|
</div>
|
||||||
7
resources/views/components/status/index.blade.php
Normal file
7
resources/views/components/status/index.blade.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@if (Str::of($status)->startsWith('running'))
|
||||||
|
<x-status.running :status="$status" />
|
||||||
|
@elseif(Str::of($status)->startsWith('restarting'))
|
||||||
|
<x-status.restarting :status="$status" />
|
||||||
|
@else
|
||||||
|
<x-status.stopped :status="$status" />
|
||||||
|
@endif
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@props([
|
@props([
|
||||||
'text' => 'Restarting',
|
'status' => 'Restarting',
|
||||||
])
|
])
|
||||||
<x-loading wire:loading.delay />
|
<x-loading wire:loading.delay />
|
||||||
<div class="flex items-center gap-2" wire:loading.remove.delay.longer>
|
<div class="flex items-center gap-2" wire:loading.remove.delay.longer>
|
||||||
<div class="badge badge-warning badge-xs"></div>
|
<div class="badge badge-warning badge-xs"></div>
|
||||||
<div class="text-xs font-medium tracking-wide text-warning">{{ $text }}</div>
|
<div class="text-xs font-medium tracking-wide text-warning">{{ Str::headline($status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@props([
|
@props([
|
||||||
'text' => 'Running',
|
'status' => 'Running',
|
||||||
])
|
])
|
||||||
<x-loading wire:loading.delay.longer />
|
<x-loading wire:loading.delay.longer />
|
||||||
<div class="flex items-center gap-2 " wire:loading.remove.delay.longer>
|
<div class="flex items-center gap-2 " wire:loading.remove.delay.longer>
|
||||||
<div class="badge badge-success badge-xs"></div>
|
<div class="badge badge-success badge-xs"></div>
|
||||||
<div class="text-xs font-medium tracking-wide text-success">{{ $text }}</div>
|
<div class="text-xs font-medium tracking-wide text-success">{{ Str::headline($status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
9
resources/views/components/status/services.blade.php
Normal file
9
resources/views/components/status/services.blade.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@if (Str::of($complexStatus)->startsWith('running'))
|
||||||
|
<x-status.running :status="$complexStatus" />
|
||||||
|
@elseif(Str::of($complexStatus)->startsWith('restarting'))
|
||||||
|
<x-status.restarting :status="$complexStatus" />
|
||||||
|
@elseif(Str::of($complexStatus)->startsWith('degraded'))
|
||||||
|
<x-status.degraded :status="$complexStatus" />
|
||||||
|
@else
|
||||||
|
<x-status.stopped :status="$complexStatus" />
|
||||||
|
@endif
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@props([
|
@props([
|
||||||
'text' => 'Stopped',
|
'status' => 'Stopped',
|
||||||
])
|
])
|
||||||
<x-loading wire:loading.delay.longer />
|
<x-loading wire:loading.delay.longer />
|
||||||
<div class="flex items-center gap-2 " wire:loading.remove.delay.longer>
|
<div class="flex items-center gap-2 " wire:loading.remove.delay.longer>
|
||||||
<div class="badge badge-error badge-xs"></div>
|
<div class="badge badge-error badge-xs"></div>
|
||||||
<div class="text-xs font-medium tracking-wide text-error">{{ $text }}</div>
|
<div class="text-xs font-medium tracking-wide text-error">{{ Str::headline($status) }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
<p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
||||||
</p>
|
</p>
|
||||||
@if ($exception->getMessage() !== '')
|
@if ($exception->getMessage() !== '')
|
||||||
<p class="mt-6 text-base leading-7 text-red-500">Error: {{ $exception->getMessage() }}
|
<p class="mt-6 text-xs leading-7 text-left text-red-500">Error: {{ $exception->getMessage() }}
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||||
|
|||||||
@@ -35,5 +35,6 @@
|
|||||||
<div class="stat-value">{{ $s3s }}</div>
|
<div class="stat-value">{{ $s3s }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains"
|
<x-forms.input placeholder="https://coolify.io" id="application.fqdn" label="Domains"
|
||||||
helper="You can specify one domain with path or more with comma.<br><span class='text-helper'>Example</span>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3" />
|
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. " />
|
||||||
@if ($wildcard_domain)
|
@if ($wildcard_domain)
|
||||||
@if ($global_wildcard_domain)
|
@if ($global_wildcard_domain)
|
||||||
<x-forms.button wire:click="generateGlobalRandomDomain">Set Global Wildcard
|
<x-forms.button wire:click="generateGlobalRandomDomain">Set Global Wildcard
|
||||||
@@ -26,13 +26,6 @@
|
|||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-2">
|
|
||||||
<x-forms.select id="application.build_pack" label="Build Pack" required>
|
|
||||||
<option value="nixpacks">Nixpacks</option>
|
|
||||||
<option value="dockerfile">Dockerfile</option>
|
|
||||||
<option disabled value="compose">Compose</option>
|
|
||||||
</x-forms.select>
|
|
||||||
</div>
|
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static)
|
||||||
<x-forms.select id="application.static_image" label="Static Image" required>
|
<x-forms.select id="application.static_image" label="Static Image" required>
|
||||||
<option value="nginx:alpine">nginx:alpine</option>
|
<option value="nginx:alpine">nginx:alpine</option>
|
||||||
@@ -47,7 +40,6 @@
|
|||||||
<x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" />
|
<x-forms.input placeholder="pnpm build" id="application.build_command" label="Build Command" />
|
||||||
<x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" />
|
<x-forms.input placeholder="pnpm start" id="application.start_command" label="Start Command" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||||
helper="Directory to use as root. Useful for monorepos. WIP" disabled />
|
helper="Directory to use as root. Useful for monorepos. WIP" disabled />
|
||||||
@@ -62,14 +54,13 @@
|
|||||||
@if ($application->dockerfile)
|
@if ($application->dockerfile)
|
||||||
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static)
|
||||||
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
|
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
|
||||||
@else
|
@else
|
||||||
<x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required
|
<x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required
|
||||||
helper="A comma separated list of ports you would like to expose for the proxy." />
|
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
|
||||||
@endif
|
@endif
|
||||||
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
||||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><span class='inline-block font-bold text-warning'>Example</span>3000:3000,3002:3002" />
|
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><span class='inline-block font-bold text-warning'>Example</span>3000:3000,3002:3002" />
|
||||||
|
|||||||
@@ -53,12 +53,12 @@
|
|||||||
@foreach ($application->previews as $preview)
|
@foreach ($application->previews as $preview)
|
||||||
<div class="flex flex-col p-4 bg-coolgray-200">
|
<div class="flex flex-col p-4 bg-coolgray-200">
|
||||||
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
|
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
|
||||||
@if (data_get($preview, 'status') === 'running')
|
@if (Str::of(data_get($preview, 'status'))->startsWith('running'))
|
||||||
<x-status.running />
|
<x-status.running :status="$status" />
|
||||||
@elseif (data_get($preview, 'status') === 'restarting')
|
@elseif(Str::of(data_get($preview, 'status'))->startsWith('restarting'))
|
||||||
<x-status.restarting />
|
<x-status.restarting :status="$status" />
|
||||||
@else
|
@else
|
||||||
<x-status.stopped />
|
<x-status.stopped :status="$status" />
|
||||||
@endif
|
@endif
|
||||||
@if (data_get($preview, 'status') !== 'exited')
|
@if (data_get($preview, 'status') !== 'exited')
|
||||||
| <a target="_blank" href="{{ data_get($preview, 'fqdn') }}">Open Preview
|
| <a target="_blank" href="{{ data_get($preview, 'fqdn') }}">Open Preview
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<div>
|
||||||
|
<h1>Create a new Service</h1>
|
||||||
|
<div class="pb-4">You can deploy complex services easily with Docker Compose.</div>
|
||||||
|
<form wire:submit.prevent="submit">
|
||||||
|
<div class="flex gap-2 pb-1">
|
||||||
|
<h2>Docker Compose</h2>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<x-forms.textarea label="Docker Compose file"
|
||||||
|
helper="
|
||||||
|
You can use these variables in your Docker Compose file and Coolify will generate default values or replace them with the values you set on the UI forms.<br>
|
||||||
|
<br>
|
||||||
|
- SERVICE_FQDN_*: FQDN - could be changable from the UI. (example: SERVICE_FQDN_GHOST)<br>
|
||||||
|
- SERVICE_URL_*: URL parsed from FQDN - could be changable from the UI. (example: SERVICE_URL_GHOST)<br>
|
||||||
|
- SERVICE_USER_*: Generated user, not encrypted in database (example: SERVICE_USER_MYSQL)<br>
|
||||||
|
- SERVICE_PASSWORD_*: Generated password, encrypted in database (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||||
|
rows="20" id="dockercompose"
|
||||||
|
placeholder='services:
|
||||||
|
ghost:
|
||||||
|
documentation: https://ghost.org/docs/config
|
||||||
|
image: ghost:5
|
||||||
|
volumes:
|
||||||
|
- ghost-content-data:/var/lib/ghost/content
|
||||||
|
environment:
|
||||||
|
- url=$SERVICE_FQDN_GHOST
|
||||||
|
- database__client=mysql
|
||||||
|
- database__connection__host=mysql
|
||||||
|
- database__connection__user=$SERVICE_USER_MYSQL
|
||||||
|
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||||
|
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||||
|
ports:
|
||||||
|
- "2368"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
mysql:
|
||||||
|
documentation: https://hub.docker.com/_/mysql
|
||||||
|
image: mysql:8.0
|
||||||
|
volumes:
|
||||||
|
- ghost-mysql-data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||||
|
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||||
|
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||||
|
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||||
|
'></x-forms.textarea>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@@ -15,34 +15,21 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
||||||
@foreach ($github_apps as $ghapp)
|
@foreach ($github_apps as $ghapp)
|
||||||
@if ($selected_github_app_id == $ghapp->id)
|
|
||||||
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
|
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
|
||||||
wire:click.prevent="loadRepositories({{ $ghapp->id }})"
|
wire:click.prevent="loadRepositories({{ $ghapp->id }})" wire:key="{{ $ghapp->id }}">
|
||||||
wire:key="{{ $ghapp->id }}">
|
<div class="flex mr-4">
|
||||||
<div class="flex gap-4 mx-6">
|
|
||||||
<div class="group-hover:text-white">
|
|
||||||
{{ $ghapp->name }}
|
|
||||||
</div>
|
|
||||||
<div>{{ $ghapp->http_url }}</div>
|
|
||||||
<span wire:target="loadRepositories({{ $ghapp->id }})" wire:loading.delay
|
|
||||||
class="loading loading-xs text-warning loading-spinner"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
|
|
||||||
wire:click.prevent="loadRepositories({{ $ghapp->id }})"
|
|
||||||
wire:key="{{ $ghapp->id }}">
|
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="group-hover:text-white">
|
<div class="group-hover:text-white">
|
||||||
{{ data_get($ghapp, 'name') }}
|
{{ data_get($ghapp, 'name') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-400 group-hover:text-white">
|
<div class="text-xs text-gray-400 group-hover:text-white">
|
||||||
{{ data_get($ghapp, 'html_url') }}</div>
|
{{ data_get($ghapp, 'html_url') }}</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
<span wire:target="loadRepositories({{ $ghapp->id }})" wire:loading.delay
|
<span wire:target="loadRepositories({{ $ghapp->id }})" wire:loading.delay
|
||||||
class="">Loading...</span>
|
class="loading loading-xs text-warning loading-spinner"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@@ -66,9 +53,14 @@
|
|||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
<x-forms.button wire:click.prevent="loadBranches"> Check
|
<x-forms.button wire:click.prevent="loadBranches"> Load Repository Details </x-forms.button>
|
||||||
repository
|
<a target="_blank" class="flex hover:no-underline"
|
||||||
|
href="{{ get_installation_path($github_app) }}">
|
||||||
|
<x-forms.button>
|
||||||
|
Change Repositories on GitHub
|
||||||
|
<x-external-link />
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div>No repositories found. Check your GitHub App configuration.</div>
|
<div>No repositories found. Check your GitHub App configuration.</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="flex flex-col gap-2 pt-10">
|
<div class="flex flex-col gap-2 pt-10">
|
||||||
@if ($current_step === 'type')
|
@if ($current_step === 'type')
|
||||||
<ul class="pb-10 steps">
|
<ul class="pb-10 steps">
|
||||||
<li class="step step-secondary">Select Source Type</li>
|
<li class="step step-secondary">Select Resource Type</li>
|
||||||
<li class="step">Select a Server</li>
|
<li class="step">Select a Server</li>
|
||||||
<li class="step">Select a Destination</li>
|
<li class="step">Select a Destination</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -52,6 +52,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (isDev())
|
||||||
|
<div class="box group" wire:click="setType('dockercompose')">
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
|
<div class="group-hover:text-white">
|
||||||
|
Based on a Docker Compose
|
||||||
|
</div>
|
||||||
|
<div class="text-xs group-hover:text-white">
|
||||||
|
You can deploy complex application easily with Docker Compose.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<h2 class="py-4">Databases</h2>
|
<h2 class="py-4">Databases</h2>
|
||||||
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
||||||
@@ -83,7 +95,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($current_step === 'servers')
|
@if ($current_step === 'servers')
|
||||||
<ul class="pb-10 steps">
|
<ul class="pb-10 steps">
|
||||||
<li class="step step-secondary">Select Source Type</li>
|
<li class="step step-secondary">Select Resource Type</li>
|
||||||
<li class="step step-secondary">Select a Server</li>
|
<li class="step step-secondary">Select a Server</li>
|
||||||
<li class="step">Select a Destination</li>
|
<li class="step">Select a Destination</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -111,7 +123,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($current_step === 'destinations')
|
@if ($current_step === 'destinations')
|
||||||
<ul class="pb-10 steps">
|
<ul class="pb-10 steps">
|
||||||
<li class="step step-secondary">Select Source Type</li>
|
<li class="step step-secondary">Select Resource Type</li>
|
||||||
<li class="step step-secondary">Select a Server</li>
|
<li class="step step-secondary">Select a Server</li>
|
||||||
<li class="step step-secondary">Select a Destination</li>
|
<li class="step step-secondary">Select a Destination</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<div>
|
||||||
|
<form wire:submit.prevent='submit'>
|
||||||
|
<div class="flex items-center gap-2 pb-4">
|
||||||
|
@if ($application->human_name)
|
||||||
|
<h2>{{ Str::headline($application->human_name) }}</h2>
|
||||||
|
@else
|
||||||
|
<h2>{{ Str::headline($application->name) }}</h2>
|
||||||
|
@endif
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
<a target="_blank" href="{{ $application->documentation() }}">Documentation <x-external-link /></a>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input label="Name" id="application.human_name" placeholder="Human readable name"></x-forms.input>
|
||||||
|
<x-forms.input label="Description" id="application.description"></x-forms.input>
|
||||||
|
<x-forms.input placeholder="https://app.coolify.io" label="Domains" required
|
||||||
|
id="application.fqdn"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@if ($application->fileStorages()->get()->count() > 0)
|
||||||
|
<h3 class="py-4">File Storages</h3>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
@foreach ($application->fileStorages()->get() as $fileStorage)
|
||||||
|
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
15
resources/views/livewire/project/service/database.blade.php
Normal file
15
resources/views/livewire/project/service/database.blade.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<form wire:submit.prevent='submit'>
|
||||||
|
<div class="flex items-center gap-2 pb-4">
|
||||||
|
@if ($database->human_name)
|
||||||
|
<h2>{{ Str::headline($database->human_name) }}</h2>
|
||||||
|
@else
|
||||||
|
<h2>{{ Str::headline($database->name) }}</h2>
|
||||||
|
@endif
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
<a target="_blank" href="{{ $database->documentation() }}">Documentation <x-external-link /></a>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input label="Name" id="database.human_name" placeholder="Name"></x-forms.input>
|
||||||
|
<x-forms.input label="Description" id="database.description"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input readonly label="File Path" id="fileStorage.fs_path"></x-forms.input>
|
||||||
|
<x-forms.input readonly label="Mount Path (in container)" id="fileStorage.mount_path"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
<x-forms.textarea label="Content" id="fileStorage.content"></x-forms.textarea>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
106
resources/views/livewire/project/service/index.blade.php
Normal file
106
resources/views/livewire/project/service/index.blade.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }">
|
||||||
|
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||||
|
<div class="flex h-full pt-6">
|
||||||
|
<div class="flex flex-col gap-4 min-w-fit">
|
||||||
|
<a :class="activeTab === 'service-stack' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'service-stack'; window.location.hash = 'service-stack'"
|
||||||
|
href="#">Service Stack</a>
|
||||||
|
<a :class="activeTab === 'compose' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'compose'; window.location.hash = 'compose'" href="#">Compose File</a>
|
||||||
|
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||||
|
href="#">Environment
|
||||||
|
Variables</a>
|
||||||
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="w-full pl-8">
|
||||||
|
<div x-cloak x-show="activeTab === 'service-stack'">
|
||||||
|
<form wire:submit.prevent='submit' class="pb-4">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<h2 class="pb-4">Configuration </h2>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input id="service.name" required label="Service Name"
|
||||||
|
placeholder="My super wordpress site" />
|
||||||
|
<x-forms.input id="service.description" label="Description" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<h2 class="pb-4"> Service Stack </h2>
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
@foreach ($service->applications as $application)
|
||||||
|
<a class="flex flex-col justify-center box"
|
||||||
|
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
|
||||||
|
@if ($application->human_name)
|
||||||
|
{{ Str::headline($application->human_name) }}
|
||||||
|
@else
|
||||||
|
{{ Str::headline($application->name) }}
|
||||||
|
@endif
|
||||||
|
@if ($application->description)
|
||||||
|
<span class="text-xs">{{ $application->description }}</span>
|
||||||
|
@endif
|
||||||
|
@if ($application->fqdn)
|
||||||
|
<span class="text-xs">{{ $application->fqdn }}</span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
@foreach ($service->databases as $database)
|
||||||
|
<a class="flex flex-col justify-center box"
|
||||||
|
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
|
||||||
|
@if ($database->human_name)
|
||||||
|
{{ Str::headline($database->human_name) }}
|
||||||
|
@else
|
||||||
|
{{ Str::headline($database->name) }}
|
||||||
|
@endif
|
||||||
|
@if ($database->description)
|
||||||
|
<span class="text-xs">{{ $database->description }}</span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'compose'">
|
||||||
|
<div x-cloak x-show="activeTab === 'compose'">
|
||||||
|
<div class="flex gap-2 pb-4">
|
||||||
|
<h2>Docker Compose</h2>
|
||||||
|
<div x-cloak x-show="raw">
|
||||||
|
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable</x-forms.button>
|
||||||
|
<x-forms.button wire:click='save'>Save</x-forms.button>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="raw === false">
|
||||||
|
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source</x-forms.button>
|
||||||
|
<x-forms.button disabled wire:click='save'>Save</x-forms.button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div x-cloak x-show="raw">
|
||||||
|
<x-forms.textarea label="Docker Compose file"
|
||||||
|
helper="
|
||||||
|
You can use these variables in your Docker Compose file and Coolify will generate default values or replace them with the values you set on the UI forms.<br>
|
||||||
|
<br>
|
||||||
|
- SERVICE_FQDN_*: FQDN - could be changable from the UI. (example: SERVICE_FQDN_GHOST)<br>
|
||||||
|
- SERVICE_URL_*: URL parsed from FQDN - could be changable from the UI. (example: SERVICE_URL_GHOST)<br>
|
||||||
|
- SERVICE_USER_*: Generated user, not encrypted in database (example: SERVICE_USER_MYSQL)<br>
|
||||||
|
- SERVICE_PASSWORD_*: Generated password, encrypted in database (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||||
|
rows="20" id="service.docker_compose_raw">
|
||||||
|
</x-forms.textarea>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="raw === false">
|
||||||
|
<x-forms.textarea label="Actual Docker Compose file that will be deployed" readonly rows="20" id="service.docker_compose">
|
||||||
|
</x-forms.textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||||
|
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||||
|
<livewire:project.shared.environment-variable.all :resource="$service" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
|
<livewire:project.shared.danger :resource="$service" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user