fix: proxy ui view

feat: build server 🌮
This commit is contained in:
Andras Bacsai 2024-01-16 15:19:14 +01:00
parent 7a0e415ecf
commit a42c8da344
25 changed files with 522 additions and 409 deletions

View File

@ -2,6 +2,7 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Events\ProxyStatusChanged;
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;
@ -13,7 +14,6 @@ class StartProxy
public function handle(Server $server, bool $async = true): string|Activity public function handle(Server $server, bool $async = true): string|Activity
{ {
try { try {
$proxyType = $server->proxyType(); $proxyType = $server->proxyType();
$commands = collect([]); $commands = collect([]);
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();

View File

@ -60,10 +60,10 @@ class Kernel extends ConsoleKernel
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
$own = Team::find(0)->servers; $own = Team::find(0)->servers;
$servers = $servers->merge($own); $servers = $servers->merge($own);
$containerServers = $servers->where('settings.is_swarm_worker', false); $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} else { } else {
$servers = Server::all()->where('ip', '!=', '1.2.3.4'); $servers = Server::all()->where('ip', '!=', '1.2.3.4');
$containerServers = $servers->where('settings.is_swarm_worker', false); $containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
} }
foreach ($containerServers as $server) { foreach ($containerServers as $server) {
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer(); $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
@ -111,7 +111,8 @@ class Kernel extends ConsoleKernel
} }
} }
private function check_scheduled_tasks($schedule) { private function check_scheduled_tasks($schedule)
{
$scheduled_tasks = ScheduledTask::all(); $scheduled_tasks = ScheduledTask::all();
if ($scheduled_tasks->isEmpty()) { if ($scheduled_tasks->isEmpty()) {
ray('no scheduled tasks'); ray('no scheduled tasks');
@ -134,7 +135,6 @@ class Kernel extends ConsoleKernel
task: $scheduled_task task: $scheduled_task
))->cron($scheduled_task->frequency)->onOneServer(); ))->cron($scheduled_task->frequency)->onOneServer();
} }
} }
protected function commands(): void protected function commands(): void

View File

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProxyStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception("Team id is null");
}
$this->teamId = $teamId;
}
public function broadcastOn(): array
{
return [
new PrivateChannel("team.{$this->teamId}"),
];
}
}

View File

