Merge branch 'next' into next
This commit is contained in:
81
.github/workflows/pr-build.yml
vendored
Normal file
81
.github/workflows/pr-build.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: PR Build (v4)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
branches-ignore: ["main", "v3"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
@@ -31,15 +31,13 @@ class StopApplication
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
}
|
||||
ray($containers);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$server
|
||||
);
|
||||
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ class StopDatabase
|
||||
if (! $server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$server
|
||||
);
|
||||
|
||||
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
|
||||
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use App\Models\ServiceDatabase;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
use App\Notifications\Container\ContainerStopped;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class GetContainersStatus
|
||||
@@ -20,13 +21,16 @@ class GetContainersStatus
|
||||
|
||||
public $applications;
|
||||
|
||||
public ?Collection $containers;
|
||||
|
||||
public ?Collection $containerReplicates;
|
||||
|
||||
public $server;
|
||||
|
||||
public function handle(Server $server)
|
||||
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
|
||||
{
|
||||
// if (isDev()) {
|
||||
// $server = Server::find(0);
|
||||
// }
|
||||
$this->containers = $containers;
|
||||
$this->containerReplicates = $containerReplicates;
|
||||
$this->server = $server;
|
||||
if (! $this->server->isFunctional()) {
|
||||
return 'Server is not ready.';
|
||||
@@ -66,322 +70,312 @@ class GetContainersStatus
|
||||
// }
|
||||
}
|
||||
|
||||
private function sentinel()
|
||||
{
|
||||
try {
|
||||
$containers = $this->server->getContainers();
|
||||
if ($containers->count() === 0) {
|
||||
return;
|
||||
}
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
// private function sentinel()
|
||||
// {
|
||||
// try {
|
||||
// $this->containers = $this->server->getContainersWithSentinel();
|
||||
// if ($this->containers->count() === 0) {
|
||||
// return;
|
||||
// }
|
||||
// $databases = $this->server->databases();
|
||||
// $services = $this->server->services()->get();
|
||||
// $previews = $this->server->previews();
|
||||
// $foundApplications = [];
|
||||
// $foundApplicationPreviews = [];
|
||||
// $foundDatabases = [];
|
||||
// $foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$labels = Arr::undot(data_get($container, 'labels'));
|
||||
$containerStatus = data_get($container, 'state');
|
||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||
if ($applicationId) {
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
if ($pullRequestId) {
|
||||
if (str($applicationId)->contains('-')) {
|
||||
$applicationId = str($applicationId)->before('-');
|
||||
}
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$preview->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
} else {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if ($application) {
|
||||
$foundApplications[] = $application->id;
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => $containerStatus]);
|
||||
}
|
||||
} else {
|
||||
//Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||
$type = data_get($labels, 'coolify.type');
|
||||
if ($uuid) {
|
||||
if ($type === 'service') {
|
||||
$database_id = data_get($labels, 'coolify.service.subId');
|
||||
if ($database_id) {
|
||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
if ($service_db) {
|
||||
$uuid = $service_db->service->uuid;
|
||||
$isPublic = data_get($service_db, 'is_public');
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'name') === "$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
StartDatabaseProxy::run($service_db);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$database = $databases->where('uuid', $uuid)->first();
|
||||
if ($database) {
|
||||
$isPublic = data_get($database, 'is_public');
|
||||
$foundDatabases[] = $database->id;
|
||||
$statusFromDb = $database->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
return data_get($value, 'name') === "$uuid-proxy";
|
||||
}
|
||||
})->first();
|
||||
if (! $foundTcpProxy) {
|
||||
StartDatabaseProxy::run($database);
|
||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data_get($container, 'name') === 'coolify-db') {
|
||||
$foundDatabases[] = 0;
|
||||
}
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
$subType = data_get($labels, 'coolify.service.subType');
|
||||
$subId = data_get($labels, 'coolify.service.subId');
|
||||
$service = $services->where('id', $serviceLabelId)->first();
|
||||
if (! $service) {
|
||||
continue;
|
||||
}
|
||||
if ($subType === 'application') {
|
||||
$service = $service->applications()->where('id', $subId)->first();
|
||||
} else {
|
||||
$service = $service->databases()->where('id', $subId)->first();
|
||||
}
|
||||
if ($service) {
|
||||
$foundServices[] = "$service->id-$service->name";
|
||||
$statusFromDb = $service->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$service->update(['status' => $containerStatus]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = collect([]);
|
||||
foreach ($services 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 (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
$fqdn = data_get($exitedService, 'fqdn');
|
||||
if ($name) {
|
||||
if ($fqdn) {
|
||||
$containerName = "$name, available at $fqdn";
|
||||
} else {
|
||||
$containerName = $name;
|
||||
}
|
||||
} else {
|
||||
if ($fqdn) {
|
||||
$containerName = $fqdn;
|
||||
} else {
|
||||
$containerName = null;
|
||||
}
|
||||
}
|
||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||
$serviceUuid = data_get($service, 'uuid');
|
||||
$environmentName = data_get($service, 'environment.name');
|
||||
// foreach ($this->containers as $container) {
|
||||
// $labels = Arr::undot(data_get($container, 'labels'));
|
||||
// $containerStatus = data_get($container, 'state');
|
||||
// $containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||
// $containerStatus = "$containerStatus ($containerHealth)";
|
||||
// $applicationId = data_get($labels, 'coolify.applicationId');
|
||||
// if ($applicationId) {
|
||||
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
// if ($pullRequestId) {
|
||||
// if (str($applicationId)->contains('-')) {
|
||||
// $applicationId = str($applicationId)->before('-');
|
||||
// }
|
||||
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
// if ($preview) {
|
||||
// $foundApplicationPreviews[] = $preview->id;
|
||||
// $statusFromDb = $preview->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $preview->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// } else {
|
||||
// //Notify user that this container should not be there.
|
||||
// }
|
||||
// } else {
|
||||
// $application = $this->applications->where('id', $applicationId)->first();
|
||||
// if ($application) {
|
||||
// $foundApplications[] = $application->id;
|
||||
// $statusFromDb = $application->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $application->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// } else {
|
||||
// //Notify user that this container should not be there.
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// $uuid = data_get($labels, 'com.docker.compose.service');
|
||||
// $type = data_get($labels, 'coolify.type');
|
||||
// if ($uuid) {
|
||||
// if ($type === 'service') {
|
||||
// $database_id = data_get($labels, 'coolify.service.subId');
|
||||
// if ($database_id) {
|
||||
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||
// if ($service_db) {
|
||||
// $uuid = $service_db->service->uuid;
|
||||
// $isPublic = data_get($service_db, 'is_public');
|
||||
// if ($isPublic) {
|
||||
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// // TODO: fix this with sentinel
|
||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
// } else {
|
||||
// return data_get($value, 'name') === "$uuid-proxy";
|
||||
// }
|
||||
// })->first();
|
||||
// if (! $foundTcpProxy) {
|
||||
// StartDatabaseProxy::run($service_db);
|
||||
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// $database = $databases->where('uuid', $uuid)->first();
|
||||
// if ($database) {
|
||||
// $isPublic = data_get($database, 'is_public');
|
||||
// $foundDatabases[] = $database->id;
|
||||
// $statusFromDb = $database->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// $database->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// if ($isPublic) {
|
||||
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// // TODO: fix this with sentinel
|
||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
// } else {
|
||||
// return data_get($value, 'name') === "$uuid-proxy";
|
||||
// }
|
||||
// })->first();
|
||||
// if (! $foundTcpProxy) {
|
||||
// StartDatabaseProxy::run($database);
|
||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// // Notify user that this container should not be there.
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (data_get($container, 'name') === 'coolify-db') {
|
||||
// $foundDatabases[] = 0;
|
||||
// }
|
||||
// }
|
||||
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
// if ($serviceLabelId) {
|
||||
// $subType = data_get($labels, 'coolify.service.subType');
|
||||
// $subId = data_get($labels, 'coolify.service.subId');
|
||||
// $service = $services->where('id', $serviceLabelId)->first();
|
||||
// if (! $service) {
|
||||
// continue;
|
||||
// }
|
||||
// if ($subType === 'application') {
|
||||
// $service = $service->applications()->where('id', $subId)->first();
|
||||
// } else {
|
||||
// $service = $service->databases()->where('id', $subId)->first();
|
||||
// }
|
||||
// if ($service) {
|
||||
// $foundServices[] = "$service->id-$service->name";
|
||||
// $statusFromDb = $service->status;
|
||||
// if ($statusFromDb !== $containerStatus) {
|
||||
// // ray('Updating status: ' . $containerStatus);
|
||||
// $service->update(['status' => $containerStatus]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// $exitedServices = collect([]);
|
||||
// foreach ($services 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 (str($exitedService->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $name = data_get($exitedService, 'name');
|
||||
// $fqdn = data_get($exitedService, 'fqdn');
|
||||
// if ($name) {
|
||||
// if ($fqdn) {
|
||||
// $containerName = "$name, available at $fqdn";
|
||||
// } else {
|
||||
// $containerName = $name;
|
||||
// }
|
||||
// } else {
|
||||
// if ($fqdn) {
|
||||
// $containerName = $fqdn;
|
||||
// } else {
|
||||
// $containerName = null;
|
||||
// }
|
||||
// }
|
||||
// $projectUuid = data_get($service, 'environment.project.uuid');
|
||||
// $serviceUuid = data_get($service, 'uuid');
|
||||
// $environmentName = data_get($service, 'environment.name');
|
||||
|
||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
$exitedService->update(['status' => 'exited']);
|
||||
}
|
||||
// if ($projectUuid && $serviceUuid && $environmentName) {
|
||||
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// $exitedService->update(['status' => 'exited']);
|
||||
// }
|
||||
|
||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $this->applications->where('id', $applicationId)->first();
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
// $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||
// foreach ($notRunningApplications as $applicationId) {
|
||||
// $application = $this->applications->where('id', $applicationId)->first();
|
||||
// if (str($application->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $application->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($application, 'name');
|
||||
$fqdn = data_get($application, 'fqdn');
|
||||
// $name = data_get($application, 'name');
|
||||
// $fqdn = data_get($application, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||
$applicationUuid = data_get($application, 'uuid');
|
||||
$environment = data_get($application, 'environment.name');
|
||||
// $projectUuid = data_get($application, 'environment.project.uuid');
|
||||
// $applicationUuid = data_get($application, 'uuid');
|
||||
// $environment = data_get($application, 'environment.name');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environment) {
|
||||
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// if ($projectUuid && $applicationUuid && $environment) {
|
||||
// $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
// foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
// $preview = $previews->where('id', $previewId)->first();
|
||||
// if (str($preview->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $preview->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($preview, 'name');
|
||||
$fqdn = data_get($preview, 'fqdn');
|
||||
// $name = data_get($preview, 'name');
|
||||
// $fqdn = data_get($preview, 'fqdn');
|
||||
|
||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||
|
||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||
$environmentName = data_get($preview, 'application.environment.name');
|
||||
$applicationUuid = data_get($preview, 'application.uuid');
|
||||
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||
// $environmentName = data_get($preview, 'application.environment.name');
|
||||
// $applicationUuid = data_get($preview, 'application.uuid');
|
||||
|
||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// if ($projectUuid && $applicationUuid && $environmentName) {
|
||||
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
// foreach ($notRunningDatabases as $database) {
|
||||
// $database = $databases->where('id', $database)->first();
|
||||
// if (str($database->status)->startsWith('exited')) {
|
||||
// continue;
|
||||
// }
|
||||
// $database->update(['status' => 'exited']);
|
||||
|
||||
$name = data_get($database, 'name');
|
||||
$fqdn = data_get($database, 'fqdn');
|
||||
// $name = data_get($database, 'name');
|
||||
// $fqdn = data_get($database, 'fqdn');
|
||||
|
||||
$containerName = $name;
|
||||
// $containerName = $name;
|
||||
|
||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||
$environmentName = data_get($database, 'environment.name');
|
||||
$databaseUuid = data_get($database, 'uuid');
|
||||
// $projectUuid = data_get($database, 'environment.project.uuid');
|
||||
// $environmentName = data_get($database, 'environment.name');
|
||||
// $databaseUuid = data_get($database, 'uuid');
|
||||
|
||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||
} else {
|
||||
$url = null;
|
||||
}
|
||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
}
|
||||
// if ($projectUuid && $databaseUuid && $environmentName) {
|
||||
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||
// } else {
|
||||
// $url = null;
|
||||
// }
|
||||
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||
// }
|
||||
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
if ($this->server->isSwarm()) {
|
||||
// TODO: fix this with sentinel
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
return data_get($value, 'name') === 'coolify-proxy';
|
||||
}
|
||||
})->first();
|
||||
if (! $foundProxyContainer) {
|
||||
try {
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
} else {
|
||||
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||
$this->server->save();
|
||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
// // Check if proxy is running
|
||||
// $this->server->proxyType();
|
||||
// $foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||
// if ($this->server->isSwarm()) {
|
||||
// // TODO: fix this with sentinel
|
||||
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
// } else {
|
||||
// return data_get($value, 'name') === 'coolify-proxy';
|
||||
// }
|
||||
// })->first();
|
||||
// if (! $foundProxyContainer) {
|
||||
// try {
|
||||
// $shouldStart = CheckProxy::run($this->server);
|
||||
// if ($shouldStart) {
|
||||
// StartProxy::run($this->server, false);
|
||||
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
// }
|
||||
// } catch (\Throwable $e) {
|
||||
// ray($e);
|
||||
// }
|
||||
// } else {
|
||||
// $this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||
// $this->server->save();
|
||||
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||
// }
|
||||
// } catch (\Exception $e) {
|
||||
// // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
// ray($e->getMessage());
|
||||
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
// return handleError($e);
|
||||
// }
|
||||
// }
|
||||
|
||||
private function old_way()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
|
||||
if (! $containers) {
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = null;
|
||||
if ($this->containers === null) {
|
||||
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
|
||||
if (is_null($this->containers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
if ($this->containerReplicates) {
|
||||
foreach ($this->containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
@@ -407,7 +401,7 @@ class GetContainersStatus
|
||||
$foundDatabases = [];
|
||||
$foundServices = [];
|
||||
|
||||
foreach ($containers as $container) {
|
||||
foreach ($this->containers as $container) {
|
||||
if ($this->server->isSwarm()) {
|
||||
$labels = data_get($container, 'Spec.Labels');
|
||||
$uuid = data_get($labels, 'coolify.name');
|
||||
@@ -461,7 +455,7 @@ class GetContainersStatus
|
||||
if ($uuid) {
|
||||
$isPublic = data_get($service_db, 'is_public');
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
@@ -486,7 +480,7 @@ class GetContainersStatus
|
||||
$database->update(['status' => $containerStatus]);
|
||||
}
|
||||
if ($isPublic) {
|
||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||
} else {
|
||||
@@ -659,7 +653,7 @@ class GetContainersStatus
|
||||
|
||||
// Check if proxy is running
|
||||
$this->server->proxyType();
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||
if ($this->server->isSwarm()) {
|
||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,6 @@ class CleanupDocker
|
||||
|
||||
public function handle(Server $server, bool $force = true)
|
||||
{
|
||||
|
||||
// cleanup docker images, containers, and builder caches
|
||||
if ($force) {
|
||||
instant_remote_process(['docker image prune -af'], $server, false);
|
||||
@@ -22,15 +21,5 @@ class CleanupDocker
|
||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||
}
|
||||
// cleanup networks
|
||||
// $networks = collectDockerNetworksByServer($server);
|
||||
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
|
||||
// $diff = $proxyNetworks->diff($networks);
|
||||
// if ($diff->count() > 0) {
|
||||
// $diff->map(function ($network) use ($server) {
|
||||
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
|
||||
// instant_remote_process(["docker network rm $network"], $server);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,7 @@ class InstallLogDrain
|
||||
}
|
||||
try {
|
||||
if ($type === 'none') {
|
||||
$command = [
|
||||
"echo 'Stopping old Fluent Bit'",
|
||||
'docker rm -f coolify-log-drain || true',
|
||||
];
|
||||
|
||||
return instant_remote_process($command, $server);
|
||||
return 'No log drain is enabled.';
|
||||
} elseif ($type === 'newrelic') {
|
||||
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||
throw new \Exception('New Relic log drain is not enabled.');
|
||||
|
||||
20
app/Actions/Server/StopLogDrain.php
Normal file
20
app/Actions/Server/StopLogDrain.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopLogDrain
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server)
|
||||
{
|
||||
try {
|
||||
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ class UpdateCoolify
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$this->server = Server::find(0);
|
||||
if (! $this->server) {
|
||||
return;
|
||||
@@ -48,7 +47,6 @@ class UpdateCoolify
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray('Running in dev mode');
|
||||
remote_process([
|
||||
'sleep 10',
|
||||
], $this->server);
|
||||
|
||||
@@ -19,18 +19,21 @@ class StopService
|
||||
ray('Stopping service: '.$service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||
instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
|
||||
instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||
} catch (\Exception $e) {
|
||||
echo $e->getMessage();
|
||||
ray($e->getMessage());
|
||||
|
||||
return $e->getMessage();
|
||||
|
||||
@@ -19,6 +19,7 @@ class CleanupDatabase extends Command
|
||||
echo "Running database cleanup in dry-run mode...\n";
|
||||
}
|
||||
if (isCloud()) {
|
||||
// Later on we can increase this to 180 days or dynamically set
|
||||
$keep_days = 60;
|
||||
} else {
|
||||
$keep_days = 60;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
@@ -12,22 +13,27 @@ use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
|
||||
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public $servers = null;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->servers = Server::all();
|
||||
$this->alive();
|
||||
get_public_ips();
|
||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
$server->settings->update(['is_metrics_enabled' => false]);
|
||||
foreach ($this->servers as $server) {
|
||||
if ($server->settings->is_metrics_enabled === true) {
|
||||
$server->settings->update(['is_metrics_enabled' => false]);
|
||||
}
|
||||
if ($server->isFunctional()) {
|
||||
StopSentinel::dispatch($server);
|
||||
}
|
||||
@@ -36,7 +42,7 @@ class Init extends Command
|
||||
|
||||
$full_cleanup = $this->option('full-cleanup');
|
||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||
|
||||
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
|
||||
$this->replace_slash_in_environment_name();
|
||||
if ($cleanup_deployments) {
|
||||
echo "Running cleanup deployments.\n";
|
||||
@@ -44,17 +50,26 @@ class Init extends Command
|
||||
|
||||
return;
|
||||
}
|
||||
if ($cleanup_proxy_networks) {
|
||||
echo "Running cleanup proxy networks.\n";
|
||||
$this->cleanup_unused_network_from_coolify_proxy();
|
||||
|
||||
return;
|
||||
}
|
||||
if ($full_cleanup) {
|
||||
// Required for falsely deleted coolify db
|
||||
$this->restore_coolify_db_backup();
|
||||
$this->update_traefik_labels();
|
||||
$this->cleanup_unused_network_from_coolify_proxy();
|
||||
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
$this->call('cleanup:queue');
|
||||
$this->call('cleanup:stucked-resources');
|
||||
if (! isCloud()) {
|
||||
try {
|
||||
$server = Server::find(0)->first();
|
||||
$server->setupDynamicProxyConfiguration();
|
||||
$localhost = $this->servers->where('id', 0)->first();
|
||||
$localhost->setupDynamicProxyConfiguration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
@@ -68,6 +83,13 @@ class Init extends Command
|
||||
$settings->update(['is_auto_update_enabled' => false]);
|
||||
}
|
||||
}
|
||||
if (isCloud()) {
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->successful()) {
|
||||
$services = $response->json();
|
||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -75,6 +97,68 @@ class Init extends Command
|
||||
$this->call('cleanup:stucked-resources');
|
||||
}
|
||||
|
||||
private function update_traefik_labels()
|
||||
{
|
||||
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||
}
|
||||
|
||||
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||
{
|
||||
if (isCloud()) {
|
||||
foreach ($this->servers as $server) {
|
||||
if (! $server->isFunctional()) {
|
||||
continue;
|
||||
}
|
||||
if ($server->id === 0) {
|
||||
continue;
|
||||
}
|
||||
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||
|
||||
return instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $server, false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanup_unused_network_from_coolify_proxy()
|
||||
{
|
||||
ray()->clearAll();
|
||||
foreach ($this->servers as $server) {
|
||||
if (! $server->isFunctional()) {
|
||||
continue;
|
||||
}
|
||||
if (! $server->isProxyShouldRun()) {
|
||||
continue;
|
||||
}
|
||||
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
|
||||
$removeNetworks = $allNetworks->diff($networks);
|
||||
$commands = collect();
|
||||
foreach ($removeNetworks as $network) {
|
||||
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||
if (empty($out)) {
|
||||
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||
} else {
|
||||
$data = collect(json_decode($out, true));
|
||||
if ($data->count() === 1) {
|
||||
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||
if ($isCoolifyProxyItself) {
|
||||
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($commands->isNotEmpty()) {
|
||||
echo "Cleaning up unused networks from coolify proxy\n";
|
||||
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function restore_coolify_db_backup()
|
||||
{
|
||||
try {
|
||||
@@ -102,8 +186,7 @@ class Init extends Command
|
||||
|
||||
private function cleanup_stucked_helper_containers()
|
||||
{
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
foreach ($this->servers as $server) {
|
||||
if ($server->isFunctional()) {
|
||||
CleanupHelperContainersJob::dispatch($server);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckLogDrainContainerJob;
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\DockerCleanupJob;
|
||||
use App\Jobs\PullCoolifyImageJob;
|
||||
@@ -12,7 +11,9 @@ use App\Jobs\PullHelperImageJob;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\PullTemplatesFromCDN;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Jobs\ServerCheckJob;
|
||||
use App\Jobs\UpdateCoolifyJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Server;
|
||||
@@ -27,25 +28,26 @@ class Kernel extends ConsoleKernel
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
$this->all_servers = Server::all();
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
||||
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
$this->schedule_updates($schedule);
|
||||
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
@@ -60,12 +62,26 @@ class Kernel extends ConsoleKernel
|
||||
|
||||
private function pull_images($schedule)
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||
$schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
|
||||
}
|
||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||
$schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
private function schedule_updates($schedule)
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
$updateCheckFrequency = $settings->update_check_frequency;
|
||||
$schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
|
||||
|
||||
if ($settings->is_auto_update_enabled) {
|
||||
$autoUpdateFrequency = $settings->auto_update_frequency;
|
||||
$schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,19 +91,11 @@ class Kernel extends ConsoleKernel
|
||||
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||
$own = Team::find(0)->servers;
|
||||
$servers = $servers->merge($own);
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||
} else {
|
||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||
}
|
||||
foreach ($containerServers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
if ($server->isLogDrainEnabled()) {
|
||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Enums;
|
||||
enum ProxyTypes: string
|
||||
{
|
||||
case NONE = 'NONE';
|
||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
||||
case TRAEFIK = 'TRAEFIK';
|
||||
case NGINX = 'NGINX';
|
||||
case CADDY = 'CADDY';
|
||||
}
|
||||
|
||||
32
app/Events/FileStorageChanged.php
Normal file
32
app/Events/FileStorageChanged.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class FileStorageChanged implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $teamId;
|
||||
|
||||
public function __construct($teamId = null)
|
||||
{
|
||||
ray($teamId);
|
||||
if (is_null($teamId)) {
|
||||
throw new \Exception('Team id is null');
|
||||
}
|
||||
$this->teamId = $teamId;
|
||||
}
|
||||
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel("team.{$this->teamId}"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -525,7 +525,7 @@ class ServersController extends Controller
|
||||
'private_key_id' => $privateKey->id,
|
||||
'team_id' => $teamId,
|
||||
'proxy' => [
|
||||
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -413,6 +413,8 @@ class ServicesController extends Controller
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
$service = $service->load(['applications', 'databases']);
|
||||
|
||||
return response()->json($this->removeSensitiveData($service));
|
||||
}
|
||||
|
||||
|
||||
@@ -871,8 +871,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$envs->push($env->key.'='.$real_value);
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
||||
$envs->push("PORT={$ports[0]}");
|
||||
if ($this->build_pack !== 'dockercompose') {
|
||||
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
||||
$envs->push("PORT={$ports[0]}");
|
||||
}
|
||||
}
|
||||
// Add HOST if not exists
|
||||
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
||||
@@ -915,15 +917,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$envs->push($env->key.'='.$real_value);
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||
$envs->push("PORT={$ports[0]}");
|
||||
if ($this->build_pack !== 'dockercompose') {
|
||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||
$envs->push("PORT={$ports[0]}");
|
||||
}
|
||||
}
|
||||
// Add HOST if not exists
|
||||
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
||||
$envs->push('HOST=0.0.0.0');
|
||||
}
|
||||
}
|
||||
|
||||
if ($envs->isEmpty()) {
|
||||
$this->env_filename = null;
|
||||
if ($this->use_build_server) {
|
||||
@@ -1022,7 +1025,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->write_deployment_configurations();
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
||||
@@ -2024,27 +2027,43 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout in seconds
|
||||
*/
|
||||
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
||||
{
|
||||
try {
|
||||
$this->execute_remote_command(
|
||||
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
||||
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
} catch (\Exception $error) {
|
||||
// report error if needed
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
private function stop_running_container(bool $force = false)
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry('Removing old containers.');
|
||||
if ($this->newVersionIsHealthy || $force) {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$containers = $containers->filter(function ($container) {
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||
$this->graceful_shutdown_container($this->container_name);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$containers = $containers->filter(function ($container) {
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||
});
|
||||
}
|
||||
$containers->each(function ($container) {
|
||||
$this->graceful_shutdown_container(data_get($container, 'Names'));
|
||||
});
|
||||
}
|
||||
$containers->each(function ($container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $containerName >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
||||
);
|
||||
});
|
||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||
@@ -2055,9 +2074,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||
]);
|
||||
$this->execute_remote_command(
|
||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
||||
);
|
||||
$this->graceful_shutdown_container($this->container_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2232,7 +2249,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
ray($code);
|
||||
if ($code !== 69420) {
|
||||
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
||||
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||
// do not remove already running container
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
||||
|
||||
42
app/Jobs/CheckForUpdatesJob.php
Normal file
42
app/Jobs/CheckForUpdatesJob.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||
$current_version = config('version');
|
||||
|
||||
if (version_compare($latest_version, $current_version, '>')) {
|
||||
// New version available
|
||||
$settings->update(['new_version_available' => true]);
|
||||
} else {
|
||||
$settings->update(['new_version_available' => false]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Consider implementing a notification to administrators
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,6 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
// $isInprogress = false;
|
||||
// $this->server->applications()->each(function ($application) use (&$isInprogress) {
|
||||
// if ($application->isDeploymentInprogress()) {
|
||||
// $isInprogress = true;
|
||||
|
||||
// return;
|
||||
// }
|
||||
// });
|
||||
// if ($isInprogress) {
|
||||
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||
// }
|
||||
if (! $this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
@@ -48,6 +37,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
|
||||
$this->usageBefore = $this->server->getDiskUsage();
|
||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||
CleanupDocker::run(server: $this->server, force: true);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||
CleanupDocker::run(server: $this->server, force: false);
|
||||
$usageAfter = $this->server->getDiskUsage();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@@ -16,16 +17,13 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
$server = Server::findOrFail(0);
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
@@ -35,7 +33,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$latest_version = get_latest_version_of_coolify();
|
||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$current_version = config('version');
|
||||
if (! $settings->is_auto_update_enabled) {
|
||||
return;
|
||||
@@ -46,10 +43,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if (version_compare($latest_version, $current_version, '<')) {
|
||||
return;
|
||||
}
|
||||
instant_remote_process([
|
||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
||||
"bash /data/coolify/source/upgrade.sh $latest_version",
|
||||
], $server);
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -22,15 +22,16 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (! isDev()) {
|
||||
ray('PullTemplatesAndVersions service-templates');
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->successful()) {
|
||||
$services = $response->json();
|
||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||
} else {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
||||
}
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
ray('PullTemplatesAndVersions service-templates');
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->successful()) {
|
||||
$services = $response->json();
|
||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||
} else {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Server\InstallLogDrain;
|
||||
@@ -15,7 +16,6 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
@@ -42,20 +42,24 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
// public function middleware(): array
|
||||
// {
|
||||
// return [(new WithoutOverlapping($this->server->uuid))];
|
||||
// }
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->uuid;
|
||||
}
|
||||
// public function uniqueId(): int
|
||||
// {
|
||||
// return $this->server->uuid;
|
||||
// }
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
try {
|
||||
$this->applications = $this->server->applications();
|
||||
$this->databases = $this->server->databases();
|
||||
$this->services = $this->server->services()->get();
|
||||
$this->previews = $this->server->previews();
|
||||
|
||||
$up = $this->serverStatus();
|
||||
if (! $up) {
|
||||
ray('Server is not reachable.');
|
||||
@@ -67,14 +71,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
return 'Server is not ready.';
|
||||
}
|
||||
$this->checkSentinel();
|
||||
$this->getContainers();
|
||||
|
||||
if (is_null($this->containers)) {
|
||||
return 'No containers found.';
|
||||
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||
if (is_null($this->containers)) {
|
||||
return 'No containers found.';
|
||||
}
|
||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||
$this->checkLogDrainContainer();
|
||||
$this->checkSentinel();
|
||||
}
|
||||
$this->checkLogDrainContainer();
|
||||
$this->containerStatus();
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
@@ -101,13 +106,13 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private function serverStatus()
|
||||
{
|
||||
$this->removeUnnevessaryCoolifyYaml();
|
||||
['uptime' => $uptime] = $this->server->validateConnection();
|
||||
if ($uptime) {
|
||||
if ($this->server->unreachable_notification_sent === true) {
|
||||
$this->server->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
} else {
|
||||
// $this->server->team?->notify(new Unreachable($this->server));
|
||||
foreach ($this->applications as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
@@ -132,18 +137,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function removeUnnevessaryCoolifyYaml()
|
||||
{
|
||||
// This will remote the coolify.yaml file from the server as it is not needed on cloud servers
|
||||
if (isCloud() && $this->server->id !== 0) {
|
||||
$file = $this->server->proxyPath().'/dynamic/coolify.yaml';
|
||||
|
||||
return instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this->server, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkLogDrainContainer()
|
||||
{
|
||||
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||
@@ -159,49 +152,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
private function getContainers()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$this->containers = format_docker_command_output_to_json($this->containers);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
$total = str($replicas)->explode('/')[1];
|
||||
if ($running === $total) {
|
||||
data_set($container, 'State.Status', 'running');
|
||||
data_set($container, 'State.Health.Status', 'healthy');
|
||||
} else {
|
||||
data_set($container, 'State.Status', 'starting');
|
||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
}
|
||||
}
|
||||
|
||||
return $container;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$this->containers = format_docker_command_output_to_json($this->containers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function containerStatus()
|
||||
{
|
||||
|
||||
$this->applications = $this->server->applications();
|
||||
$this->databases = $this->server->databases();
|
||||
$this->services = $this->server->services()->get();
|
||||
$this->previews = $this->server->previews();
|
||||
|
||||
$foundApplications = [];
|
||||
$foundApplicationPreviews = [];
|
||||
$foundDatabases = [];
|
||||
|
||||
51
app/Jobs/UpdateCoolifyJob.php
Normal file
51
app/Jobs/UpdateCoolifyJob.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 600;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
CheckForUpdatesJob::dispatchSync();
|
||||
$settings = InstanceSettings::get();
|
||||
if (! $settings->new_version_available) {
|
||||
Log::info('No new version available. Skipping update.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$server = Server::findOrFail(0);
|
||||
if (! $server) {
|
||||
Log::error('Server not found. Cannot proceed with update.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::info('Starting Coolify update process...');
|
||||
UpdateCoolify::run(false); // false means it's not a manual update
|
||||
|
||||
$settings->update(['new_version_available' => false]);
|
||||
Log::info('Coolify update completed successfully.');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('UpdateCoolifyJob failed: '.$e->getMessage());
|
||||
// Consider implementing a notification to administrators
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
|
||||
35
app/Livewire/NavbarDeleteTeam.php
Normal file
35
app/Livewire/NavbarDeleteTeam.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Component;
|
||||
|
||||
class NavbarDeleteTeam extends Component
|
||||
{
|
||||
public function delete()
|
||||
{
|
||||
$currentTeam = currentTeam();
|
||||
$currentTeam->delete();
|
||||
|
||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||
if ($user->id === auth()->user()->id) {
|
||||
return;
|
||||
}
|
||||
$user->teams()->detach($currentTeam);
|
||||
$session = DB::table('sessions')->where('user_id', $user->id)->first();
|
||||
if ($session) {
|
||||
DB::table('sessions')->where('id', $session->id)->delete();
|
||||
}
|
||||
});
|
||||
|
||||
refreshSession();
|
||||
|
||||
return redirect()->route('team.index');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.navbar-delete-team');
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ class Advanced extends Component
|
||||
|
||||
public function saveCustomName()
|
||||
{
|
||||
if (isset($this->application->settings->custom_internal_name)) {
|
||||
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
||||
} else {
|
||||
$this->application->settings->custom_internal_name = null;
|
||||
|
||||
@@ -214,7 +214,7 @@ class General extends Component
|
||||
}
|
||||
$this->dispatch('success', 'Docker compose file loaded.');
|
||||
$this->dispatch('compose_loaded');
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Throwable $e) {
|
||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||
|
||||
@@ -5,8 +5,6 @@ namespace App\Livewire\Project\Application;
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
@@ -46,11 +44,7 @@ class Heading extends Component
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
|
||||
// dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
} else {
|
||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||
}
|
||||
|
||||
if ($showNotification) {
|
||||
$this->dispatch('success', 'Success', 'Application status updated.');
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ class Configuration extends Component
|
||||
return [
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
||||
'check_status',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -76,8 +75,7 @@ class Configuration extends Component
|
||||
{
|
||||
try {
|
||||
GetContainersStatus::run($this->service->server);
|
||||
// dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('$refresh');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class FileStorage extends Component
|
||||
|
||||
public ?string $workdir = null;
|
||||
|
||||
public bool $permanently_delete = true;
|
||||
|
||||
protected $rules = [
|
||||
'fileStorage.is_directory' => 'required',
|
||||
'fileStorage.fs_path' => 'required',
|
||||
@@ -56,7 +58,7 @@ class FileStorage extends Component
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,20 +73,27 @@ class FileStorage extends Component
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->fileStorage->deleteStorageOnServer();
|
||||
$message = 'File deleted.';
|
||||
if ($this->fileStorage->is_directory) {
|
||||
$message = 'Directory deleted.';
|
||||
}
|
||||
if ($this->permanently_delete) {
|
||||
$message = 'Directory deleted from the server.';
|
||||
$this->fileStorage->deleteStorageOnServer();
|
||||
}
|
||||
$this->fileStorage->delete();
|
||||
$this->dispatch('success', 'File deleted.');
|
||||
$this->dispatch('success', $message);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,11 @@ class Navbar extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function check_status_without_notification()
|
||||
{
|
||||
$this->dispatch('check_status');
|
||||
}
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
$this->dispatch('check_status');
|
||||
|
||||
@@ -9,14 +9,36 @@ class Storage extends Component
|
||||
{
|
||||
public $resource;
|
||||
|
||||
public $fileStorage;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
|
||||
return [
|
||||
"echo-private:team.{$teamId},FileStorageChanged" => 'refreshStoragesFromEvent',
|
||||
'refreshStorages',
|
||||
'addNewVolume',
|
||||
'refresh_storages' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->refreshStorages();
|
||||
}
|
||||
|
||||
public function refreshStoragesFromEvent()
|
||||
{
|
||||
$this->refreshStorages();
|
||||
$this->dispatch('warning', 'File storage changed. Usually it means that the file / directory is already defined on the server, so Coolify set it up for you properly on the UI.');
|
||||
}
|
||||
|
||||
public function refreshStorages()
|
||||
{
|
||||
$this->fileStorage = $this->resource->fileStorages()->get();
|
||||
$this->dispatch('$refresh');
|
||||
}
|
||||
|
||||
public function addNewVolume($data)
|
||||
{
|
||||
try {
|
||||
@@ -30,7 +52,7 @@ class Storage extends Component
|
||||
$this->resource->refresh();
|
||||
$this->dispatch('success', 'Storage added successfully');
|
||||
$this->dispatch('clearAddStorage');
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ class Add extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
|
||||
if (str($this->resource->getMorphClass())->contains('Standalone')) {
|
||||
$this->file_storage_directory_source = database_configuration_dir()."/{$this->resource->uuid}";
|
||||
} else {
|
||||
$this->file_storage_directory_source = application_configuration_dir()."/{$this->resource->uuid}";
|
||||
}
|
||||
$this->uuid = $this->resource->uuid;
|
||||
$this->parameters = get_route_parameters();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
@@ -92,7 +96,7 @@ class Add extends Component
|
||||
'resource_type' => get_class($this->resource),
|
||||
],
|
||||
);
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -119,7 +123,7 @@ class Add extends Component
|
||||
'resource_type' => get_class($this->resource),
|
||||
],
|
||||
);
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ class All extends Component
|
||||
{
|
||||
public $resource;
|
||||
|
||||
protected $listeners = ['refresh_storages' => '$refresh'];
|
||||
protected $listeners = ['refreshStorages' => '$refresh'];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,6 @@ class Show extends Component
|
||||
public function delete()
|
||||
{
|
||||
$this->storage->delete();
|
||||
$this->dispatch('refresh_storages');
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\InstallLogDrain;
|
||||
use App\Actions\Server\StopLogDrain;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -132,6 +133,9 @@ class LogDrains extends Component
|
||||
'is_logdrain_axiom_enabled' => false,
|
||||
]);
|
||||
}
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
StopLogDrain::dispatch($this->server);
|
||||
}
|
||||
$this->server->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class ByIp extends Component
|
||||
'private_key_id' => $this->private_key_id,
|
||||
'proxy' => [
|
||||
// set default proxy type to traefik v2
|
||||
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -20,6 +20,10 @@ class Proxy extends Component
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
|
||||
|
||||
protected $rules = [
|
||||
'server.settings.generate_exact_labels' => 'required|boolean',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = $this->server->proxyType();
|
||||
@@ -31,13 +35,13 @@ class Proxy extends Component
|
||||
$this->dispatch('refresh')->self();
|
||||
}
|
||||
|
||||
public function change_proxy()
|
||||
public function changeProxy()
|
||||
{
|
||||
$this->server->proxy = null;
|
||||
$this->server->save();
|
||||
}
|
||||
|
||||
public function select_proxy($proxy_type)
|
||||
public function selectProxy($proxy_type)
|
||||
{
|
||||
$this->server->proxy->set('status', 'exited');
|
||||
$this->server->proxy->set('type', $proxy_type);
|
||||
@@ -49,6 +53,17 @@ class Proxy extends Component
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->server->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -21,7 +21,6 @@ class DynamicConfigurations extends Component
|
||||
return [
|
||||
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
|
||||
'loadDynamicConfigurations',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ class DynamicConfigurations extends Component
|
||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
}
|
||||
$this->contents = $contents;
|
||||
$this->dispatch('refresh');
|
||||
$this->dispatch('$refresh');
|
||||
}
|
||||
|
||||
public function mount()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -45,7 +46,7 @@ class NewDynamicConfiguration extends Component
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
$proxy_type = $this->server->proxyType();
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
if (! str($this->fileName)->endsWith('.yaml') && ! str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
}
|
||||
@@ -69,7 +70,7 @@ class NewDynamicConfiguration extends Component
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Configuration extends Component
|
||||
{
|
||||
public ModelsInstanceSettings $settings;
|
||||
|
||||
public bool $do_not_track;
|
||||
|
||||
public bool $is_auto_update_enabled;
|
||||
|
||||
public bool $is_registration_enabled;
|
||||
|
||||
public bool $is_dns_validation_enabled;
|
||||
|
||||
public bool $is_api_enabled;
|
||||
|
||||
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
||||
|
||||
protected Server $server;
|
||||
|
||||
protected $rules = [
|
||||
'settings.fqdn' => 'nullable',
|
||||
'settings.resale_license' => 'nullable',
|
||||
'settings.public_port_min' => 'required',
|
||||
'settings.public_port_max' => 'required',
|
||||
'settings.custom_dns_servers' => 'nullable',
|
||||
'settings.instance_name' => 'nullable',
|
||||
'settings.allowed_ips' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'settings.fqdn' => 'FQDN',
|
||||
'settings.resale_license' => 'Resale License',
|
||||
'settings.public_port_min' => 'Public port min',
|
||||
'settings.public_port_max' => 'Public port max',
|
||||
'settings.custom_dns_servers' => 'Custom DNS servers',
|
||||
'settings.allowed_ips' => 'Allowed IPs',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->do_not_track = $this->settings->do_not_track;
|
||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
||||
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
|
||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->settings->do_not_track = $this->do_not_track;
|
||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
||||
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
|
||||
$this->settings->is_api_enabled = $this->is_api_enabled;
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$error_show = false;
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->resetErrorBag();
|
||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->validate();
|
||||
|
||||
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
|
||||
if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
|
||||
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$error_show = true;
|
||||
}
|
||||
}
|
||||
if ($this->settings->fqdn) {
|
||||
check_domain_usage(domain: $this->settings->fqdn);
|
||||
}
|
||||
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
|
||||
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
|
||||
return str($dns)->trim()->lower();
|
||||
});
|
||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
|
||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
|
||||
|
||||
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
|
||||
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
|
||||
return str($ip)->trim();
|
||||
});
|
||||
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
|
||||
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
|
||||
|
||||
$this->settings->save();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
if (! $error_show) {
|
||||
$this->dispatch('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,170 @@
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Jobs\CheckForUpdatesJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
public StandalonePostgresql $database;
|
||||
public bool $do_not_track;
|
||||
|
||||
public $s3s;
|
||||
public bool $is_auto_update_enabled;
|
||||
|
||||
public bool $is_registration_enabled;
|
||||
|
||||
public bool $is_dns_validation_enabled;
|
||||
|
||||
public bool $is_api_enabled;
|
||||
|
||||
public string $auto_update_frequency;
|
||||
|
||||
public string $update_check_frequency;
|
||||
|
||||
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
||||
|
||||
protected Server $server;
|
||||
|
||||
protected $rules = [
|
||||
'settings.fqdn' => 'nullable',
|
||||
'settings.resale_license' => 'nullable',
|
||||
'settings.public_port_min' => 'required',
|
||||
'settings.public_port_max' => 'required',
|
||||
'settings.custom_dns_servers' => 'nullable',
|
||||
'settings.instance_name' => 'nullable',
|
||||
'settings.allowed_ips' => 'nullable',
|
||||
'settings.is_auto_update_enabled' => 'boolean',
|
||||
'auto_update_frequency' => 'string',
|
||||
'update_check_frequency' => 'string',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'settings.fqdn' => 'FQDN',
|
||||
'settings.resale_license' => 'Resale License',
|
||||
'settings.public_port_min' => 'Public port min',
|
||||
'settings.public_port_max' => 'Public port max',
|
||||
'settings.custom_dns_servers' => 'Custom DNS servers',
|
||||
'settings.allowed_ips' => 'Allowed IPs',
|
||||
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
|
||||
'auto_update_frequency' => 'Auto Update Frequency',
|
||||
'update_check_frequency' => 'Update Check Frequency',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (isInstanceAdmin()) {
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
|
||||
if ($database) {
|
||||
if ($database->status !== 'running') {
|
||||
$database->status = 'running';
|
||||
$database->save();
|
||||
}
|
||||
$this->database = $database;
|
||||
}
|
||||
$this->settings = $settings;
|
||||
$this->s3s = $s3s;
|
||||
$this->settings = InstanceSettings::get();
|
||||
$this->do_not_track = $this->settings->do_not_track;
|
||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
||||
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
|
||||
$this->is_api_enabled = $this->settings->is_api_enabled;
|
||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->settings->do_not_track = $this->do_not_track;
|
||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
||||
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
|
||||
$this->settings->is_api_enabled = $this->is_api_enabled;
|
||||
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
||||
$this->settings->update_check_frequency = $this->update_check_frequency;
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$error_show = false;
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->resetErrorBag();
|
||||
if ($this->settings->public_port_min > $this->settings->public_port_max) {
|
||||
$this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->validate();
|
||||
|
||||
if ($this->is_auto_update_enabled && ! validate_cron_expression($this->auto_update_frequency)) {
|
||||
$this->dispatch('error', 'Invalid Cron / Human expression for Auto Update Frequency.');
|
||||
if (empty($this->auto_update_frequency)) {
|
||||
$this->auto_update_frequency = '0 0 * * *';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! validate_cron_expression($this->update_check_frequency)) {
|
||||
$this->dispatch('error', 'Invalid Cron / Human expression for Update Check Frequency.');
|
||||
if (empty($this->update_check_frequency)) {
|
||||
$this->update_check_frequency = '0 * * * *';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
|
||||
if (! validate_dns_entry($this->settings->fqdn, $this->server)) {
|
||||
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$error_show = true;
|
||||
}
|
||||
}
|
||||
if ($this->settings->fqdn) {
|
||||
check_domain_usage(domain: $this->settings->fqdn);
|
||||
}
|
||||
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->replaceEnd(',', '')->trim();
|
||||
$this->settings->custom_dns_servers = str($this->settings->custom_dns_servers)->trim()->explode(',')->map(function ($dns) {
|
||||
return str($dns)->trim()->lower();
|
||||
});
|
||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
|
||||
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
|
||||
|
||||
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
|
||||
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
|
||||
return str($ip)->trim();
|
||||
});
|
||||
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
|
||||
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
|
||||
|
||||
$this->settings->do_not_track = $this->do_not_track;
|
||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
||||
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
|
||||
$this->settings->is_api_enabled = $this->is_api_enabled;
|
||||
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
||||
$this->settings->update_check_frequency = $this->update_check_frequency;
|
||||
$this->settings->save();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
if (! $error_show) {
|
||||
$this->dispatch('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkManually()
|
||||
{
|
||||
CheckForUpdatesJob::dispatchSync();
|
||||
$this->dispatch('updateAvailable');
|
||||
$settings = InstanceSettings::get();
|
||||
if ($settings->new_version_available) {
|
||||
$this->dispatch('success', 'New version available!');
|
||||
} else {
|
||||
$this->dispatch('success', 'No new version available.');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.index');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Models\InstanceSettings;
|
||||
@@ -10,7 +10,7 @@ use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Livewire\Component;
|
||||
|
||||
class Backup extends Component
|
||||
class SettingsBackup extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
@@ -41,8 +41,24 @@ class Backup extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->backup = $this->database?->scheduledBackups->first() ?? null;
|
||||
$this->executions = $this->backup?->executions ?? [];
|
||||
if (isInstanceAdmin()) {
|
||||
$settings = InstanceSettings::get();
|
||||
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
|
||||
if ($database) {
|
||||
if ($database->status !== 'running') {
|
||||
$database->status = 'running';
|
||||
$database->save();
|
||||
}
|
||||
$this->database = $database;
|
||||
}
|
||||
$this->settings = $settings;
|
||||
$this->s3s = $s3s;
|
||||
$this->backup = $this->database?->scheduledBackups?->first() ?? null;
|
||||
$this->executions = $this->backup?->executions ?? [];
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
public function add_coolify_database()
|
||||
@@ -1,12 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Notifications\TransactionalEmails\Test;
|
||||
use Livewire\Component;
|
||||
|
||||
class Email extends Component
|
||||
class SettingsEmail extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
@@ -42,7 +42,13 @@ class Email extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->emails = auth()->user()->email;
|
||||
if (isInstanceAdmin()) {
|
||||
$this->settings = InstanceSettings::get();
|
||||
$this->emails = auth()->user()->email;
|
||||
} else {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function submitFromFields()
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\OauthSetting;
|
||||
use Livewire\Component;
|
||||
|
||||
class Auth extends Component
|
||||
class SettingsOauth extends Component
|
||||
{
|
||||
public $oauth_settings_map;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
|
||||
class Upgrade extends Component
|
||||
@@ -15,14 +17,23 @@ class Upgrade extends Component
|
||||
|
||||
public string $latestVersion = '';
|
||||
|
||||
protected $listeners = ['updateAvailable' => 'checkUpdate'];
|
||||
|
||||
public function checkUpdate()
|
||||
{
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$currentVersion = config('version');
|
||||
version_compare($currentVersion, $this->latestVersion, '<') ? $this->isUpgradeAvailable = true : $this->isUpgradeAvailable = false;
|
||||
if (isDev()) {
|
||||
$this->isUpgradeAvailable = true;
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
$this->latestVersion = data_get($versions, 'coolify.v4.version');
|
||||
}
|
||||
$this->isUpgradeAvailable = $settings->new_version_available;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function upgrade()
|
||||
|
||||
@@ -104,6 +104,8 @@ class Application extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['server_status'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($application) {
|
||||
@@ -466,6 +468,28 @@ class Application extends BaseModel
|
||||
return $this->getRawOriginal('status');
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
if ($this->additional_servers->count() === 0) {
|
||||
return $this->destination->server->isFunctional();
|
||||
} else {
|
||||
$additional_servers_status = $this->additional_servers->pluck('pivot.status');
|
||||
$main_server_status = $this->destination->server->isFunctional();
|
||||
foreach ($additional_servers_status as $status) {
|
||||
$server_status = str($status)->before(':')->value();
|
||||
if ($main_server_status !== $server_status) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function status(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@@ -18,6 +18,9 @@ class InstanceSettings extends Model implements SendsEmail
|
||||
'resale_license' => 'encrypted',
|
||||
'smtp_password' => 'encrypted',
|
||||
'allowed_ip_ranges' => 'array',
|
||||
'is_auto_update_enabled' => 'boolean',
|
||||
'auto_update_frequency' => 'string',
|
||||
'update_check_frequency' => 'string',
|
||||
];
|
||||
|
||||
public function fqdn(): Attribute
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\FileStorageChanged;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class LocalFileVolume extends BaseModel
|
||||
@@ -33,16 +34,23 @@ class LocalFileVolume extends BaseModel
|
||||
$workdir = $this->resource->workdir();
|
||||
$server = $this->resource->destination->server;
|
||||
}
|
||||
$commands = collect([
|
||||
"cd $workdir",
|
||||
]);
|
||||
$commands = collect([]);
|
||||
$fs_path = data_get($this, 'fs_path');
|
||||
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
|
||||
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
|
||||
$commands->push("rm -rf $fs_path");
|
||||
}
|
||||
ray($commands);
|
||||
ray($isFile, $isDir);
|
||||
if ($isFile === 'OK') {
|
||||
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
||||
|
||||
return instant_remote_process($commands, $server);
|
||||
} elseif ($isDir === 'OK') {
|
||||
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
||||
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
if ($commands->count() > 0) {
|
||||
return instant_remote_process($commands, $server);
|
||||
}
|
||||
}
|
||||
|
||||
public function saveStorageOnServer()
|
||||
@@ -55,13 +63,10 @@ class LocalFileVolume extends BaseModel
|
||||
$workdir = $this->resource->workdir();
|
||||
$server = $this->resource->destination->server;
|
||||
}
|
||||
$commands = collect([
|
||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||
"cd $workdir",
|
||||
]);
|
||||
$is_directory = $this->is_directory;
|
||||
if ($is_directory) {
|
||||
$commands = collect([]);
|
||||
if ($this->is_directory) {
|
||||
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
|
||||
$commands->push("cd $workdir");
|
||||
}
|
||||
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
|
||||
$parent_dir = str($this->fs_path)->beforeLast('/');
|
||||
@@ -79,8 +84,11 @@ class LocalFileVolume extends BaseModel
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||
$content = instant_remote_process(["cat $path"], $server, false);
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->content = $content;
|
||||
$fileVolume->save();
|
||||
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
||||
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
||||
$fileVolume->is_directory = true;
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Notifications\Server\Revived;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -150,7 +151,7 @@ class Server extends BaseModel
|
||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||
$proxy_type = $this->proxyType();
|
||||
$redirect_url = $this->proxy->redirect_url;
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||
} elseif ($proxy_type === 'CADDY') {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||
@@ -180,7 +181,7 @@ respond 404
|
||||
|
||||
return;
|
||||
}
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$dynamic_conf = [
|
||||
'http' => [
|
||||
'routers' => [
|
||||
@@ -254,7 +255,7 @@ respond 404
|
||||
{
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$dynamic_config_path = $this->proxyPath().'/dynamic';
|
||||
if ($this->proxyType() === 'TRAEFIK_V2') {
|
||||
if ($this->proxyType() === ProxyTypes::TRAEFIK->value) {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
|
||||
instant_remote_process([
|
||||
@@ -402,7 +403,7 @@ $schema://$host {
|
||||
// TODO: should use /traefik for already exisiting configurations?
|
||||
// Should move everything except /caddy and /nginx to /traefik
|
||||
// The code needs to be modified as well, so maybe it does not worth it
|
||||
if ($proxyType === ProxyTypes::TRAEFIK_V2->value) {
|
||||
if ($proxyType === ProxyTypes::TRAEFIK->value) {
|
||||
$proxy_path = $proxy_path;
|
||||
} elseif ($proxyType === ProxyTypes::CADDY->value) {
|
||||
$proxy_path = $proxy_path.'/caddy';
|
||||
@@ -420,7 +421,7 @@ $schema://$host {
|
||||
// return $proxyType;
|
||||
// }
|
||||
// if (is_null($proxyType)) {
|
||||
// $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
// $this->proxy->type = ProxyTypes::TRAEFIK->value;
|
||||
// $this->proxy->status = ProxyStatus::EXITED->value;
|
||||
// $this->save();
|
||||
// }
|
||||
@@ -677,7 +678,49 @@ $schema://$host {
|
||||
return instant_remote_process(["docker start $id"], $this);
|
||||
}
|
||||
|
||||
public function getContainers(): Collection
|
||||
public function getContainers()
|
||||
{
|
||||
$containers = collect([]);
|
||||
$containerReplicates = collect([]);
|
||||
if ($this->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this, false);
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
$replicas = data_get($containerReplica, 'Replicas');
|
||||
$running = str($replicas)->explode('/')[0];
|
||||
$total = str($replicas)->explode('/')[1];
|
||||
if ($running === $total) {
|
||||
data_set($container, 'State.Status', 'running');
|
||||
data_set($container, 'State.Health.Status', 'healthy');
|
||||
} else {
|
||||
data_set($container, 'State.Status', 'starting');
|
||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||
}
|
||||
}
|
||||
|
||||
return $container;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containerReplicates = collect([]);
|
||||
}
|
||||
|
||||
return [
|
||||
'containers' => $containers ?? collect([]),
|
||||
'containerReplicates' => $containerReplicates ?? collect([]),
|
||||
];
|
||||
}
|
||||
|
||||
public function getContainersWithSentinel(): Collection
|
||||
{
|
||||
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
@@ -690,21 +733,6 @@ $schema://$host {
|
||||
$containers = data_get(json_decode($containers, true), 'containers', []);
|
||||
|
||||
return collect($containers);
|
||||
} else {
|
||||
if ($this->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
|
||||
} else {
|
||||
$containers = instant_remote_process(['docker container ls -q'], $this, false);
|
||||
if (! $containers) {
|
||||
return collect([]);
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
return format_docker_command_output_to_json($containers);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -39,6 +40,8 @@ class Service extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['server_status'];
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray();
|
||||
@@ -77,6 +80,15 @@ class Service extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isRunning()
|
||||
{
|
||||
return (bool) str($this->status())->contains('running');
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneClickhouse extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'clickhouse_password' => 'encrypted',
|
||||
@@ -40,6 +40,15 @@ class StandaloneClickhouse extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneDragonfly extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'dragonfly_password' => 'encrypted',
|
||||
@@ -40,6 +40,15 @@ class StandaloneDragonfly extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneKeydb extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'keydb_password' => 'encrypted',
|
||||
@@ -40,6 +40,15 @@ class StandaloneKeydb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings.$this->keydb_conf;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneMariadb extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'mariadb_password' => 'encrypted',
|
||||
@@ -40,6 +40,15 @@ class StandaloneMariadb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings.$this->mariadb_conf;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneMongodb extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -44,6 +44,15 @@ class StandaloneMongodb extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings.$this->mongo_conf;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneMysql extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'mysql_password' => 'encrypted',
|
||||
@@ -41,6 +41,15 @@ class StandaloneMysql extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings.$this->mysql_conf;
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandalonePostgresql extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected $casts = [
|
||||
'init_scripts' => 'array',
|
||||
@@ -46,6 +46,15 @@ class StandalonePostgresql extends BaseModel
|
||||
return database_configuration_dir()."/{$this->uuid}";
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
|
||||
@@ -14,7 +14,7 @@ class StandaloneRedis extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
|
||||
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -36,6 +36,15 @@ class StandaloneRedis extends BaseModel
|
||||
});
|
||||
}
|
||||
|
||||
protected function serverStatus(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
return $this->destination->server->isFunctional();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image.$this->ports_mappings.$this->redis_conf;
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class Revived extends Notification implements ShouldQueue
|
||||
{
|
||||
@@ -44,8 +45,20 @@ class Revived extends Notification implements ShouldQueue
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
$executed = RateLimiter::attempt(
|
||||
'notification-server-revived-'.$this->server->uuid,
|
||||
1,
|
||||
function () use ($channels) {
|
||||
return $channels;
|
||||
},
|
||||
7200,
|
||||
);
|
||||
|
||||
return $channels;
|
||||
if (! $executed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $executed;
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
|
||||
@@ -10,6 +10,7 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
|
||||
class Unreachable extends Notification implements ShouldQueue
|
||||
{
|
||||
@@ -35,8 +36,20 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
$executed = RateLimiter::attempt(
|
||||
'notification-server-unreachable-'.$this->server->uuid,
|
||||
1,
|
||||
function () use ($channels) {
|
||||
return $channels;
|
||||
},
|
||||
7200,
|
||||
);
|
||||
|
||||
return $channels;
|
||||
if (! $executed) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $executed;
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
|
||||
@@ -44,6 +44,8 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
Fortify::createUsersUsing(CreateNewUser::class);
|
||||
Fortify::registerView(function () {
|
||||
$isFirstUser = User::count() === 0;
|
||||
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
if (! $settings->is_registration_enabled) {
|
||||
return redirect()->route('login');
|
||||
@@ -51,7 +53,9 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
if (config('coolify.waitlist')) {
|
||||
return redirect()->route('waitlist.index');
|
||||
} else {
|
||||
return view('auth.register');
|
||||
return view('auth.register', [
|
||||
'isFirstUser' => $isFirstUser,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
@@ -539,26 +540,55 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
if ($pull_request_id === 0) {
|
||||
if ($application->fqdn) {
|
||||
$domains = str(data_get($application, 'fqdn'))->explode(',');
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
// Add Caddy labels
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
$shouldGenerateLabelsExactly = $application->destination->server->settings->generate_exact_labels;
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($application->destination->server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
break;
|
||||
case ProxyTypes::CADDY->value:
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled(),
|
||||
redirect_direction: $application->redirect
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if (data_get($preview, 'fqdn')) {
|
||||
@@ -566,24 +596,50 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
} else {
|
||||
$domains = collect([]);
|
||||
}
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
// Add Caddy labels
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
$shouldGenerateLabelsExactly = $application->destination->server->settings->generate_exact_labels;
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($application->destination->server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
break;
|
||||
case ProxyTypes::CADDY->value:
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ function collectProxyDockerNetworksByServer(Server $server)
|
||||
}
|
||||
function collectDockerNetworksByServer(Server $server)
|
||||
{
|
||||
$allNetworks = collect([]);
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
@@ -34,11 +35,13 @@ function collectDockerNetworksByServer(Server $server)
|
||||
return $docker['network'];
|
||||
});
|
||||
}
|
||||
$allNetworks = $allNetworks->merge($networks);
|
||||
// Service networks
|
||||
foreach ($server->services()->get() as $service) {
|
||||
if ($service->isRunning()) {
|
||||
$networks->push($service->networks());
|
||||
}
|
||||
$allNetworks->push($service->networks());
|
||||
}
|
||||
// Docker compose based apps
|
||||
$docker_compose_apps = $server->dockerComposeBasedApplications();
|
||||
@@ -46,6 +49,7 @@ function collectDockerNetworksByServer(Server $server)
|
||||
if ($app->isRunning()) {
|
||||
$networks->push($app->uuid);
|
||||
}
|
||||
$allNetworks->push($app->uuid);
|
||||
}
|
||||
// Docker compose based preview deployments
|
||||
$docker_compose_previews = $server->dockerComposeBasedPreviewDeployments();
|
||||
@@ -61,23 +65,30 @@ function collectDockerNetworksByServer(Server $server)
|
||||
}
|
||||
$network = "{$application->uuid}-{$pullRequestId}";
|
||||
$networks->push($network);
|
||||
$allNetworks->push($network);
|
||||
}
|
||||
$networks = collect($networks)->flatten()->unique();
|
||||
$allNetworks = $allNetworks->flatten()->unique();
|
||||
if ($server->isSwarm()) {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify-overlay']);
|
||||
$allNetworks = collect(['coolify-overlay']);
|
||||
}
|
||||
} else {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
$allNetworks = collect(['coolify']);
|
||||
}
|
||||
}
|
||||
|
||||
return $networks;
|
||||
return [
|
||||
'networks' => $networks,
|
||||
'allNetworks' => $allNetworks,
|
||||
];
|
||||
}
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
$networks = collectDockerNetworksByServer($server);
|
||||
['networks' => $networks] = collectDockerNetworksByServer($server);
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
@@ -125,7 +136,7 @@ function generate_default_proxy_configuration(Server $server)
|
||||
'external' => true,
|
||||
];
|
||||
});
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if ($proxy_type === ProxyTypes::TRAEFIK->value) {
|
||||
$labels = [
|
||||
'traefik.enable=true',
|
||||
'traefik.http.routers.traefik.entrypoints=http',
|
||||
|
||||
@@ -78,7 +78,7 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->save();
|
||||
instant_remote_process(["mkdir -p $fileLocation"], $server);
|
||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && ! $content) {
|
||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && is_null($content)) {
|
||||
// Does not exists (no dir or file), not flagged as directory, is init, has no content => create directory
|
||||
$fileVolume->content = null;
|
||||
$fileVolume->is_directory = true;
|
||||
@@ -122,14 +122,22 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$path = $fqdn->getPath();
|
||||
$fqdn = $fqdn->getScheme().'://'.$fqdn->getHost();
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $fqdn.$path;
|
||||
if ($path === '/') {
|
||||
$generatedEnv->value = $fqdn;
|
||||
} else {
|
||||
$generatedEnv->value = $fqdn.$path;
|
||||
}
|
||||
$generatedEnv->save();
|
||||
}
|
||||
if ($port) {
|
||||
$variableName = $variableName."_$port";
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $fqdn.$path;
|
||||
if ($path === '/') {
|
||||
$generatedEnv->value = $fqdn;
|
||||
} else {
|
||||
$generatedEnv->value = $fqdn.$path;
|
||||
}
|
||||
$generatedEnv->save();
|
||||
}
|
||||
}
|
||||
@@ -141,14 +149,22 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$url = $url->getHost();
|
||||
if ($generatedEnv) {
|
||||
$url = str($fqdn)->after('://');
|
||||
$generatedEnv->value = $url.$path;
|
||||
if ($path === '/') {
|
||||
$generatedEnv->value = $url;
|
||||
} else {
|
||||
$generatedEnv->value = $url.$path;
|
||||
}
|
||||
$generatedEnv->save();
|
||||
}
|
||||
if ($port) {
|
||||
$variableName = $variableName."_$port";
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $url.$path;
|
||||
if ($path === '/') {
|
||||
$generatedEnv->value = $url;
|
||||
} else {
|
||||
$generatedEnv->value = $url.$path;
|
||||
}
|
||||
$generatedEnv->save();
|
||||
}
|
||||
}
|
||||
@@ -165,10 +181,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$service_fqdn = str($port_env->key)->beforeLast('_')->after('SERVICE_FQDN_');
|
||||
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_FQDN_'.$service_fqdn)->first();
|
||||
if ($env) {
|
||||
$env->value = $host.$path;
|
||||
if ($path === '/') {
|
||||
$env->value = $host;
|
||||
} else {
|
||||
$env->value = $host.$path;
|
||||
}
|
||||
$env->save();
|
||||
}
|
||||
$port_env->value = $host.$path;
|
||||
if ($path === '/') {
|
||||
$port_env->value = $host;
|
||||
} else {
|
||||
$port_env->value = $host.$path;
|
||||
}
|
||||
$port_env->save();
|
||||
}
|
||||
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
|
||||
@@ -176,10 +200,18 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$service_url = str($port_env_url->key)->beforeLast('_')->after('SERVICE_URL_');
|
||||
$env = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'SERVICE_URL_'.$service_url)->first();
|
||||
if ($env) {
|
||||
$env->value = $url.$path;
|
||||
if ($path === '/') {
|
||||
$env->value = $url;
|
||||
} else {
|
||||
$env->value = $url.$path;
|
||||
}
|
||||
$env->save();
|
||||
}
|
||||
$port_env_url->value = $url.$path;
|
||||
if ($path === '/') {
|
||||
$port_env_url->value = $url;
|
||||
} else {
|
||||
$port_env_url->value = $url.$path;
|
||||
}
|
||||
$port_env_url->save();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Jobs\ServerFilesFromServerJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
@@ -181,11 +182,7 @@ function get_latest_version_of_coolify(): string
|
||||
$versions = json_decode($versions, true);
|
||||
|
||||
return data_get($versions, 'coolify.v4.version');
|
||||
// $response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
// $versions = $response->json();
|
||||
// return data_get($versions, 'coolify.v4.version');
|
||||
} catch (\Throwable $e) {
|
||||
//throw $e;
|
||||
ray($e->getMessage());
|
||||
|
||||
return '0.0.0';
|
||||
@@ -505,7 +502,7 @@ function get_service_templates(bool $force = false): Collection
|
||||
{
|
||||
if ($force) {
|
||||
try {
|
||||
$response = Http::retry(3, 50)->get(config('constants.services.official'));
|
||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||
if ($response->failed()) {
|
||||
return collect([]);
|
||||
}
|
||||
@@ -1308,27 +1305,58 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||
if (! $isDatabase && $fqdns->count() > 0) {
|
||||
if ($fqdns) {
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
$shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels;
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($resource->server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
break;
|
||||
case ProxyTypes::CADDY->value:
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
is_force_https_enabled: true,
|
||||
serviceLabels: $serviceLabels,
|
||||
is_gzip_enabled: $savedService->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
|
||||
service_name: $serviceName,
|
||||
image: data_get($service, 'image')
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
|
||||
@@ -2034,26 +2062,64 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
});
|
||||
}
|
||||
}
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
generate_unique_uuid: $resource->build_pack === 'dockercompose',
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
));
|
||||
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
|
||||
if ($shouldGenerateLabelsExactly) {
|
||||
switch ($server->proxyType()) {
|
||||
case ProxyTypes::TRAEFIK->value:
|
||||
$serviceLabels = $serviceLabels->merge(
|
||||
fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
generate_unique_uuid: $resource->build_pack === 'dockercompose',
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
)
|
||||
);
|
||||
break;
|
||||
case ProxyTypes::CADDY->value:
|
||||
$serviceLabels = $serviceLabels->merge(
|
||||
fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$serviceLabels = $serviceLabels->merge(
|
||||
fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
generate_unique_uuid: $resource->build_pack === 'dockercompose',
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
)
|
||||
);
|
||||
$serviceLabels = $serviceLabels->merge(
|
||||
fqdnLabelsForCaddy(
|
||||
network: $resource->destination->network,
|
||||
uuid: $resource->uuid,
|
||||
domains: $fqdns,
|
||||
serviceLabels: $serviceLabels,
|
||||
image: data_get($service, 'image'),
|
||||
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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('instance_settings', function (Blueprint $table) {
|
||||
$table->string('auto_update_frequency')->default('0 0 * * *');
|
||||
$table->string('update_check_frequency')->default('0 * * * *');
|
||||
$table->boolean('new_version_available')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('instance_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('update_check_frequency');
|
||||
$table->dropColumn('auto_update_frequency');
|
||||
$table->dropColumn('new_version_available');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('generate_exact_labels')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('generate_exact_labels');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -100,7 +100,7 @@ class ProductionSeeder extends Seeder
|
||||
'private_key_id' => 0,
|
||||
];
|
||||
$server_details['proxy'] = ServerMetadata::from([
|
||||
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
]);
|
||||
$server = Server::create($server_details);
|
||||
@@ -153,7 +153,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
'private_key_id' => 0,
|
||||
];
|
||||
$server_details['proxy'] = ServerMetadata::from([
|
||||
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||
'type' => ProxyTypes::TRAEFIK->value,
|
||||
'status' => ProxyStatus::EXITED->value,
|
||||
]);
|
||||
$server = Server::create($server_details);
|
||||
|
||||
1
public/svgs/minecraft.svg
Normal file
1
public/svgs/minecraft.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="144px" height="144px"><path fill="#8BC34A" d="M11 7L21 7 20 11 23 11 21 16 25 16 27 11 30 11 31 7 41 7 35 33 3 33z"/><path fill="#263238" d="M40.754,6H40h-9h-0.74l-1.182,5H27.5h-1.299l-0.509,1.854L20.785,6H20h-7.266H11h-0.359h-0.01L2,33l8.908,9h0.229h2.004h7.982l1.523-6.172L23.698,37h2.62l4.104,5H30.6h1.784h8.538L46,17L40.754,6z M31.333,8h8.133l-1.808,8h-3.754l1.086-4.402c0.029-0.146-0.009-0.299-0.104-0.415C34.792,11.067,34.649,11,34.5,11h-3.908L31.333,8z M27.5,11.916V12h6.39l-1.012,4h-6.581l1.087-3.66L27.5,11.916z M18.887,11h-7.715l0.938-3h7.688L18.887,11z M12.334,33.042H4.369L4.328,33H4.297l3.125-10h1.915L7.52,29.362c-0.043,0.151-0.014,0.313,0.081,0.438C7.695,29.927,7.843,30,8,30h5.217L12.334,33.042z M9.52,26h4.858l-0.871,3H8.663L9.52,26z M25.094,33l2.628-11h-2.094h-1.121l-1.352,5H17.4l1.354-5h-3.215l-0.871,3H9.806l0.675-2.362c0.043-0.151,0.014-0.313-0.081-0.438C10.305,22.073,10.157,22,10,22H7.734l3.125-10h7.725H19h2.292l-1.354,5H25h1h11.432l-3.615,16H25.094z"/><path fill="#8D6E63" d="M18.841 40L12.334 33 4.328 33 11.182 40zM38.801 40L33.816 33 25.094 33 30.764 40zM23.036 33L23 33 24.117 28.324 23.084 27 17.484 27 24.071 35.001 24.678 35.001z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -16,9 +16,16 @@ $email = getOldOrLocal('email', 'test3@example.com');
|
||||
</a>
|
||||
<div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
|
||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
||||
Create an account
|
||||
</h1>
|
||||
<div>
|
||||
<h1
|
||||
class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
||||
Create an account
|
||||
</h1>
|
||||
@if ($isFirstUser)
|
||||
<div class="text-xs dark:text-warning">This user will be the root user (full admin access).
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<form action="/register" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<x-forms.input id="name" required type="text" name="name" value="{{ $name }}"
|
||||
|
||||
@@ -351,6 +351,9 @@
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@if (!isSubscribed() && isCloud() && auth()->user()->teams()->get()->count() > 1)
|
||||
<livewire:navbar-delete-team />
|
||||
@endif
|
||||
<li>
|
||||
<x-modal-input title="How can we help?">
|
||||
<x-slot:content>
|
||||
@@ -380,44 +383,5 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{{-- <li>
|
||||
<div class="text-xs font-semibold leading-6 text-gray-400">Your teams</div>
|
||||
<ul role="list" class="mt-2 -mx-2 space-y-1">
|
||||
<li>
|
||||
<a href="#"
|
||||
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
|
||||
<span
|
||||
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">H</span>
|
||||
<span class="truncate">Heroicons</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
|
||||
<span
|
||||
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">T</span>
|
||||
<span class="truncate">Tailwind Labs</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#"
|
||||
class="flex p-2 text-sm font-semibold leading-6 text-gray-700 rounded-md hover:text-indigo-600 hover:bg-gray-50 group gap-x-3">
|
||||
<span
|
||||
class="flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">W</span>
|
||||
<span class="truncate">Workcation</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="mt-auto -mx-6">
|
||||
<a href="#"
|
||||
class="flex items-center px-6 py-3 text-sm font-semibold leading-6 text-gray-900 gap-x-4 hover:bg-gray-50">
|
||||
<img class="w-8 h-8 rounded-full bg-gray-50"
|
||||
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="">
|
||||
<span class="sr-only">Your profile</span>
|
||||
<span aria-hidden="true">Tom Cook</span>
|
||||
</a>
|
||||
</li> --}}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
@if ($server->proxyType() !== 'NONE')
|
||||
{{-- @if ($server->proxyType() === 'TRAEFIK_V2') --}}
|
||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||
<button>Dynamic Configurations</button>
|
||||
</a>
|
||||
{{-- @if ($server->proxyType() === 'TRAEFIK') --}}
|
||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||
<button>Dynamic Configurations</button>
|
||||
</a>
|
||||
{{-- @endif --}}
|
||||
<a class="{{ request()->routeIs('server.proxy.logs') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('server.proxy.logs', $parameters) }}">
|
||||
|
||||
@@ -13,6 +13,18 @@
|
||||
<button>Resale License</button>
|
||||
</a>
|
||||
@endif
|
||||
<a class="{{ request()->routeIs('settings.backup') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('settings.backup') }}">
|
||||
<button>Backup</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('settings.email') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('settings.email') }}">
|
||||
<button>Transactional Email</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('settings.oauth') ? 'dark:text-white' : '' }}"
|
||||
href="{{ route('settings.oauth') }}">
|
||||
<button>OAuth</button>
|
||||
</a>
|
||||
<div class="flex-1"></div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
5
resources/views/livewire/navbar-delete-team.blade.php
Normal file
5
resources/views/livewire/navbar-delete-team.blade.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<div>
|
||||
<x-modal-confirmation buttonFullWidth isErrorButton buttonTitle="Delete Team">
|
||||
This team be deleted. It is not reversible. <br>Please think again.
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
@@ -41,6 +41,14 @@
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
@if ($application->server_status == false)
|
||||
<span title="The underlying server(s) has problems.">
|
||||
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
|
||||
</svg>
|
||||
</span>
|
||||
@endif
|
||||
</a>
|
||||
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav wire:poll.5000ms="check_status">
|
||||
<nav wire:poll.10000ms="check_status">
|
||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" :lastDeploymentInfo="$lastDeploymentInfo" :lastDeploymentLink="$lastDeploymentLink" />
|
||||
<div class="navbar-main">
|
||||
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
||||
@@ -23,9 +23,9 @@
|
||||
<div>Please load a Compose file.</div>
|
||||
@else
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
<div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@if (!str($application->status)->startsWith('exited'))
|
||||
|
||||
@@ -72,6 +72,10 @@
|
||||
</div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.description"></div>
|
||||
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||
<template x-if="item.server_status == false">
|
||||
<div class="px-4 text-xs font-bold text-error">The underlying server has problems
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status">
|
||||
<x-slot:title>
|
||||
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
|
||||
</x-slot>
|
||||
@@ -183,7 +183,7 @@
|
||||
@endforeach
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
|
||||
<livewire:project.shared.scheduled-task.all :resource="$service" lazy />
|
||||
<livewire:project.shared.scheduled-task.all :resource="$service" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'webhooks'">
|
||||
<livewire:project.shared.webhooks :resource="$service" />
|
||||
|
||||
@@ -14,16 +14,31 @@
|
||||
<div class="flex gap-2">
|
||||
@if ($fileStorage->is_directory)
|
||||
<x-modal-confirmation action="convertToFile" buttonTitle="Convert to file">
|
||||
This will delete all files in this directory. It is not reversible. <br>Please think again.
|
||||
<div>This will delete all files in this directory. It is not reversible. <strong
|
||||
class="text-error">Please think
|
||||
again.</strong><br><br></div>
|
||||
</x-modal-confirmation>
|
||||
@else
|
||||
<x-modal-confirmation action="convertToDirectory" buttonTitle="Convert to directory">
|
||||
This will convert this to a directory. If it was a file, it will be deleted. It is not reversible.
|
||||
<br>Please think again.
|
||||
<div>This will delete the file and make a directory instead. It is not reversible.
|
||||
<strong class="text-error">Please think
|
||||
again.</strong><br><br>
|
||||
</div>
|
||||
</x-modal-confirmation>
|
||||
@endif
|
||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||
This file / directory will be deleted. It is not reversible. <br>Please think again.
|
||||
<div class="px-2">This storage will be deleted. It is not reversible. <strong
|
||||
class="text-error">Please
|
||||
think
|
||||
again.</strong><br><br></div>
|
||||
<h4>Actions</h4>
|
||||
@if ($fileStorage->is_directory)
|
||||
<x-forms.checkbox id="permanently_delete"
|
||||
label="Permanently delete directory from the server?"></x-forms.checkbox>
|
||||
@else
|
||||
<x-forms.checkbox id="permanently_delete"
|
||||
label="Permanently delete file from the server?"></x-forms.checkbox>
|
||||
@endif
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
@if (!$fileStorage->is_directory)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div>
|
||||
<div wire:poll.10000ms="check_status_without_notification">
|
||||
<livewire:project.shared.configuration-checker :resource="$service" />
|
||||
<x-slide-over @startservice.window="slideOverOpen = true" closeWithX fullScreen>
|
||||
<x-slot:title>Service Startup</x-slot:title>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose
|
||||
file or reload the compose file to reread the storage layout.</span>
|
||||
@else
|
||||
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
|
||||
@if ($resource->persistentStorages()->get()->count() === 0 && $fileStorage->count() == 0)
|
||||
<div class="pt-4">No storage found.</div>
|
||||
@endif
|
||||
@endif
|
||||
@@ -33,9 +33,9 @@
|
||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||
<livewire:project.shared.storages.all :resource="$resource" />
|
||||
@endif
|
||||
@if ($resource->fileStorages()->get()->count() > 0)
|
||||
@if ($fileStorage->count() > 0)
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
|
||||
@foreach ($fileStorage->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||
@endforeach
|
||||
@@ -48,9 +48,9 @@
|
||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||
<livewire:project.shared.storages.all :resource="$resource" />
|
||||
@endif
|
||||
@if ($resource->fileStorages()->get()->count() > 0)
|
||||
@if ($fileStorage->count() > 0)
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
@foreach ($resource->fileStorages()->get()->sort() as $fileStorage)
|
||||
@foreach ($fileStorage->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||
@endforeach
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<div class="box-description">
|
||||
Network: {{ data_get($resource, 'destination.network') }}
|
||||
</div>
|
||||
@if ($resource->server_status == false)
|
||||
<div class="text-xs font-bold text-error"> This server has connection problems. </div>
|
||||
@endif
|
||||
</div>
|
||||
@if ($resource?->additional_networks?->count() > 0)
|
||||
<div class="flex gap-2">
|
||||
@@ -76,7 +79,6 @@
|
||||
@if ($resource->getMorphClass() === 'App\Models\Application' && data_get($resource, 'build_pack') !== 'dockercompose')
|
||||
@if (count($networks) > 0)
|
||||
<h4>Choose another server</h4>
|
||||
<div class="pb-4 description">(experimental) </div>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
@foreach ($networks as $network)
|
||||
<div wire:click="addServer('{{ $network->id }}','{{ data_get($network, 'server.id') }}')"
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 lg:flex-row">
|
||||
<x-forms.input id="server.ip" label="IP Address/Domain"
|
||||
<x-forms.input type="password" id="server.ip" label="IP Address/Domain"
|
||||
helper="An IP Address (127.0.0.1) or domain (example.com)." required />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="server.user" label="User" required />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@php use App\Enums\ProxyTypes; @endphp
|
||||
<div>
|
||||
@if ($server->proxyType())
|
||||
<div x-init="$wire.loadProxyConfiguration">
|
||||
@@ -6,9 +7,9 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
@if ($server->proxy->status === 'exited' || $server->proxy->status === 'removing')
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
<x-forms.button wire:click.prevent="changeProxy">Switch Proxy</x-forms.button>
|
||||
@else
|
||||
<x-forms.button disabled wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
<x-forms.button disabled wire:click.prevent="changeProxy">Switch Proxy</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
|
||||
@@ -20,7 +21,14 @@
|
||||
</svg>Before switching proxies, please read <a class="underline dark:text-white"
|
||||
href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>.
|
||||
</div>
|
||||
@if ($server->proxyType() === 'TRAEFIK_V2')
|
||||
<h4>Advanced</h4>
|
||||
<div class="pb-4 w-96">
|
||||
<x-forms.checkbox
|
||||
helper="If set, all resources will only have docker container labels for {{ str($server->proxyType())->title() }}.<br>For applications, labels needs to be regenerated manually. <br>Resources needs to be restarted."
|
||||
id="server.settings.generate_exact_labels"
|
||||
label="Generate labels only for {{ str($server->proxyType())->title() }}" instantSave />
|
||||
</div>
|
||||
@if ($server->proxyType() === ProxyTypes::TRAEFIK->value)
|
||||
<h4>Traefik</h4>
|
||||
@elseif ($server->proxyType() === 'CADDY')
|
||||
<h4>Caddy</h4>
|
||||
@@ -52,13 +60,13 @@
|
||||
@elseif($selectedProxy === 'NONE')
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
<x-forms.button wire:click.prevent="changeProxy">Switch Proxy</x-forms.button>
|
||||
</div>
|
||||
<div class="pt-2 pb-4">Custom (None) Proxy Selected</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
<x-forms.button wire:click.prevent="changeProxy">Switch Proxy</x-forms.button>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
@@ -66,13 +74,13 @@
|
||||
<h2>Configuration</h2>
|
||||
<div class="subtitle">Select a proxy you would like to use on this server.</div>
|
||||
<div class="grid gap-4">
|
||||
<x-forms.button class="box" wire:click="select_proxy('NONE')">
|
||||
<x-forms.button class="box" wire:click="selectProxy('NONE')">
|
||||
Custom (None)
|
||||
</x-forms.button>
|
||||
<x-forms.button class="box" wire:click="select_proxy('TRAEFIK_V2')">
|
||||
<x-forms.button class="box" wire:click="selectProxy('TRAEFIK')">
|
||||
Traefik
|
||||
</x-forms.button>
|
||||
<x-forms.button class="box" wire:click="select_proxy('CADDY')">
|
||||
<x-forms.button class="box" wire:click="selectProxy('CADDY')">
|
||||
Caddy
|
||||
</x-forms.button>
|
||||
<x-forms.button disabled class="box">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@php use App\Enums\ProxyTypes; @endphp
|
||||
<div>
|
||||
@if (
|
||||
$server->proxyType() !== 'NONE' &&
|
||||
@@ -12,7 +13,10 @@
|
||||
</x-slide-over>
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<div class="flex gap-2">
|
||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
|
||||
@if (
|
||||
$currentRoute === 'server.proxy' &&
|
||||
$traefikDashboardAvailable &&
|
||||
$server->proxyType() === ProxyTypes::TRAEFIK->value)
|
||||
<button>
|
||||
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
||||
Traefik Dashboard
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Settings | Coolify
|
||||
</x-slot>
|
||||
<x-settings.navbar />
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Backup</h2>
|
||||
@@ -8,7 +12,7 @@
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
<div class="pb-4">Backup your Coolify instance settings</div>
|
||||
<div class="pb-4">Backup configuration for Coolify instance.</div>
|
||||
<div>
|
||||
@if (isset($database))
|
||||
<div class="flex flex-col gap-3 pb-4">
|
||||
@@ -1,4 +1,8 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Settings | Coolify
|
||||
</x-slot>
|
||||
<x-settings.navbar />
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Transactional Email</h2>
|
||||
</div>
|
||||
@@ -1,4 +1,8 @@
|
||||
<div>
|
||||
<x-slot:title>
|
||||
Settings | Coolify
|
||||
</x-slot>
|
||||
<x-settings.navbar />
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -7,20 +11,26 @@
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div class="pb-4 ">Custom authentication (OAuth) configurations.</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
@foreach ($oauth_settings_map as $oauth_setting)
|
||||
<div class="p-4 border dark:border-coolgray-300">
|
||||
<h3>{{ucfirst($oauth_setting->provider)}} Oauth</h3>
|
||||
<h3>{{ ucfirst($oauth_setting->provider) }} Oauth</h3>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave id="oauth_settings_map.{{$oauth_setting->provider}}.enabled" label="Enabled" />
|
||||
<x-forms.checkbox instantSave id="oauth_settings_map.{{ $oauth_setting->provider }}.enabled"
|
||||
label="Enabled" />
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-2 xl:flex-row">
|
||||
<x-forms.input id="oauth_settings_map.{{$oauth_setting->provider}}.client_id" label="Client ID" />
|
||||
<x-forms.input id="oauth_settings_map.{{$oauth_setting->provider}}.client_secret" type="password" label="Client Secret" />
|
||||
<x-forms.input id="oauth_settings_map.{{$oauth_setting->provider}}.redirect_uri" label="Redirect URI" />
|
||||
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.client_id"
|
||||
label="Client ID" />
|
||||
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.client_secret"
|
||||
type="password" label="Client Secret" />
|
||||
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.redirect_uri"
|
||||
label="Redirect URI" />
|
||||
@if ($oauth_setting->provider == 'azure')
|
||||
<x-forms.input id="oauth_settings_map.{{$oauth_setting->provider}}.tenant" label="Tenant" />
|
||||
<x-forms.input id="oauth_settings_map.{{ $oauth_setting->provider }}.tenant"
|
||||
label="Tenant" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,49 +0,0 @@
|
||||
<div>
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div>General configuration for your Coolify instance.</div>
|
||||
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
|
||||
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" />
|
||||
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
|
||||
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
|
||||
placeholder="1.1.1.1,8.8.8.8" />
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- <div class="flex gap-2 ">
|
||||
<x-forms.input type="number" id="settings.public_port_min" label="Public Port Min" />
|
||||
<x-forms.input type="number" id="settings.public_port_max" label="Public Port Max" />
|
||||
</div> --}}
|
||||
</div>
|
||||
<h2 class="pt-6">API</h2>
|
||||
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
||||
</div>
|
||||
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
|
||||
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
|
||||
placeholder="1.1.1.1,8.8.8.8" />
|
||||
</form>
|
||||
|
||||
<h2 class="pt-6">Advanced</h2>
|
||||
<div class="text-right md:w-96">
|
||||
@if (!is_null(env('AUTOUPDATE', null)))
|
||||
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled
|
||||
id="is_auto_update_enabled" label="Auto Update Coolify" />
|
||||
@else
|
||||
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
|
||||
@endif
|
||||
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,32 +3,72 @@
|
||||
Settings | Coolify
|
||||
</x-slot>
|
||||
<x-settings.navbar />
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-1 sm:flex-row">
|
||||
<div class="flex gap-6 overflow-x-scroll sm:gap-2 sm:overflow-x-hidden scrollbar sm:flex-col whitespace-nowrap">
|
||||
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||
<a class="menu-item" :class="activeTab === 'backup' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'backup'; window.location.hash = 'backup'" href="#">Instance Backup</a>
|
||||
<a class="menu-item" :class="activeTab === 'smtp' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional
|
||||
Email</a>
|
||||
<a class="menu-item" :class="activeTab === 'auth' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication
|
||||
(OAuth)</a>
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Configuration</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||
<livewire:settings.configuration :settings="$settings" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'backup'" class="h-full">
|
||||
<livewire:settings.backup :settings="$settings" :database="$database" :s3s="$s3s" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'smtp'" class="h-full">
|
||||
<livewire:settings.email :settings="$settings" />
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'auth'" class="h-full">
|
||||
<livewire:settings.auth />
|
||||
<div>General configuration for your Coolify instance.</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-end gap-2">
|
||||
<h4 class="pt-6">Instance Settings</h4>
|
||||
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
|
||||
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" />
|
||||
<h4 class="w-full pt-6">DNS Validation</h4>
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Enabled" />
|
||||
</div>
|
||||
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
|
||||
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
|
||||
placeholder="1.1.1.1,8.8.8.8" />
|
||||
</div>
|
||||
|
||||
{{-- <div class="flex gap-2 ">
|
||||
<x-forms.input type="number" id="settings.public_port_min" label="Public Port Min" />
|
||||
<x-forms.input type="number" id="settings.public_port_max" label="Public Port Max" />
|
||||
</div> --}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="pt-6">API</h4>
|
||||
<div class="md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
||||
</div>
|
||||
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
|
||||
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
|
||||
placeholder="1.1.1.1,8.8.8.8" />
|
||||
|
||||
<h4 class="pt-6">Advanced</h4>
|
||||
<div class="text-right md:w-96">
|
||||
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||
</div>
|
||||
<h5 class="pt-4 font-bold text-white">Update</h5>
|
||||
<div class="text-right md:w-96">
|
||||
@if (!is_null(env('AUTOUPDATE', null)))
|
||||
<div class="text-right md:w-96">
|
||||
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there."
|
||||
disabled id="is_auto_update_enabled" label="Enabled" />
|
||||
</div>
|
||||
@else
|
||||
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Enabled" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input required id="update_check_frequency" label="Update Check Frequency"
|
||||
placeholder="0 * * * *"
|
||||
helper="Cron expression for update check frequency (check for new Coolify versions and pull new Service Templates from CDN). Default is every hour." />
|
||||
<x-forms.button wire:click='checkManually'>Check Manually</x-forms.button>
|
||||
</div>
|
||||
|
||||
@if (is_null(env('AUTOUPDATE', null)) && $is_auto_update_enabled)
|
||||
<x-forms.input required id="auto_update_frequency" label="Auto Update Frequency" placeholder="0 0 * * *"
|
||||
helper="Cron expression for auto update frequency (automatically update coolify). Default is every day at 00:00" />
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,9 @@ use App\Livewire\Server\Resources as ResourcesShow;
|
||||
use App\Livewire\Server\Show as ServerShow;
|
||||
use App\Livewire\Settings\Index as SettingsIndex;
|
||||
use App\Livewire\Settings\License as SettingsLicense;
|
||||
use App\Livewire\SettingsBackup;
|
||||
use App\Livewire\SettingsEmail;
|
||||
use App\Livewire\SettingsOauth;
|
||||
use App\Livewire\SharedVariables\Environment\Index as EnvironmentSharedVariablesIndex;
|
||||
use App\Livewire\SharedVariables\Environment\Show as EnvironmentSharedVariablesShow;
|
||||
use App\Livewire\SharedVariables\Index as SharedVariablesIndex;
|
||||
@@ -113,6 +116,9 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('/subscription/new', SubscriptionIndex::class)->name('subscription.index');
|
||||
|
||||
Route::get('/settings', SettingsIndex::class)->name('settings.index');
|
||||
Route::get('/settings/backup', SettingsBackup::class)->name('settings.backup');
|
||||
Route::get('/settings/email', SettingsEmail::class)->name('settings.email');
|
||||
Route::get('/settings/oauth', SettingsOauth::class)->name('settings.oauth');
|
||||
Route::get('/settings/license', SettingsLicense::class)->name('settings.license');
|
||||
|
||||
Route::get('/profile', ProfileIndex::class)->name('profile');
|
||||
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
postgresql:
|
||||
image: postgres:16-alpine
|
||||
image: postgis/postgis:16-3.4-alpine
|
||||
volumes:
|
||||
- directus-postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# documentation: https://formbricks.com
|
||||
# slogan: Open Source Experience Management
|
||||
# tags: form, builder, forms, open source, experience, management, self-hosted, docker
|
||||
# documentation: https://formbricks.com/docs/self-hosting/configuration
|
||||
# slogan: Open Source Survey Platform
|
||||
# tags: form, builder, forms, survey, open source, experience, management, self-hosted, docker
|
||||
# logo: svgs/formbricks.png
|
||||
# port: 3000
|
||||
|
||||
@@ -11,33 +11,55 @@ services:
|
||||
- SERVICE_FQDN_FORMBRICKS_3000
|
||||
- WEBAPP_URL=$SERVICE_FQDN_FORMBRICKS
|
||||
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgresql:5432/${POSTGRESQL_DATABASE:-formbricks}
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- NEXTAUTH_SECRET=$SERVICE_BASE64_64_NEXTAUTH
|
||||
- NEXTAUTH_URL=$SERVICE_FQDN_FORMBRICKS
|
||||
- ENCRYPTION_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||
- CRON_SECRET=$SERVICE_BASE64_64_CRON
|
||||
- ENTERPRISE_LICENSE_KEY=${ENTERPRISE_LICENSE_KEY}
|
||||
- MAIL_FROM=${MAIL_FROM:-test@example.com}
|
||||
- SMTP_HOST=${SMTP_HOST:-test.example.com}
|
||||
- SMTP_PORT=${SMTP_PORT:-587}
|
||||
- SMTP_USER=${SMTP_USER:-test}
|
||||
- SMTP_PASSWORD=${SMTP_PASSWORD:-test}
|
||||
- SMTP_SECURE_ENABLED=${SMTP_SECURE_ENABLED:-0}
|
||||
- SMTP_REJECT_UNAUTHORIZED_TLS=${SMTP_REJECT_UNAUTHORIZED_TLS:-1}
|
||||
- SHORT_URL_BASE=${SHORT_URL_BASE}
|
||||
- EMAIL_VERIFICATION_DISABLED=${EMAIL_VERIFICATION_DISABLED:-1}
|
||||
- PASSWORD_RESET_DISABLED=${PASSWORD_RESET_DISABLED:-1}
|
||||
- SIGNUP_DISABLED=${SIGNUP_DISABLED:-0}
|
||||
- EMAIL_AUTH_DISABLED=${EMAIL_AUTH_DISABLED:-0}
|
||||
- INVITE_DISABLED=${INVITE_DISABLED:-0}
|
||||
- ASSET_PREFIX_URL=${ASSET_PREFIX_URL}
|
||||
- UNSPLASH_ACCESS_KEY=${UNSPLASH_ACCESS_KEY}
|
||||
- GITHUB_ID=${GITHUB_ID}
|
||||
- GITHUB_SECRET=${GITHUB_SECRET}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||
- AZUREAD_CLIENT_ID=${AZUREAD_CLIENT_ID}
|
||||
- AZUREAD_CLIENT_SECRET=${AZUREAD_CLIENT_SECRET}
|
||||
- AZUREAD_TENANT_ID=${AZUREAD_TENANT_ID}
|
||||
- OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
|
||||
- OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
|
||||
- OIDC_ISSUER=${OIDC_ISSUER}
|
||||
- OIDC_DISPLAY_NAME=${OIDC_DISPLAY_NAME}
|
||||
- OIDC_SIGNING_ALGORITHM=${OIDC_SIGNING_ALGORITHM}
|
||||
- NOTION_OAUTH_CLIENT_ID=${NOTION_OAUTH_CLIENT_ID}
|
||||
- NOTION_OAUTH_CLIENT_SECRET=${NOTION_OAUTH_CLIENT_SECRET}
|
||||
- GOOGLE_SHEETS_CLIENT_ID=${GOOGLE_SHEETS_CLIENT_ID}
|
||||
- GOOGLE_SHEETS_CLIENT_SECRET=${GOOGLE_SHEETS_CLIENT_SECRET}
|
||||
- GOOGLE_SHEETS_REDIRECT_URL=${GOOGLE_SHEETS_REDIRECT_URL}
|
||||
- AIRTABLE_CLIENT_ID=${AIRTABLE_CLIENT_ID}
|
||||
- SLACK_CLIENT_ID=${SLACK_CLIENT_ID}
|
||||
- SLACK_CLIENT_SECRET=${SLACK_CLIENT_SECRET}
|
||||
- PRIVACY_URL=${PRIVACY_URL}
|
||||
- TERMS_URL=${TERMS_URL}
|
||||
- IMPRINT_URL=${IMPRINT_URL}
|
||||
- GITHUB_AUTH_ENABLED=${GITHUB_AUTH_ENABLED:-0}
|
||||
- GITHUB_ID=${GITHUB_ID}
|
||||
- GITHUB_SECRET=${GITHUB_SECRET}
|
||||
- GOOGLE_AUTH_ENABLED=${GOOGLE_AUTH_ENABLED:-0}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||
- ASSET_PREFIX_URL=${ASSET_PREFIX_URL}
|
||||
- CRON_SECRET=$SERVICE_BASE64_64_CRON
|
||||
- ENCRYPTION_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||
- RATE_LIMITING_DISABLED=${RATE_LIMITING_DISABLED:-0}
|
||||
- OPENTELEMETRY_LISTENER_URL=${OPENTELEMETRY_LISTENER_URL}
|
||||
- REDIS_URL=${REDIS_URL}
|
||||
- REDIS_HTTP_URL=${REDIS_HTTP_URL}
|
||||
- DEFAULT_ORGANIZATION_ID=${DEFAULT_ORGANIZATION_ID}
|
||||
- DEFAULT_ORGANIZATION_ROLE=${DEFAULT_ORGANIZATION_ROLE:-admin}
|
||||
volumes:
|
||||
- formbricks-uploads:/apps/web/uploads/
|
||||
depends_on:
|
||||
|
||||
@@ -11,6 +11,112 @@ services:
|
||||
- SERVICE_FQDN_HOMEPAGE_3000
|
||||
- HOMEPAGE_VAR_BASE=${SERVICE_FQDN_HOMEPAGE}
|
||||
volumes:
|
||||
- homepage-config:/app/config
|
||||
- homepage-images:/app/public/images
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./images:/app/public/images
|
||||
|
||||
- type: bind
|
||||
source: ./config/bookmarks.yaml
|
||||
target: /app/config/bookmarks.yaml
|
||||
content: |
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/bookmarks
|
||||
|
||||
- Developer:
|
||||
- Github:
|
||||
- abbr: GH
|
||||
href: https://github.com/
|
||||
|
||||
- Social:
|
||||
- Reddit:
|
||||
- abbr: RE
|
||||
href: https://reddit.com/
|
||||
|
||||
- Entertainment:
|
||||
- YouTube:
|
||||
- abbr: YT
|
||||
href: https://youtube.com/
|
||||
|
||||
- type: bind
|
||||
source: ./config/custom.css
|
||||
target: /app/config/custom.css
|
||||
content: ""
|
||||
|
||||
- type: bind
|
||||
source: ./config/custom.js
|
||||
target: /app/config/custom.js
|
||||
content: ""
|
||||
|
||||
- type: bind
|
||||
source: ./config/docker.yaml
|
||||
target: /app/config/docker.yaml
|
||||
content: |
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/docker/
|
||||
|
||||
# my-docker:
|
||||
# host: 127.0.0.1
|
||||
# port: 2375
|
||||
|
||||
# my-docker:
|
||||
# socket: /var/run/docker.sock
|
||||
|
||||
- type: bind
|
||||
source: ./config/kubernetes.yaml
|
||||
target: /app/config/kubernetes.yaml
|
||||
content: |
|
||||
---
|
||||
# sample kubernetes config
|
||||
|
||||
- type: bind
|
||||
source: ./config/services.yaml
|
||||
target: /app/config/services.yaml
|
||||
content: |
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/services
|
||||
|
||||
- My First Group:
|
||||
- My First Service:
|
||||
href: http://localhost/
|
||||
description: Homepage is awesome
|
||||
|
||||
- My Second Group:
|
||||
- My Second Service:
|
||||
href: http://localhost/
|
||||
description: Homepage is the best
|
||||
|
||||
- My Third Group:
|
||||
- My Third Service:
|
||||
href: http://localhost/
|
||||
description: Homepage is 😎
|
||||
|
||||
- type: bind
|
||||
source: ./config/settings.yaml
|
||||
target: /app/config/settings.yaml
|
||||
content: |
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/settings
|
||||
|
||||
providers:
|
||||
openweathermap: openweathermapapikey
|
||||
weatherapi: weatherapiapikey
|
||||
|
||||
- type: bind
|
||||
source: ./config/widgets.yaml
|
||||
target: /app/config/widgets.yaml
|
||||
content: |
|
||||
---
|
||||
# For configuration options and examples, please see:
|
||||
# https://gethomepage.dev/latest/configs/service-widgets
|
||||
|
||||
- resources:
|
||||
cpu: true
|
||||
memory: true
|
||||
disk: /
|
||||
|
||||
- search:
|
||||
provider: duckduckgo
|
||||
target: _blank
|
||||
|
||||
48
templates/compose/minecraft.yaml
Normal file
48
templates/compose/minecraft.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
# documentation: https://github.com/itzg/docker-minecraft-server
|
||||
# slogan: Minecraft Server that will automatically download selected version at startup.
|
||||
# tags: minecraft
|
||||
# logo: svgs/minecraft.svg
|
||||
# port: 25565
|
||||
|
||||
services:
|
||||
mc:
|
||||
image: itzg/minecraft-server
|
||||
ports:
|
||||
- ${PORT}:25565
|
||||
environment:
|
||||
- EULA=true
|
||||
- VERSION=${MINECRAFT_VERSION:-latest}
|
||||
- TYPE=${MINECRAFT_TYPE:-VANILLA}
|
||||
- SERVER_NAME=${MINECRAFT_SERVER_NAME:-Minecraft Server}
|
||||
- MOTD=${MINECRAFT_MOTD:-Minecraft Server powered by §aCoolify§r}
|
||||
- DIFFICULTY=${MINECRAFT_DIFFICULTY:-normal}
|
||||
- MAX_PLAYERS=${MINECRAFT_MAX_PLAYERS:-10}
|
||||
- MAX_WORLD_SIZE=${MINECRAFT_MAX_WORLD_SIZE:-10000}
|
||||
- VIEW_DISTANCE=${MINECRAFT_VIEW_DISTANCE:-10}
|
||||
- MAX_BUILD_HEIGHT=${MINECRAFT_MAX_BUILD_HEIGHT:-256}
|
||||
- MAX_TICK_TIME=${MINECRAFT_MAX_TICK_TIME:-60000}
|
||||
- ALLOW_NETHER=${MINECRAFT_ALLOW_NETHER:-true}
|
||||
- ANNOUNCE_PLAYER_ACHIEVEMENTS=${MINECRAFT_ANNOUNCE_PLAYER_ACHIEVEMENTS:-true}
|
||||
- GENERATE_STRUCTURES=${MINECRAFT_GENERATE_STRUCTURES:-true}
|
||||
- PVP=${MINECRAFT_PVP:-true}
|
||||
- FORCE_GAMEMODE=${MINECRAFT_FORCE_GAMEMODE:-false}
|
||||
- HARDCORE=${MINECRAFT_HARDCORE:-false}
|
||||
- ENABLE_COMMAND_BLOCK=${MINECRAFT_ENABLE_COMMAND_BLOCK:-false}
|
||||
- SPAWN_ANIMALS=${MINECRAFT_SPAWN_ANIMALS:-true}
|
||||
- SPAWN_MONSTERS=${MINECRAFT_SPAWN_MONSTERS:-true}
|
||||
- SPAWN_NPCS=${MINECRAFT_SPAWN_NPCS:-true}
|
||||
- SNOOPER_ENABLED=${MINECRAFT_SNOOPER_ENABLED:-true}
|
||||
- ONLINE_MODE=${MINECRAFT_ONLINE_MODE:-true}
|
||||
- PLAYER_IDLE_TIMEOUT=${MINECRAFT_PLAYER_IDLE_TIMEOUT:-0}
|
||||
- MEMORY=${MINECRAFT_MEMORY:-1G}
|
||||
- ENABLE_AUTOPAUSE=${MINECRAFT_ENABLE_AUTOPAUSE:-false}
|
||||
- RCON_PASSWORD=${SERVICE_PASSWORD_RCON}
|
||||
- PORT=${PORT:-25565}
|
||||
volumes:
|
||||
- ./minecraft-data:/data
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "/usr/local/bin/mc-monitor", "status", "--host", "localhost"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
services:
|
||||
plausible:
|
||||
image: "ghcr.io/plausible/community-edition:v2.1.0"
|
||||
image: "ghcr.io/plausible/community-edition:v2.1"
|
||||
command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
|
||||
environment:
|
||||
- "DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible"
|
||||
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE
|
||||
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
|
||||
- TOTP_VAULT_KEY=$SERVICE_BASE64_TOTP
|
||||
- TOTP_VAULT_KEY=$SERVICE_REALBASE64_32_TOTP
|
||||
depends_on:
|
||||
- plausible_db
|
||||
- plausible_events_db
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user