@ -56,7 +56,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private GithubApp|GitlabApp|string $source = 'other'; private GithubApp|GitlabApp|string $source = 'other';
private StandaloneDocker|SwarmDocker $destination; private StandaloneDocker|SwarmDocker $destination;
// Deploy to Server
private Server $server; private Server $server;
// Build Server
private Server $build_server;
private bool $use_build_server = false;
// Save original server between phases
private Server $original_server;
private Server $mainServer; private Server $mainServer;
private ?ApplicationPreview $preview = null; private ?ApplicationPreview $preview = null;
private ?string $git_type = null; private ?string $git_type = null;
@ -196,6 +202,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Check custom port // Check custom port
['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository(); ['repository' => $this->customRepository, 'port' => $this->customPort] = $this->application->customRepository();
if (data_get($this->application, 'settings.is_build_server_enabled')) {
$teamId = data_get($this->application, 'environment.project.team.id');
$buildServers = Server::where('team_id', $teamId)->whereRelation('settings', 'is_build_server', true)->get();
if ($buildServers->count() === 0) {
$this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server.");
$this->build_server = $this->server;
$this->original_server = $this->server;
} else {
$this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed.");
$this->build_server = $buildServers->random();
$this->original_server = $this->server;
$this->use_build_server = true;
}
} else {
// Set build server & original_server to the same as deployment server
$this->build_server = $this->server;
$this->original_server = $this->server;
}
ray($this->build_server);
try { try {
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') { if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
$this->just_restart(); $this->just_restart();
@ -225,7 +250,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server)); dispatch(new ContainerStatusJob($this->server));
} }
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && !$this->application->destination->server->isSwarm()) { // Otherwise built image needs to be pushed before from the build server.
if (!$this->use_build_server) {
$this->push_to_docker_registry(); $this->push_to_docker_registry();
} }
$this->next(ApplicationDeploymentStatus::FINISHED->value); $this->next(ApplicationDeploymentStatus::FINISHED->value);
@ -234,23 +260,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->fail($e); $this->fail($e);
throw $e; throw $e;
} finally { } finally {
if (isset($this->docker_compose_base64)) { if ($this->use_build_server) {
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at); $this->server = $this->build_server;
$composeFileName = "$this->configuration_dir/docker-compose.yml"; } else {
if ($this->pull_request_id !== 0) { $this->write_deployment_configurations();
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
} }
$this->execute_remote_command( $this->execute_remote_command(
[ [
@ -269,42 +282,72 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
} }
} }
private function push_to_docker_registry() private function write_deployment_configurations()
{ {
try { if (isset($this->docker_compose_base64)) {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server); if ($this->use_build_server) {
$this->server = $this->original_server;
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command( $this->execute_remote_command(
[ [
"echo '\n----------------------------------------'", "mkdir -p $this->configuration_dir"
], ],
["echo -n 'Pushing image to docker registry ({$this->production_image_name}).'"],
[ [
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true "echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
], ],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
); );
if ($this->application->docker_registry_image_tag) { if ($this->use_build_server) {
// Tag image with latest $this->server = $this->build_server;
}
}
}
private function push_to_docker_registry($forceFail = false)
{
ray((str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()));
if (
$this->application->docker_registry_image_name &&
$this->application->build_pack !== 'dockerimage' &&
!$this->application->destination->server->isSwarm() &&
!$this->restart_only &&
!(str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())
) {
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command( $this->execute_remote_command(
['echo -n "Tagging and pushing image with latest tag."'],
[ [
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
],
[
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
], ],
); );
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
],
[
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
],
);
}
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
if ($forceFail) {
throw $e;
}
ray($e);
} }
$this->execute_remote_command([
"echo -n 'Image pushed to docker registry.'"
]);
} catch (Exception $e) {
if ($this->application->destination->server->isSwarm()) {
throw $e;
}
$this->execute_remote_command(
["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"],
);
ray($e);
} }
} }
private function generate_image_names() private function generate_image_names()
@ -340,20 +383,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function just_restart() private function just_restart()
{ {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'");
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->set_base_dir(); $this->set_base_dir();
$this->generate_image_names(); $this->generate_image_names();
$this->check_image_locally_or_remotely(); $this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
"echo 'Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.'",
]);
$this->create_workdir(); $this->create_workdir();
$this->generate_compose_file(); $this->generate_compose_file();
$this->rolling_update(); $this->rolling_update();
@ -397,16 +434,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function deploy_simple_dockerfile() private function deploy_simple_dockerfile()
{ {
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$dockerfile_base64 = base64_encode($this->application->dockerfile); $dockerfile_base64 = base64_encode($this->application->dockerfile);
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
[
"echo 'Starting deployment of {$this->application->name}.'"
],
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->execute_remote_command( $this->execute_remote_command(
[ [
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location") executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > {$this->workdir}{$this->dockerfile_location}")
], ],
); );
$this->generate_image_names(); $this->generate_image_names();
@ -422,11 +458,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->dockerImage = $this->application->docker_registry_image_name; $this->dockerImage = $this->application->docker_registry_image_name;
$this->dockerImageTag = $this->application->docker_registry_image_tag; $this->dockerImageTag = $this->application->docker_registry_image_tag;
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"); ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.");
[
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
],
);
$this->generate_image_names(); $this->generate_image_names();
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->generate_compose_file(); $this->generate_compose_file();
@ -496,24 +528,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]); ]);
} }
if (isset($this->docker_compose_base64)) { $this->write_deployment_configurations();
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
$composeFileName = "$this->configuration_dir/docker-compose.yml";
if ($this->pull_request_id !== 0) {
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
}
$this->execute_remote_command(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
}
// Start compose file // Start compose file
if ($this->docker_compose_custom_start_command) { if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command( $this->execute_remote_command(
@ -528,14 +543,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_dockerfile_buildpack() private function deploy_dockerfile_buildpack()
{ {
if ($this->use_build_server) {
$this->server = $this->build_server;
}
if (data_get($this->application, 'dockerfile_location')) { if (data_get($this->application, 'dockerfile_location')) {
$this->dockerfile_location = $this->application->dockerfile_location; $this->dockerfile_location = $this->application->dockerfile_location;
} }
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
[
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
],
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->clone_repository(); $this->clone_repository();
@ -555,11 +569,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_nixpacks_buildpack() private function deploy_nixpacks_buildpack()
{ {
$this->execute_remote_command( if ($this->use_build_server) {
[ $this->server = $this->build_server;
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" }
], $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->set_base_dir(); $this->set_base_dir();
@ -568,17 +581,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->check_image_locally_or_remotely(); $this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) { if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
$this->create_workdir(); $this->create_workdir();
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
]);
$this->generate_compose_file(); $this->generate_compose_file();
$this->rolling_update(); $this->rolling_update();
return; return;
} }
if ($this->application->isConfigurationChanged()) { if ($this->application->isConfigurationChanged()) {
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
"echo 'Configuration changed. Rebuilding image.'",
]);
} }
} }
$this->clone_repository(); $this->clone_repository();
@ -592,11 +601,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_static_buildpack() private function deploy_static_buildpack()
{ {
$this->execute_remote_command( if ($this->use_build_server) {
[ $this->server = $this->build_server;
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'" }
], $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->set_base_dir(); $this->set_base_dir();
@ -619,18 +627,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
} }
if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) { if ($nixpacks_php_fallback_path?->value === '/index.php' && $nixpacks_php_root_dir?->value === '/app/public' && $this->newVersionIsHealthy === false) {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements", 'stderr');
[
"echo 'There was a change in how Laravel is deployed. Please update your environment variables to match the new deployment method. More details here: https://coolify.io/docs/frameworks/laravel#requirements'", 'type' => 'err'
],
);
} }
} }
private function rolling_update() private function rolling_update()
{ {
if ($this->server->isSwarm()) { if ($this->server->isSwarm()) {
if ($this->build_pack !== 'dockerimage') { if ($this->build_pack !== 'dockerimage') {
$this->push_to_docker_registry(); $this->push_to_docker_registry(forceFail: true);
} }
$this->application_deployment_queue->addLogEntry("Rolling update started."); $this->application_deployment_queue->addLogEntry("Rolling update started.");
$this->execute_remote_command( $this->execute_remote_command(
@ -640,22 +644,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
); );
$this->application_deployment_queue->addLogEntry("Rolling update completed."); $this->application_deployment_queue->addLogEntry("Rolling update completed.");
} else { } else {
if ($this->use_build_server) {
$this->push_to_docker_registry(forceFail: true);
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0) { if (count($this->application->ports_mappings_array) > 0) {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("----------------------------------------");
[ $this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
"echo '\n----------------------------------------'",
],
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
);
$this->stop_running_container(force: true); $this->stop_running_container(force: true);
$this->start_by_compose_file(); $this->start_by_compose_file();
} else { } else {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("----------------------------------------");
[ $this->application_deployment_queue->addLogEntry("Rolling update started.");
"echo '\n----------------------------------------'",
],
["echo -n 'Rolling update started.'"],
);
$this->start_by_compose_file(); $this->start_by_compose_file();
$this->health_check(); $this->health_check();
$this->stop_running_container(); $this->stop_running_container();
@ -676,17 +677,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// ray('New container name: ', $this->container_name); // ray('New container name: ', $this->container_name);
if ($this->container_name) { if ($this->container_name) {
$counter = 1; $counter = 1;
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Waiting for healthcheck to pass on the new container.");
[
"echo 'Waiting for healthcheck to pass on the new container.'"
]
);
if ($this->full_healthcheck_url) { if ($this->full_healthcheck_url) {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
[
"echo 'Healthcheck URL (inside the container): {$this->full_healthcheck_url}'"
]
);
} }
while ($counter < $this->application->health_check_retries) { while ($counter < $this->application->health_check_retries) {
$this->execute_remote_command( $this->execute_remote_command(
@ -698,19 +691,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
], ],
); );
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}");
[
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
],
);
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') { if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
$this->newVersionIsHealthy = true; $this->newVersionIsHealthy = true;
$this->application->update(['status' => 'running']); $this->application->update(['status' => 'running']);
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("New container is healthy.");
[
"echo 'New container is healthy.'"
],
);
break; break;
} }
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') { if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
@ -725,11 +710,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function deploy_pull_request() private function deploy_pull_request()
{ {
if ($this->use_build_server) {
$this->server = $this->build_server;
}
$this->newVersionIsHealthy = true; $this->newVersionIsHealthy = true;
$this->generate_image_names(); $this->generate_image_names();
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.'",
]);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->clone_repository(); $this->clone_repository();
$this->set_base_dir(); $this->set_base_dir();
@ -754,10 +740,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
], ],
); );
} else { } else {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Starting preview deployment.");
["echo -n 'Starting preview deployment.'"], if ($this->use_build_server) {
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up -d"), "hidden" => true], $this->execute_remote_command(
); ["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
);
}
} }
} }
private function create_workdir() private function create_workdir()
@ -774,16 +766,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// Get user home directory // Get user home directory
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server); $this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
if ($this->use_build_server) {
if ($this->dockerConfigFileExists === 'OK') { if ($this->dockerConfigFileExists === 'NOK') {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; throw new RuntimeException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.');
}
$runCommand = "docker run -d --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else { } else {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}"; if ($this->dockerConfigFileExists === 'OK') {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
} else {
$runCommand = "docker run -d --network {$this->destination->network} --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
}
} }
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
$this->execute_remote_command( $this->execute_remote_command(
[
"echo -n 'Preparing container with helper image: $helperImage.'",
],
[ [
$runCommand, $runCommand,
"hidden" => true, "hidden" => true,
@ -801,19 +797,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$destination = StandaloneDocker::find($destination_id); $destination = StandaloneDocker::find($destination_id);
$server = $destination->server; $server = $destination->server;
if ($server->team_id !== $this->mainServer->team_id) { if ($server->team_id !== $this->mainServer->team_id) {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
[
"echo -n 'Skipping deployment to {$server->name}. Not in the same team?!'",
],
);
continue; continue;
} }
$this->server = $server; $this->server = $server;
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Deploying to {$this->server->name}.");
[
"echo -n 'Deploying to {$this->server->name}.'",
],
);
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->generate_image_names(); $this->generate_image_names();
$this->rolling_update(); $this->rolling_update();
@ -821,11 +809,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function set_base_dir() private function set_base_dir()
{ {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
[
"echo -n 'Setting base directory to {$this->workdir}.'"
],
);
} }
private function check_git_if_build_needed() private function check_git_if_build_needed()
{ {
@ -898,10 +882,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function generate_nixpacks_confs() private function generate_nixpacks_confs()
{ {
$nixpacks_command = $this->nixpacks_build_cmd(); $nixpacks_command = $this->nixpacks_build_cmd();
$this->application_deployment_queue->addLogEntry("Generating nixpacks configuration with: $nixpacks_command");
$this->execute_remote_command( $this->execute_remote_command(
[
"echo -n 'Generating nixpacks configuration with: $nixpacks_command'",
],
[executeInDocker($this->deployment_uuid, $nixpacks_command), "save" => "nixpacks_plan", "hidden" => true], [executeInDocker($this->deployment_uuid, $nixpacks_command), "save" => "nixpacks_plan", "hidden" => true],
[executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), "save" => "nixpacks_type", "hidden" => true], [executeInDocker($this->deployment_uuid, "nixpacks detect {$this->workdir}"), "save" => "nixpacks_type", "hidden" => true],
); );
@ -911,13 +893,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($this->saved_outputs->get('nixpacks_plan')) { if ($this->saved_outputs->get('nixpacks_plan')) {
$this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan'); $this->nixpacks_plan = $this->saved_outputs->get('nixpacks_plan');
if ($this->nixpacks_plan) { if ($this->nixpacks_plan) {
$this->application_deployment_queue->addLogEntry("Found application type: {$this->nixpacks_type}.");
$this->application_deployment_queue->addLogEntry("If you need further customization, please check the documentation of Nixpacks: https://nixpacks.com/docs/providers/{$this->nixpacks_type}");
$parsed = Toml::Parse($this->nixpacks_plan); $parsed = Toml::Parse($this->nixpacks_plan);
// Do any modifications here // Do any modifications here
$this->generate_env_variables(); $this->generate_env_variables();
$merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', [])));
data_set($parsed, 'variables', $merged_envs->toArray()); data_set($parsed, 'variables', $merged_envs->toArray());
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
ray($this->nixpacks_plan);
} }
} }
} }
@ -1234,9 +1217,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function pull_latest_image($image) private function pull_latest_image($image)
{ {
$this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Pulling latest image ($image) from the registry.'"],
[ [
executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker pull {$image}"), "hidden" => true
] ]
@ -1244,25 +1226,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
} }
private function build_image() private function build_image()
{ {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if ($this->application->build_pack === 'static') { if ($this->application->build_pack === 'static') {
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("Static deployment. Copying static assets to the image.");
"echo -n 'Static deployment. Copying static assets to the image.'",
]);
} else { } else {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Building docker image started.");
[ $this->application_deployment_queue->addLogEntry("To check the current progress, click on Show Debug Logs.");
"echo -n 'Building docker image started.'",
],
["echo -n 'To check the current progress, click on Show Debug Logs.'"]
);
} }
if ($this->application->settings->is_static || $this->application->build_pack === 'static') { if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
if ($this->application->static_image) { if ($this->application->static_image) {
$this->pull_latest_image($this->application->static_image); $this->pull_latest_image($this->application->static_image);
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Continuing with the building process.");
["echo -n 'Continue with the building process.'"],
);
} }
if ($this->application->build_pack === 'static') { if ($this->application->build_pack === 'static') {
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
@ -1405,9 +1380,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
} }
} }
$this->execute_remote_command([ $this->application_deployment_queue->addLogEntry("Building docker image completed.");
"echo -n 'Building docker image completed.'",
]);
} }
private function stop_running_container(bool $force = false) private function stop_running_container(bool $force = false)
@ -1463,13 +1436,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
); );
} else { } else {
if ($this->docker_compose_location) { if ($this->use_build_server) {
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true], ["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
); );
} else { } else {
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true], [executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
); );
} }
} }
@ -1535,13 +1508,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
public function failed(Throwable $exception): void public function failed(Throwable $exception): void
{ {
$this->execute_remote_command( $this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'], $this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
["echo '{$exception->getMessage()}'", 'type' => 'err'],
);
if ($this->application->build_pack !== 'dockercompose') { if ($this->application->build_pack !== 'dockercompose') {
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true] [executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
); );
} }

View File

@ -3,6 +3,7 @@
namespace App\Livewire; namespace App\Livewire;
use App\Enums\ProcessStatus; use App\Enums\ProcessStatus;
use App\Models\User;
use Livewire\Component; use Livewire\Component;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
@ -10,14 +11,16 @@ class ActivityMonitor extends Component
{ {
public ?string $header = null; public ?string $header = null;
public $activityId; public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false; public $isPollingActive = false;
protected $activity; protected $activity;
protected $listeners = ['newMonitorActivity']; protected $listeners = ['newMonitorActivity'];
public function newMonitorActivity($activityId) public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
{ {
$this->activityId = $activityId; $this->activityId = $activityId;
$this->eventToDispatch = $eventToDispatch;
$this->hydrateActivity(); $this->hydrateActivity();
@ -35,13 +38,28 @@ class ActivityMonitor extends Component
// $this->setStatus(ProcessStatus::IN_PROGRESS); // $this->setStatus(ProcessStatus::IN_PROGRESS);
$exit_code = data_get($this->activity, 'properties.exitCode'); $exit_code = data_get($this->activity, 'properties.exitCode');
if ($exit_code !== null) { if ($exit_code !== null) {
if ($exit_code === 0) { // if ($exit_code === 0) {
// $this->setStatus(ProcessStatus::FINISHED); // // $this->setStatus(ProcessStatus::FINISHED);
} else { // } else {
// $this->setStatus(ProcessStatus::ERROR); // // $this->setStatus(ProcessStatus::ERROR);
} // }
$this->isPollingActive = false; $this->isPollingActive = false;
$this->dispatch('activityFinished'); if ($exit_code === 0) {
if ($this->eventToDispatch !== null) {
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
$causer_id = data_get($this->activity, 'causer_id');
$user = User::find($causer_id);
if ($user) {
foreach($user->teams as $team) {
$teamId = $team->id;
$this->eventToDispatch::dispatch($teamId);
}
}
return;
}
$this->dispatch($this->eventToDispatch);
}
}
} }
} }

View File

@ -16,6 +16,7 @@ class Advanced extends Component
'application.settings.is_force_https_enabled' => 'boolean|required', 'application.settings.is_force_https_enabled' => 'boolean|required',
'application.settings.is_log_drain_enabled' => 'boolean|required', 'application.settings.is_log_drain_enabled' => 'boolean|required',
'application.settings.is_gpu_enabled' => 'boolean|required', 'application.settings.is_gpu_enabled' => 'boolean|required',
'application.settings.is_build_server_enabled' => 'boolean|required',
'application.settings.gpu_driver' => 'string|required', 'application.settings.gpu_driver' => 'string|required',
'application.settings.gpu_count' => 'string|required', 'application.settings.gpu_count' => 'string|required',
'application.settings.gpu_device_ids' => 'string|required', 'application.settings.gpu_device_ids' => 'string|required',

View File

@ -227,7 +227,6 @@ class General extends Component
if ($this->ports_exposes !== $this->application->ports_exposes) { if ($this->ports_exposes !== $this->application->ports_exposes) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
} }
if (data_get($this->application, 'build_pack') === 'dockerimage') { if (data_get($this->application, 'build_pack') === 'dockerimage') {
$this->validate([ $this->validate([
'application.docker_registry_image_name' => 'required', 'application.docker_registry_image_name' => 'required',

View File

@ -74,7 +74,11 @@ class Heading extends Component
return; return;
} }
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) { if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'Please set a Docker image name first.'); $this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
$this->dispatch('error', 'To use a build server you must set a Docker image name first.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/build-server">documentation</a>');
return; return;
} }
$this->setDeploymentUuid(); $this->setDeploymentUuid();

View File

@ -104,7 +104,7 @@ class Select extends Component
if ($this->includeSwarm) { if ($this->includeSwarm) {
$this->servers = $this->allServers; $this->servers = $this->allServers;
} else { } else {
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false); $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} }
} }
public function setType(string $type) public function setType(string $type)
@ -120,13 +120,13 @@ class Select extends Component
case 'mongodb': case 'mongodb':
$this->isDatabase = true; $this->isDatabase = true;
$this->includeSwarm = false; $this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false); $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
break; break;
} }
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) { if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
$this->isDatabase = true; $this->isDatabase = true;
$this->includeSwarm = false; $this->includeSwarm = false;
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false); $this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false)->where('settings.is_build_server', false);
} }
if ($type === "existing-postgresql") { if ($type === "existing-postgresql") {
$this->current_step = $type; $this->current_step = $type;

View File

@ -8,5 +8,5 @@ class Destination extends Component
{ {
public $resource; public $resource;
public $servers = []; public $servers = [];
public $additionalServers = []; public $additional_servers = [];
} }

View File

@ -26,6 +26,7 @@ class Form extends Component
'server.settings.is_reachable' => 'required', 'server.settings.is_reachable' => 'required',
'server.settings.is_swarm_manager' => 'required|boolean', 'server.settings.is_swarm_manager' => 'required|boolean',
'server.settings.is_swarm_worker' => 'required|boolean', 'server.settings.is_swarm_worker' => 'required|boolean',
'server.settings.is_build_server' => 'required|boolean',
'wildcard_domain' => 'nullable|url', 'wildcard_domain' => 'nullable|url',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@ -38,6 +39,7 @@ class Form extends Component
'server.settings.is_reachable' => 'Is reachable', 'server.settings.is_reachable' => 'Is reachable',
'server.settings.is_swarm_manager' => 'Swarm Manager', 'server.settings.is_swarm_manager' => 'Swarm Manager',
'server.settings.is_swarm_worker' => 'Swarm Worker', 'server.settings.is_swarm_worker' => 'Swarm Worker',
'server.settings.is_build_server' => 'Build Server',
]; ];
public function mount() public function mount()

View File

@ -25,6 +25,8 @@ class ByIp extends Component
public bool $is_swarm_worker = false; public bool $is_swarm_worker = false;
public $selected_swarm_cluster = null; public $selected_swarm_cluster = null;
public bool $is_build_server = false;
public $swarm_managers = []; public $swarm_managers = [];
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
@ -34,6 +36,7 @@ class ByIp extends Component
'port' => 'required|integer', 'port' => 'required|integer',
'is_swarm_manager' => 'required|boolean', 'is_swarm_manager' => 'required|boolean',
'is_swarm_worker' => 'required|boolean', 'is_swarm_worker' => 'required|boolean',
'is_build_server' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
'name' => 'Name', 'name' => 'Name',
@ -43,6 +46,7 @@ class ByIp extends Component
'port' => 'Port', 'port' => 'Port',
'is_swarm_manager' => 'Swarm Manager', 'is_swarm_manager' => 'Swarm Manager',
'is_swarm_worker' => 'Swarm Worker', 'is_swarm_worker' => 'Swarm Worker',
'is_build_server' => 'Build Server',
]; ];
public function mount() public function mount()
@ -89,8 +93,14 @@ class ByIp extends Component
$payload['swarm_cluster'] = $this->selected_swarm_cluster; $payload['swarm_cluster'] = $this->selected_swarm_cluster;
} }
$server = Server::create($payload); $server = Server::create($payload);
$server->settings->is_swarm_manager = $this->is_swarm_manager; if ($this->is_build_server) {
$server->settings->is_swarm_worker = $this->is_swarm_worker; $this->is_swarm_manager = false;
$this->is_swarm_worker = false;
} else {
$server->settings->is_swarm_manager = $this->is_swarm_manager;
$server->settings->is_swarm_worker = $this->is_swarm_worker;
}
$server->settings->is_build_server = $this->is_build_server;
$server->settings->save(); $server->settings->save();
$server->addInitialNetwork(); $server->addInitialNetwork();
return redirect()->route('server.show', $server->uuid); return redirect()->route('server.show', $server->uuid);

View File

@ -4,6 +4,7 @@ namespace App\Livewire\Server\Proxy;
use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\CheckProxy;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Events\ProxyStatusChanged;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -14,7 +15,17 @@ class Deploy extends Component
public ?string $currentRoute = null; public ?string $currentRoute = null;
public ?string $serverIp = null; public ?string $serverIp = null;
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"]; public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},ProxyStatusChanged" => 'proxyStarted',
'proxyStatusUpdated',
'traefikDashboardAvailable',
'serverRefresh' => 'proxyStatusUpdated',
"checkProxy", "startProxy"
];
}
public function mount() public function mount()
{ {
@ -29,13 +40,15 @@ class Deploy extends Component
{ {
$this->traefikDashboardAvailable = $data; $this->traefikDashboardAvailable = $data;
} }
public function proxyStarted()
{
CheckProxy::run($this->server, true);
$this->dispatch('success', 'Proxy started.');
}
public function proxyStatusUpdated() public function proxyStatusUpdated()
{ {
$this->server->refresh(); $this->server->refresh();
} }
public function ip()
{
}
public function checkProxy() public function checkProxy()
{ {
try { try {
@ -50,7 +63,7 @@ class Deploy extends Component
{ {
try { try {
$activity = StartProxy::run($this->server); $activity = StartProxy::run($this->server);
$this->dispatch('newMonitorActivity', $activity->id); $this->dispatch('newMonitorActivity', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@ -77,6 +90,5 @@ class Deploy extends Component
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@ -71,7 +71,7 @@ class Server extends BaseModel
static public function isUsable() static public function isUsable()
{ {
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false); return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false)->whereRelation('settings', 'is_build_server', false);
} }
static public function destinationsByServer(string $server_id) static public function destinationsByServer(string $server_id)
@ -328,7 +328,7 @@ class Server extends BaseModel
} }
public function isProxyShouldRun() public function isProxyShouldRun()
{ {
if ($this->proxyType() === ProxyTypes::NONE->value) { if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
return false; return false;
} }
// foreach ($this->applications() as $application) { // foreach ($this->applications() as $application) {
@ -436,7 +436,7 @@ class Server extends BaseModel
} }
$this->settings->is_usable = true; $this->settings->is_usable = true;
$this->settings->save(); $this->settings->save();
$this->validateCoolifyNetwork(isSwarm: false); $this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
return true; return true;
} }
public function validateDockerSwarm() public function validateDockerSwarm()
@ -466,8 +466,11 @@ class Server extends BaseModel
$this->settings->save(); $this->settings->save();
return true; return true;
} }
public function validateCoolifyNetwork($isSwarm = false) public function validateCoolifyNetwork($isSwarm = false, $isBuildServer = false)
{ {
if ($isBuildServer) {
return;
}
if ($isSwarm) { if ($isSwarm) {
return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false); return instant_remote_process(["docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true"], $this, false);
} else { } else {

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->boolean('is_build_server_enabled')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_settings', function (Blueprint $table) {
$table->dropColumn('is_build_server_enabled');
});
}
};

View File

@ -14,7 +14,7 @@ class ServerSettingSeeder extends Seeder
{ {
$server_2 = Server::find(0)->load(['settings']); $server_2 = Server::find(0)->load(['settings']);
$server_2->settings->wildcard_domain = 'http://127.0.0.1.sslip.io'; $server_2->settings->wildcard_domain = 'http://127.0.0.1.sslip.io';
$server_2->settings->is_build_server = true; $server_2->settings->is_build_server = false;
$server_2->settings->is_usable = true; $server_2->settings->is_usable = true;
$server_2->settings->is_reachable = true; $server_2->settings->is_reachable = true;
$server_2->settings->save(); $server_2->settings->save();

View File

@ -1,119 +0,0 @@
<div class="navbar-main">
<a class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}"
href="{{ route('project.application.configuration', $parameters) }}">
<button>Configuration</button>
</a>
@if (!$application->destination->server->isSwarm())
<a class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
href="{{ route('project.application.command', $parameters) }}">
<button>Execute Command</button>
</a>
@endif
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
</a>
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'text-white' : '' }}"
href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button>
</a>
<x-applications.links :application="$application" />
<div class="flex-1"></div>
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<x-applications.advanced :application="$application" />
@endif
@if ($application->status !== 'exited')
@if (!$application->destination->server->isSwarm())
<button title="With rolling update if possible" wire:click='deploy'
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-orange-400" 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="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Redeploy
</button>
@endif
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'
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>
Update Service
</button>
@else
<button title="Restart without rebuilding" wire:click='restart'
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
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
</button>
@endif
{{-- @if (isDev())
<button title="Restart without rebuilding" wire:click='restartNew'
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 (new)
</button>
@endif --}}
@endif
<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>
@else
<button wire:click='deploy'
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>
{{-- @if (isDev())
<button wire:click='deployNew'
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 (new)
</button>
@endif --}}
@endif
@endif
</div>

View File

@ -2,38 +2,42 @@
<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->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker()) @if (
$server->proxyType() !== 'NONE' &&
$server->isFunctional() &&
!$server->isSwarmWorker() &&
!$server->settings->is_build_server)
<livewire:server.proxy.status :server="$server" /> <livewire:server.proxy.status :server="$server" />
@endif @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">
<a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}"
href="{{ route('server.show', [ href="{{ route('server.show', [
'server_uuid' => data_get($parameters, 'server_uuid'), 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>General</button> <button>General</button>
</a> </a>
<a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
href="{{ route('server.private-key', [ href="{{ route('server.private-key', [
'server_uuid' => data_get($parameters, 'server_uuid'), 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Private Key</button> <button>Private Key</button>
</a> </a>
@if (!$server->isSwarmWorker()) @if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
href="{{ route('server.proxy', [ href="{{ route('server.proxy', [
'server_uuid' => data_get($parameters, 'server_uuid'), 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Proxy</button> <button>Proxy</button>
</a> </a>
<a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
href="{{ route('server.destinations', [ href="{{ route('server.destinations', [
'server_uuid' => data_get($parameters, 'server_uuid'), 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
<button>Destinations</button> <button>Destinations</button>
</a> </a>
<a class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}"
href="{{ route('server.log-drains', [ href="{{ route('server.log-drains', [
'server_uuid' => data_get($parameters, 'server_uuid'), 'server_uuid' => data_get($parameters, 'server_uuid'),
]) }}"> ]) }}">
@ -42,7 +46,7 @@
@endif @endif
<div class="flex-1"></div> <div class="flex-1"></div>
@if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker()) @if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker() && !$server->settings->is_build_server)
<livewire:server.proxy.deploy :server="$server" /> <livewire:server.proxy.deploy :server="$server" />
@endif @endif
</nav> </nav>

View File

@ -4,32 +4,41 @@
<h2>Advanced</h2> <h2>Advanced</h2>
</div> </div>
<div>Advanced configuration for your application.</div> <div>Advanced configuration for your application.</div>
<div class="flex flex-col w-full pt-4"> <div class="flex flex-col pt-4 ">
@if (!$application->settings->is_raw_compose_deployment_enabled) <h4>General</h4>
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave id="application.settings.is_log_drain_enabled" label="Drain Logs" />
@endif
<x-forms.checkbox <x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..." helper="Use a build server to build your application. You can configure your build server in the Server settings."
instantSave id="application.settings.is_force_https_enabled" label="Force Https" /> instantSave id="application.settings.is_build_server_enabled" label="Use a Build Server?" />
@if ($application->git_based()) @if ($application->git_based())
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave <x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
id="application.settings.is_auto_deploy_enabled" label="Auto Deploy" /> id="application.settings.is_auto_deploy_enabled" label="Auto Deploy" />
<x-forms.checkbox <x-forms.checkbox
helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments." helper="Allow to automatically deploy Preview Deployments for all opened PR's.<br><br>Closing a PR will delete Preview Deployments."
instantSave id="application.settings.is_preview_deployments_enabled" label="Preview Deployments" /> instantSave id="application.settings.is_preview_deployments_enabled" label="Preview Deployments" />
@endif
<x-forms.checkbox
helper="Your application will be available only on https if your domain starts with https://..."
instantSave id="application.settings.is_force_https_enabled" label="Force Https" />
<h4>Logs</h4>
@if (!$application->settings->is_raw_compose_deployment_enabled)
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave id="application.settings.is_log_drain_enabled" label="Drain Logs" />
@endif
<h4>Git</h4>
@if ($application->git_based())
<x-forms.checkbox instantSave id="application.settings.is_git_submodules_enabled" label="Git Submodules" <x-forms.checkbox instantSave id="application.settings.is_git_submodules_enabled" label="Git Submodules"
helper="Allow Git Submodules during build process." /> helper="Allow Git Submodules during build process." />
<x-forms.checkbox instantSave id="application.settings.is_git_lfs_enabled" label="Git LFS" <x-forms.checkbox instantSave id="application.settings.is_git_lfs_enabled" label="Git LFS"
helper="Allow Git LFS during build process." /> helper="Allow Git LFS during build process." />
@endif @endif
<h4>GPU</h4>
<form wire:submit="submit"> <form wire:submit="submit">
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.checkbox <x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>." helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="GPU Enabled Application" /> instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
@if ($application->settings->is_gpu_enabled) @if ($application->settings->is_gpu_enabled)
<x-forms.button type="submiot">Save</x-forms.button> <x-forms.button type="submiot">Save</x-forms.button>
@endif @endif

View File

@ -18,9 +18,11 @@
href="#">Environment href="#">Environment
Variables</a> Variables</a>
@endif @endif
<a :class="activeTab === 'scheduled-tasks' && 'text-white'" @if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose')
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'" href="#">Scheduled Tasks <a :class="activeTab === 'storages' && 'text-white'"
</a> @click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
</a>
@endif
@if ($application->git_based()) @if ($application->git_based())
<a :class="activeTab === 'source' && 'text-white'" <a :class="activeTab === 'source' && 'text-white'"
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a> @click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
@ -28,11 +30,12 @@
<a :class="activeTab === 'server' && 'text-white'" <a :class="activeTab === 'server' && 'text-white'"
@click.prevent="activeTab = 'server'; window.location.hash = 'server'" href="#">Server @click.prevent="activeTab = 'server'; window.location.hash = 'server'" href="#">Server
</a> </a>
@if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose')
<a :class="activeTab === 'storages' && 'text-white'" <a :class="activeTab === 'scheduled-tasks' && 'text-white'"
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages @click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
</a> href="#">Scheduled Tasks
@endif </a>
<a :class="activeTab === 'webhooks' && 'text-white'" <a :class="activeTab === 'webhooks' && 'text-white'"
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks @click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
</a> </a>

View File

@ -64,25 +64,25 @@
</div> </div>
@endif @endif
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<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. 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. " /> 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. " />
<x-forms.button wire:click="getWildcardDomain">Generate Domain <x-forms.button wire:click="getWildcardDomain">Generate Domain
</x-forms.button> </x-forms.button>
</div> </div>
@endif @endif
@if ($application->build_pack !== 'dockercompose') @if ($application->build_pack !== 'dockercompose')
<h3>Docker Registry</h3> <h3>Docker Registry</h3>
@if ($application->destination->server->isSwarm()) @if ($application->destination->server->isSwarm())
@if ($application->build_pack !== 'dockerimage') @if ($application->build_pack !== 'dockerimage')
<div>Docker Swarm requires the image to be available in a registry. More info <a <div>Docker Swarm requires the image to be available in a registry. More info <a
class="underline" href="https://coolify.io/docs/docker-registries" class="underline" href="https://coolify.io/docs/docker/registry"
target="_blank">here</a>.</div> target="_blank">here</a>.</div>
@endif @endif
@else @else
@if ($application->build_pack !== 'dockerimage') @if ($application->build_pack !== 'dockerimage')
<div>Push the built image to a docker registry. More info <a class="underline" <div>Push the built image to a docker registry. More info <a class="underline"
href="https://coolify.io/docs/docker-registries" target="_blank">here</a>.</div> href="https://coolify.io/docs/docker/registry" target="_blank">here</a>.</div>
@endif @endif
@endif @endif
<div class="flex flex-col gap-2 xl:flex-row"> <div class="flex flex-col gap-2 xl:flex-row">
@ -190,7 +190,8 @@
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button> <x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
@if ($application->settings->is_raw_compose_deployment_enabled) @if ($application->settings->is_raw_compose_deployment_enabled)
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw" <x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
label="Docker Compose Content (applicationId: {{$application->id}})" helper="You need to modify the docker compose file." /> label="Docker Compose Content (applicationId: {{ $application->id }})"
helper="You need to modify the docker compose file." />
@else @else
<x-forms.textarea rows="10" readonly id="application.docker_compose" <x-forms.textarea rows="10" readonly id="application.docker_compose"
label="Docker Compose Content" helper="You need to modify the docker compose file." /> label="Docker Compose Content" helper="You need to modify the docker compose file." />

View File

@ -1,4 +1,124 @@
<nav wire:poll.30000ms="check_status"> <nav wire:poll.30000ms="check_status">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$application" :parameters="$parameters" />
<x-applications.navbar :application="$application" :parameters="$parameters" /> <div class="navbar-main">
<a class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}"
href="{{ route('project.application.configuration', $parameters) }}">
<button>Configuration</button>
</a>
@if (!$application->destination->server->isSwarm())
<a class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
href="{{ route('project.application.command', $parameters) }}">
<button>Execute Command</button>
</a>
@endif
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
href="{{ route('project.application.logs', $parameters) }}">
<button>Logs</button>
</a>
<a class="{{ request()->routeIs('project.application.deployment.index') ? 'text-white' : '' }}"
href="{{ route('project.application.deployment.index', $parameters) }}">
<button>Deployments</button>
</a>
<x-applications.links :application="$application" />
<div class="flex-1"></div>
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<x-applications.advanced :application="$application" />
@endif
@if ($application->status !== 'exited')
@if (!$application->destination->server->isSwarm())
<button title="With rolling update if possible" wire:click='deploy'
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-orange-400" 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="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path>
<path d="M7.05 11.038v-3.988"></path>
</svg>
Redeploy
</button>
@endif
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'
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>
Update Service
</button>
@else
<button title="Restart without rebuilding" wire:click='restart'
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
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
</button>
@endif
{{-- @if (isDev())
<button title="Restart without rebuilding" wire:click='restartNew'
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 (new)
</button>
@endif --}}
@endif
<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>
@else
<button wire:click='deploy'
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>
{{-- @if (isDev())
<button wire:click='deployNew'
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 (new)
</button>
@endif --}}
@endif
@endif
</div>
</nav> </nav>

View File

@ -1,7 +1,8 @@
<div> <div>
<h2>Server</h2> <h2>Server</h2>
<div class="">The destination server where your application will be deployed to.</div> <div class="">Server related configurations.</div>
<div class="py-4 "> <h3 class="pt-4">Destination Server & Network</h3>
<div class="py-4">
<a class="box" <a class="box"
href="{{ route('server.show', ['server_uuid' => data_get($resource, 'destination.server.uuid')]) }}">On href="{{ route('server.show', ['server_uuid' => data_get($resource, 'destination.server.uuid')]) }}">On
server <span class="px-1 text-warning">{{ data_get($resource, 'destination.server.name') }}</span> server <span class="px-1 text-warning">{{ data_get($resource, 'destination.server.name') }}</span>

View File

@ -35,7 +35,7 @@
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input id="server.name" label="Name" required /> <x-forms.input id="server.name" label="Name" required />
<x-forms.input id="server.description" label="Description" /> <x-forms.input id="server.description" label="Description" />
@if (!$server->settings->is_swarm_worker) @if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server)
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" <x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" /> helper="Wildcard domain for your applications. If you set this, you will get a random generated domain for your new applications.<br><span class='font-bold text-white'>Example:</span><br>In case you set:<span class='text-helper'>https://example.com</span> your applications will get:<br> <span class='text-helper'>https://randomId.example.com</span>" />
@endif @endif
@ -51,29 +51,34 @@
</div> </div>
<div class="w-64"> <div class="w-64">
@if (!$server->isLocalhost()) @if (!$server->isLocalhost())
<x-forms.checkbox instantSave @if ($server->settings->is_build_server)
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>" <x-forms.checkbox instantSave disabled id="server.settings.is_build_server"
id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" /> label="Use it as a build server?" />
@if ($server->isSwarm())
<div class="pt-6"> Swarm support is in alpha version. </div>
@endif
@if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else @else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager" <x-forms.checkbox instantSave
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.<br><span class='text-warning'>Coolify does not install/setup Cloudflare (cloudflared) on your server.</span>"
label="Is it a Swarm Manager?" /> id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" />
@endif @if ($server->isSwarm())
@if ($server->settings->is_swarm_manager) <div class="pt-6"> Swarm support is in alpha version. </div>
<x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_worker" @endif
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." @if ($server->settings->is_swarm_worker)
label="Is it a Swarm Worker?" /> <x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_manager"
@else helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker" label="Is it a Swarm Manager?" />
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." @else
label="Is it a Swarm Worker?" /> <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif
@if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox" id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif
@endif @endif
@endif @endif
</div> </div>

View File

@ -26,8 +26,14 @@
@endforeach @endforeach
</x-forms.select> </x-forms.select>
<div class="w-96"> <div class="w-96">
<div class="pt-6"> Swarm support is in alpha version. Read the docs <a class='text-white' href='https://coolify.io/docs/docker/swarm#deploy-with-persistent-storage' target='_blank'>here</a>.</div> <x-forms.checkbox instantSave type="checkbox" id="is_build_server" label="Use it as a build server?" />
@if ($is_swarm_worker) </div>
<div class="w-96">
<h3 class="pt-6">Swarm Support</h3>
<div> Swarm support is in alpha version. Read the docs <a class='text-white'
href='https://coolify.io/docs/docker/swarm#deploy-with-persistent-storage'
target='_blank'>here</a>.</div>
@if ($is_swarm_worker || $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager" <x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_manager"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" /> label="Is it a Swarm Manager?" />
@ -36,7 +42,7 @@
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" /> label="Is it a Swarm Manager?" />
@endif @endif
@if ($is_swarm_manager) @if ($is_swarm_manager|| $is_build_server)
<x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_worker" <x-forms.checkbox disabled instantSave type="checkbox" id="is_swarm_worker"
helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>." helper="For more information, please read the documentation <a class='text-white' href='https://coolify.io/docs/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" /> label="Is it a Swarm Worker?" />