mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-24 12:33:17 +00:00
Compare commits
103 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21e77bf0c1 | ||
|
|
0686e48e89 | ||
|
|
1cef233db2 | ||
|
|
907e52572c | ||
|
|
795c8abf64 | ||
|
|
cc641d8cba | ||
|
|
d4668ef44a | ||
|
|
e8b539c3bd | ||
|
|
6555f0b50c | ||
|
|
bb05058dda | ||
|
|
9667cd4a7a | ||
|
|
3ae9501814 | ||
|
|
73d0948734 | ||
|
|
c3e2a741ea | ||
|
|
4792146f1d | ||
|
|
09b9305aa3 | ||
|
|
ff7d0d442d | ||
|
|
9a127bdc80 | ||
|
|
919e88afb4 | ||
|
|
1d1ec20cb7 | ||
|
|
5c29ecdf10 | ||
|
|
9e09c449cf | ||
|
|
09bcd693f5 | ||
|
|
0c15e45419 | ||
|
|
31cbf552a2 | ||
|
|
f7853ee174 | ||
|
|
de3a7b6eca | ||
|
|
b56c7c34cb | ||
|
|
49845f3da7 | ||
|
|
987409bae4 | ||
|
|
07d8461f96 | ||
|
|
f255a71434 | ||
|
|
2a2818ac0d | ||
|
|
fd3cdc2c7d | ||
|
|
84c3f832ae | ||
|
|
70c28fceeb | ||
|
|
f2c4f83f5a | ||
|
|
c8dd6f07ac | ||
|
|
561e424a7d | ||
|
|
c46d38907e | ||
|
|
5c334bbac6 | ||
|
|
9628072b0c | ||
|
|
39ecff9f90 | ||
|
|
d1daec060a | ||
|
|
fb5bea7f91 | ||
|
|
efc3ea6e40 | ||
|
|
ecefb9e1f5 | ||
|
|
a4dea2009a | ||
|
|
829e41f93f | ||
|
|
e7e3adc7fb | ||
|
|
a993fef235 | ||
|
|
81df71416c | ||
|
|
40af7aa025 | ||
|
|
050155328b | ||
|
|
376c081bed | ||
|
|
9f5e1fa9e3 | ||
|
|
7d139fd33b | ||
|
|
b75a2857a0 | ||
|
|
ab6c1ddc20 | ||
|
|
dc0b0980a9 | ||
|
|
788d1711db | ||
|
|
d92dc4c5e6 | ||
|
|
4bf34aea62 | ||
|
|
7e9a54ce67 | ||
|
|
f8c19e1fb3 | ||
|
|
27c1bda09b | ||
|
|
1af7ffcdc4 | ||
|
|
39647367a5 | ||
|
|
ae4b263810 | ||
|
|
8901bb5df8 | ||
|
|
053aa25d2c | ||
|
|
7a7157c155 | ||
|
|
0c5e8600bd | ||
|
|
048e153025 | ||
|
|
e7cafe6850 | ||
|
|
1385a86084 | ||
|
|
348923ae02 | ||
|
|
7d754558b0 | ||
|
|
744609e7e9 | ||
|
|
9bd05b65a3 | ||
|
|
d42934f258 | ||
|
|
ff752e2411 | ||
|
|
4120fba9a8 | ||
|
|
6ecb9c21ce | ||
|
|
01f7b07fa3 | ||
|
|
54d8cb9027 | ||
|
|
238337fecb | ||
|
|
7a51acbf8d | ||
|
|
fb478c79b3 | ||
|
|
abcc004953 | ||
|
|
2edf71a0dd | ||
|
|
cbec39099a | ||
|
|
dba5499182 | ||
|
|
2fdf52929c | ||
|
|
8128dfc061 | ||
|
|
2b394d6fea | ||
|
|
964ded1d0b | ||
|
|
838c3830d6 | ||
|
|
2db93bd9b9 | ||
|
|
2f82dedd4f | ||
|
|
8106602d15 | ||
|
|
3d0bf6b472 | ||
|
|
ba7a7e9695 |
12
.env.windows-docker-desktop.example
Normal file
12
.env.windows-docker-desktop.example
Normal file
@@ -0,0 +1,12 @@
|
||||
IS_WINDOWS_DOCKER_DESKTOP=true
|
||||
|
||||
APP_ID=coolify-windows-docker-desktop
|
||||
APP_NAME=Coolify
|
||||
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
||||
|
||||
DB_PASSWORD=coolify
|
||||
REDIS_PASSWORD=coolify
|
||||
|
||||
PUSHER_APP_ID=coolify
|
||||
PUSHER_APP_KEY=coolify
|
||||
PUSHER_APP_SECRET=coolify
|
||||
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Testing Host (v4-non-prod)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "next" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-testing-host.yml
|
||||
- docker/testing-host/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-testing-host"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/testing-host/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/testing-host/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [ amd64, aarch64 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
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 }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
20
.github/workflows/development-build.yml
vendored
20
.github/workflows/development-build.yml
vendored
@@ -15,15 +15,15 @@ jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
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@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -36,15 +36,15 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
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@v3
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -59,13 +59,13 @@ jobs:
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
||||
22
.github/workflows/production-build.yml
vendored
22
.github/workflows/production-build.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -34,9 +34,9 @@ jobs:
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -61,13 +61,13 @@ jobs:
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
@@ -11,6 +11,9 @@ class StopApplication
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||
} else {
|
||||
|
||||
@@ -140,7 +140,7 @@ class StartMariadb
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
|
||||
@@ -156,7 +156,7 @@ class StartMongodb
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||
|
||||
@@ -140,7 +140,7 @@ class StartMysql
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||
|
||||
@@ -164,7 +164,7 @@ class StartPostgresql
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@@ -106,7 +107,7 @@ class StartRedis
|
||||
'target' => '/usr/local/etc/redis/redis.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
@@ -150,7 +151,7 @@ class StartRedis
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
@@ -165,8 +166,9 @@ class StartRedis
|
||||
return;
|
||||
}
|
||||
$filename = 'redis.conf';
|
||||
$content = $this->database->redis_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||
Storage::disk('local')->put("tmp/redis.conf_{$this->database->uuid}", $this->database->redis_conf);
|
||||
$path = Storage::path("tmp/redis.conf_{$this->database->uuid}");
|
||||
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
|
||||
Storage::disk('local')->delete("tmp/redis.conf_{$this->database->uuid}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ class StopDatabase
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$server
|
||||
|
||||
@@ -10,35 +10,41 @@ class DeleteService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
StopService::run($service);
|
||||
}
|
||||
$storagesToDelete = collect([]);
|
||||
try {
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
$storagesToDelete = collect([]);
|
||||
|
||||
$service->environment_variables()->delete();
|
||||
$commands = [];
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
$service->environment_variables()->delete();
|
||||
$commands = [];
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
}
|
||||
$application->forceDelete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
} finally {
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$application->forceDelete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$database->forceDelete();
|
||||
}
|
||||
$database->forceDelete();
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
|
||||
$service->forceDelete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ class StartService
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Starting service: ' . $service->name);
|
||||
$network = $service->destination->network;
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
@@ -24,10 +23,13 @@ class StartService
|
||||
$commands[] = "echo 'Starting containers.'";
|
||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||
$compose = data_get($service, 'docker_compose', []);
|
||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||
if (data_get($service, 'connect_to_docker_network')) {
|
||||
$compose = data_get($service, 'docker_compose', []);
|
||||
$network = $service->destination->network;
|
||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||
}
|
||||
}
|
||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||
return $activity;
|
||||
|
||||
@@ -10,6 +10,10 @@ class StopService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$server = $service->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
ray('Stopping service: ' . $service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
|
||||
23
app/Console/Commands/CleanupQueue.php
Normal file
23
app/Console/Commands/CleanupQueue.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class CleanupQueue extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:queue';
|
||||
protected $description = 'Cleanup Queue';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running queue cleanup...\n";
|
||||
$prefix = config('database.redis.options.prefix');
|
||||
$keys = Redis::connection()->keys('*:laravel*');
|
||||
foreach ($keys as $key) {
|
||||
$keyWithoutPrefix = str_replace($prefix, '', $key);
|
||||
Redis::connection()->del($keyWithoutPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CleanupStuckedResources extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:stucked-resources';
|
||||
protected $description = 'Cleanup Stucked Resources';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running cleanup stucked...\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
{
|
||||
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
echo "Deleting stuck application: {$application->name}\n";
|
||||
$application->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||
$postgresql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($redis as $redis) {
|
||||
echo "Deleting stuck redis: {$redis->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||
$mongodb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mysqls as $mysql) {
|
||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||
$mysql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||
$mariadb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($services as $service) {
|
||||
echo "Deleting stuck service: {$service->name}\n";
|
||||
$service->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceApps as $serviceApp) {
|
||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||
$serviceApp->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceDbs as $serviceDb) {
|
||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||
$serviceDb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in redis: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CleanupUnreachableServers extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:unreachable-servers';
|
||||
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running unreachable server cleanup...\n";
|
||||
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
||||
if ($servers->count() > 0) {
|
||||
foreach ($servers as $server) {
|
||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||
$server->update([
|
||||
'ip' => '1.2.3.4'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use App\Notifications\Application\DeploymentFailed;
|
||||
use App\Notifications\Application\DeploymentSuccess;
|
||||
@@ -18,13 +16,11 @@ use App\Notifications\Application\StatusChanged;
|
||||
use App\Notifications\Database\BackupFailed;
|
||||
use App\Notifications\Database\BackupSuccess;
|
||||
use App\Notifications\Test;
|
||||
use App\Notifications\TransactionalEmails\InvitationLink;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Mail;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
@@ -4,19 +4,11 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
@@ -30,8 +22,8 @@ class Init extends Command
|
||||
$this->alive();
|
||||
$cleanup = $this->option('cleanup');
|
||||
if ($cleanup) {
|
||||
echo "Running cleanup\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
echo "Running cleanups...\n";
|
||||
$this->call('cleanup:stucked-resources');
|
||||
// Required for falsely deleted coolify db
|
||||
$this->restore_coolify_db_backup();
|
||||
|
||||
@@ -54,6 +46,7 @@ class Init extends Command
|
||||
$settings->update(['is_auto_update_enabled' => false]);
|
||||
}
|
||||
}
|
||||
$this->call('cleanup:queue');
|
||||
}
|
||||
private function restore_coolify_db_backup()
|
||||
{
|
||||
@@ -136,273 +129,5 @@ class Init extends Command
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
{
|
||||
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
echo "Deleting stuck application: {$application->name}\n";
|
||||
$application->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||
$postgresql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($redis as $redis) {
|
||||
echo "Deleting stuck redis: {$redis->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||
$mongodb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mysqls as $mysql) {
|
||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||
$mysql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||
$mariadb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($services as $service) {
|
||||
echo "Deleting stuck service: {$service->name}\n";
|
||||
$service->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceApps as $serviceApp) {
|
||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||
$serviceApp->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceDbs as $serviceDb) {
|
||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||
$serviceDb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in redis: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ class Kernel extends ConsoleKernel
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
|
||||
|
||||
@@ -10,4 +10,5 @@ enum ProcessStatus: string
|
||||
case ERROR = 'error';
|
||||
case KILLED = 'killed';
|
||||
case CANCELLED = 'cancelled';
|
||||
case CLOSED = 'closed';
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
@@ -158,6 +158,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->preview->fqdn = $preview_fqdn;
|
||||
$this->preview->save();
|
||||
}
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +223,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
}
|
||||
ray($this->build_server);
|
||||
try {
|
||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
|
||||
$this->just_restart();
|
||||
@@ -230,6 +232,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(false);
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockercompose') {
|
||||
@@ -241,11 +245,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else if ($this->application->build_pack === 'static') {
|
||||
$this->deploy_static_buildpack();
|
||||
} else {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else {
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
@@ -255,8 +255,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->push_to_docker_registry();
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->application->isConfigurationChanged(true);
|
||||
} catch (Exception $e) {
|
||||
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR);
|
||||
}
|
||||
ray($e);
|
||||
$this->fail($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
@@ -311,7 +320,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function push_to_docker_registry($forceFail = false)
|
||||
{
|
||||
ray((str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()));
|
||||
if (
|
||||
$this->application->docker_registry_image_name &&
|
||||
$this->application->build_pack !== 'dockerimage' &&
|
||||
@@ -340,9 +348,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
|
||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
|
||||
} catch (Exception $e) {
|
||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
|
||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
|
||||
if ($forceFail) {
|
||||
throw $e;
|
||||
}
|
||||
@@ -383,7 +391,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function just_restart()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
@@ -417,11 +425,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$envs = collect([]);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
foreach ($this->application->environment_variables_preview as $env) {
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->environment_variables as $env) {
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
}
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
@@ -480,10 +488,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
}
|
||||
$this->server->executeRemoteCommand(
|
||||
commands: $this->application->prepareHelperImage($this->deployment_uuid),
|
||||
loggingModel: $this->application_deployment_queue
|
||||
);
|
||||
ray('asddf');
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->clone_repository();
|
||||
$this->generate_image_names();
|
||||
@@ -681,7 +687,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->full_healthcheck_url) {
|
||||
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
|
||||
}
|
||||
while ($counter < $this->application->health_check_retries) {
|
||||
while ($counter <= $this->application->health_check_retries) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||
@@ -724,10 +730,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_nixpacks_confs();
|
||||
}
|
||||
$this->generate_compose_file();
|
||||
|
||||
// Needs separate preview variables
|
||||
$this->generate_build_env_variables();
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
if ($this->application->build_pack === 'dockerfile') {
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
}
|
||||
$this->build_image();
|
||||
@@ -863,7 +867,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function generate_git_import_commands()
|
||||
{
|
||||
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands($this->deployment_uuid, $this->pull_request_id, $this->git_type);
|
||||
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands(
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
pull_request_id: $this->pull_request_id,
|
||||
git_type: $this->git_type,
|
||||
commit: $this->commit
|
||||
);
|
||||
return $commands;
|
||||
}
|
||||
|
||||
@@ -929,11 +938,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_nixpacks_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,11 +953,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$this->env_args->put($env->key, $env->value);
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$this->env_args->put($env->key, $env->value);
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
}
|
||||
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
||||
@@ -1117,6 +1126,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||
@@ -1159,22 +1173,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function generate_environment_variables($ports)
|
||||
{
|
||||
$environment_variables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($this->pull_request_id === 0) {
|
||||
// ray($this->application->runtime_environment_variables)->green();
|
||||
foreach ($this->application->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
@@ -1457,12 +1468,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$value = escapeshellarg($env->value);
|
||||
$value = escapeshellarg($env->real_value);
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$value = escapeshellarg($env->value);
|
||||
$value = escapeshellarg($env->real_value);
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
}
|
||||
@@ -1478,11 +1489,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
@@ -1494,13 +1505,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
queue_next_deployment($this->application);
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
queue_next_deployment($this->application);
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
@@ -1512,7 +1523,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
|
||||
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
||||
if (str($exception->getMessage())->isNotEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
||||
}
|
||||
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
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 RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||
|
||||
public $timeout = 3600;
|
||||
public $tries = 1;
|
||||
|
||||
public static int $batch_counter = 0;
|
||||
public Server $mainServer;
|
||||
public $servers;
|
||||
public string $basedir;
|
||||
public string $workdir;
|
||||
|
||||
public string $deploymentUuid;
|
||||
public int $pullRequestId = 0;
|
||||
|
||||
// Git related
|
||||
public string $gitImportCommands;
|
||||
public ?string $gitType = null;
|
||||
public string $gitRepository;
|
||||
public string $gitBranch;
|
||||
public int $gitPort;
|
||||
public string $gitFullRepoUrl;
|
||||
|
||||
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
|
||||
{
|
||||
$this->mainServer = data_get($this->application, 'destination.server');
|
||||
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
|
||||
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
|
||||
$this->gitType = data_get($this->deployment, 'git_type');
|
||||
|
||||
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
|
||||
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
ray()->clearAll();
|
||||
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
|
||||
|
||||
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
|
||||
if ($this->application->dockerfile_target_build) {
|
||||
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
||||
}
|
||||
|
||||
// Get the git repository and port (custom port or default port)
|
||||
[
|
||||
'repository' => $this->gitRepository,
|
||||
'port' => $this->gitPort
|
||||
] = $this->application->customRepository();
|
||||
|
||||
// Get the git branch and git import commands
|
||||
[
|
||||
'commands' => $this->gitImportCommands,
|
||||
'branch' => $this->gitBranch,
|
||||
'fullRepoUrl' => $this->gitFullRepoUrl
|
||||
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
|
||||
|
||||
$this->servers = $this->application->servers();
|
||||
|
||||
if ($this->deployment->restart_only) {
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
throw new \Exception('Restart only is not supported for docker image based deployments');
|
||||
}
|
||||
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
$this->servers->each(function ($server) {
|
||||
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
|
||||
$this->restartOnly($server);
|
||||
});
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
} catch (Throwable $exception) {
|
||||
$this->fail($exception);
|
||||
} finally {
|
||||
$this->servers->each(function ($server) {
|
||||
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
"command" => "docker rm -f {$this->deploymentUuid}",
|
||||
"hidden" => true,
|
||||
"ignoreErrors" => true,
|
||||
]),
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
public function restartOnly(Server $server)
|
||||
{
|
||||
$server->executeRemoteCommand(
|
||||
commands: $this->application->prepareHelperImage($this->deploymentUuid),
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
|
||||
$privateKey = data_get($this->application, 'private_key.private_key', null);
|
||||
$gitLsRemoteCommand = collect([]);
|
||||
if ($privateKey) {
|
||||
$privateKey = base64_decode($privateKey);
|
||||
$gitLsRemoteCommand
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
|
||||
])
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
|
||||
])
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
|
||||
])
|
||||
->push([
|
||||
"name" => "git_commit_sha",
|
||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$gitLsRemoteCommand->push([
|
||||
"name" => "git_commit_sha",
|
||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
}
|
||||
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
|
||||
|
||||
$server->executeRemoteCommand(
|
||||
commands: $gitLsRemoteCommand,
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
|
||||
|
||||
[
|
||||
'productionImageName' => $productionImageName
|
||||
] = $this->application->generateImageNames($commit, $this->pullRequestId);
|
||||
|
||||
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
|
||||
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
|
||||
|
||||
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
|
||||
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
|
||||
|
||||
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
|
||||
|
||||
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
|
||||
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||
}
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
ray($exception);
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
}
|
||||
private function next(string $status)
|
||||
{
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->deployment->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
queue_next_deployment($this->application, isNew: true);
|
||||
}
|
||||
}
|
||||
@@ -17,38 +17,34 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $build_logs_url;
|
||||
public Application $application;
|
||||
public ApplicationPreview $preview;
|
||||
public string $body;
|
||||
|
||||
public function __construct(
|
||||
public string $application_id,
|
||||
public int $pull_request_id,
|
||||
public string $deployment_uuid,
|
||||
public string $status
|
||||
public Application $application,
|
||||
public ApplicationPreview $preview,
|
||||
public ProcessStatus $status,
|
||||
public ?string $deployment_uuid = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$this->application = Application::findOrFail($this->application_id);
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
|
||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
|
||||
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
|
||||
if ($this->status === ProcessStatus::CLOSED) {
|
||||
$this->delete_comment();
|
||||
return;
|
||||
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
|
||||
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
||||
}
|
||||
if ($this->status === ProcessStatus::FINISHED->value) {
|
||||
} else if ($this->status === ProcessStatus::FINISHED) {
|
||||
$this->body = "The preview deployment is ready. 🟢\n\n";
|
||||
if ($this->preview->fqdn) {
|
||||
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
||||
}
|
||||
}
|
||||
if ($this->status === ProcessStatus::ERROR->value) {
|
||||
} else if ($this->status === ProcessStatus::ERROR) {
|
||||
$this->body = "The preview deployment failed. 🔴\n\n";
|
||||
}
|
||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
|
||||
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
||||
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
||||
|
||||
@@ -77,10 +73,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function create_comment()
|
||||
{
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
|
||||
'body' => $this->body,
|
||||
]);
|
||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||
$this->preview->save();
|
||||
}
|
||||
private function delete_comment()
|
||||
{
|
||||
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
@@ -37,11 +40,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->handle();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->server->isFunctional()) {
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Service\DeleteService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneMariadb;
|
||||
@@ -30,41 +31,22 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$server = $this->resource->destination->server;
|
||||
$this->resource->delete();
|
||||
if (!$server->isFunctional()) {
|
||||
if ($this->resource->type() === 'service') {
|
||||
ray('dispatching delete service');
|
||||
DeleteService::dispatch($this->resource);
|
||||
} else {
|
||||
$this->resource->forceDelete();
|
||||
}
|
||||
return 'Server is not functional';
|
||||
}
|
||||
$this->resource->forceDelete();
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
StopApplication::run($this->resource);
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mongodb':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mysql':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mariadb':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
}
|
||||
if ($this->resource->type() === 'service') {
|
||||
DeleteService::dispatch($this->resource);
|
||||
} else {
|
||||
$this->resource->forceDelete();
|
||||
case 'service':
|
||||
StopService::run($this->resource);
|
||||
DeleteService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
|
||||
@@ -19,7 +19,7 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
|
||||
@@ -38,6 +38,8 @@ class ScheduledTaskJob implements ShouldQueue
|
||||
$this->resource = $service;
|
||||
} else if ($application = $task->application()->first()) {
|
||||
$this->resource = $application;
|
||||
} else {
|
||||
throw new \Exception('ScheduledTaskJob failed: No resource found.');
|
||||
}
|
||||
$this->team = Team::find($task->team_id);
|
||||
}
|
||||
|
||||
@@ -2,18 +2,35 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
{
|
||||
public $projects = [];
|
||||
public $servers = [];
|
||||
public Collection $servers;
|
||||
public $deployments_per_server;
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||
$this->get_deployments();
|
||||
}
|
||||
public function get_deployments()
|
||||
{
|
||||
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $this->servers->pluck("id"))->get([
|
||||
"id",
|
||||
"application_id",
|
||||
"application_name",
|
||||
"deployment_url",
|
||||
"pull_request_id",
|
||||
"server_name",
|
||||
"server_id",
|
||||
"status"
|
||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||
}
|
||||
// public function getIptables()
|
||||
// {
|
||||
|
||||
@@ -15,6 +15,7 @@ class Index extends Component
|
||||
public int $skip = 0;
|
||||
public int $default_take = 40;
|
||||
public bool $show_next = false;
|
||||
public bool $show_prev = false;
|
||||
public ?string $pull_request_id = null;
|
||||
protected $queryString = ['pull_request_id'];
|
||||
public function mount()
|
||||
@@ -60,15 +61,30 @@ class Index extends Component
|
||||
{
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function previous_page(?int $take = null)
|
||||
{
|
||||
|
||||
public function load_deployments(int|null $take = null)
|
||||
if ($take) {
|
||||
$this->skip = $this->skip - $take;
|
||||
}
|
||||
$this->skip = $this->skip - $this->default_take;
|
||||
if ($this->skip < 0) {
|
||||
$this->show_prev = false;
|
||||
$this->skip = 0;
|
||||
}
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function next_page(?int $take = null)
|
||||
{
|
||||
if ($take) {
|
||||
$this->skip = $this->skip + $take;
|
||||
}
|
||||
$take = $this->default_take;
|
||||
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
||||
$this->show_prev = true;
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function load_deployments()
|
||||
{
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->show_pull_request_only();
|
||||
|
||||
@@ -41,13 +41,10 @@ class DeploymentNavbar extends Component
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
$kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}";
|
||||
if ($this->application_deployment_queue->current_process_id) {
|
||||
$process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers");
|
||||
if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) {
|
||||
Process::run($kill_command);
|
||||
}
|
||||
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
||||
if ($this->application_deployment_queue->logs) {
|
||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$new_log_entry = [
|
||||
'command' => $kill_command,
|
||||
'output' => "Deployment cancelled by user.",
|
||||
@@ -60,15 +57,17 @@ class DeploymentNavbar extends Component
|
||||
$this->application_deployment_queue->update([
|
||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||
]);
|
||||
instant_remote_process([$kill_command], $this->server);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->application_deployment_queue->update([
|
||||
'current_process_id' => null,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
queue_next_deployment($this->application);
|
||||
// queue_next_deployment($this->application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,12 @@ class General extends Component
|
||||
'application.docker_compose_pr' => 'nullable',
|
||||
'application.docker_compose_raw' => 'nullable',
|
||||
'application.docker_compose_pr_raw' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.dockerfile_target_build' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.docker_compose_custom_start_command' => 'nullable',
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.custom_docker_run_options' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
];
|
||||
@@ -97,9 +98,10 @@ class General extends Component
|
||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||
];
|
||||
@@ -243,12 +245,16 @@ class General extends Component
|
||||
$domains = $domains->unique();
|
||||
foreach ($domains as $domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.","Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||
}
|
||||
}
|
||||
check_fqdn_usage($this->application);
|
||||
$this->application->fqdn = $domains->implode(',');
|
||||
}
|
||||
|
||||
if (data_get($this->application, 'custom_docker_run_options')) {
|
||||
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
|
||||
}
|
||||
if (data_get($this->application, 'dockerfile')) {
|
||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||
if ($port && !$this->application->ports_exposes) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\Application;
|
||||
@@ -55,7 +54,7 @@ class Heading extends Component
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: false,
|
||||
is_new_deployment: true,
|
||||
@@ -83,7 +82,7 @@ class Heading extends Component
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
);
|
||||
@@ -112,7 +111,7 @@ class Heading extends Component
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
is_new_deployment: true,
|
||||
@@ -128,7 +127,7 @@ class Heading extends Component
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ class Previews extends Component
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
force_rebuild: false,
|
||||
pull_request_id: $pull_request_id,
|
||||
|
||||
@@ -24,7 +24,7 @@ class Rollback extends Component
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
commit: $commit,
|
||||
force_rebuild: false,
|
||||
|
||||
@@ -12,7 +12,24 @@ class Edit extends Component
|
||||
'project.name' => 'required|min:3|max:255',
|
||||
'project.description' => 'nullable|string|max:255',
|
||||
];
|
||||
public function mount() {
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->project->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'project',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->project->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$projectUuid = request()->route('project_uuid');
|
||||
$teamId = currentTeam()->id;
|
||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
||||
|
||||
@@ -12,14 +12,30 @@ class EnvironmentEdit extends Component
|
||||
public Application $application;
|
||||
public $environment;
|
||||
public array $parameters;
|
||||
|
||||
protected $rules = [
|
||||
'environment.name' => 'required|min:3|max:255',
|
||||
'environment.description' => 'nullable|min:3|max:255',
|
||||
];
|
||||
public function mount() {
|
||||
$this->parameters = get_route_parameters();
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->environment->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'environment',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->environment->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
|
||||
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ class GithubPrivateRepository extends Component
|
||||
public bool $is_static = false;
|
||||
public string|null $publish_directory = null;
|
||||
protected int $page = 1;
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
|
||||
public function mount()
|
||||
@@ -49,6 +51,20 @@ class GithubPrivateRepository extends Component
|
||||
$this->repositories = $this->branches = collect();
|
||||
$this->github_apps = GithubApp::private();
|
||||
}
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
}
|
||||
}
|
||||
public function loadRepositories($github_app_id)
|
||||
{
|
||||
$this->repositories = collect();
|
||||
@@ -95,7 +111,7 @@ class GithubPrivateRepository extends Component
|
||||
$this->loadBranchByPage();
|
||||
}
|
||||
}
|
||||
$this->selected_branch_name = data_get($this->branches,'0.name');
|
||||
$this->selected_branch_name = data_get($this->branches, '0.name', 'main');
|
||||
}
|
||||
|
||||
protected function loadBranchByPage()
|
||||
|
||||
@@ -29,12 +29,17 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
|
||||
public string $repository_url;
|
||||
public string $branch;
|
||||
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
protected $rules = [
|
||||
'repository_url' => 'required',
|
||||
'branch' => 'required|string',
|
||||
'port' => 'required|numeric',
|
||||
'is_static' => 'required|boolean',
|
||||
'publish_directory' => 'nullable|string',
|
||||
'build_pack' => 'required|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'repository_url' => 'Repository',
|
||||
@@ -42,6 +47,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'port' => 'Port',
|
||||
'is_static' => 'Is static',
|
||||
'publish_directory' => 'Publish directory',
|
||||
'build_pack' => 'Build pack',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||
@@ -62,6 +68,20 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->is_static) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class PublicGitRepository extends Component
|
||||
public GithubApp|GitlabApp|string $git_source = 'other';
|
||||
public string $git_host;
|
||||
public string $git_repository;
|
||||
public $build_pack;
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
protected $rules = [
|
||||
@@ -61,9 +61,11 @@ class PublicGitRepository extends Component
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
|
||||
@@ -53,6 +53,7 @@ class Application extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
check_fqdn_usage($this->application);
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
|
||||
@@ -30,7 +30,10 @@ class Configuration extends Component
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->first();
|
||||
if (!$this->service) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->databases = $this->service->databases->sort();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class StackForm extends Component
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'nullable',
|
||||
'service.connect_to_docker_network' => 'nullable',
|
||||
];
|
||||
public $validationAttributes = [];
|
||||
public function mount()
|
||||
@@ -44,6 +45,9 @@ class StackForm extends Component
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
public function instantSave() {
|
||||
$this->service->save();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
|
||||
@@ -24,7 +24,8 @@ class Danger extends Component
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
DeleteResourceJob::dispatchSync($this->resource);
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource);
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName
|
||||
|
||||
@@ -32,7 +32,6 @@ class Add extends Component
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
ray($this->key, $this->value, $this->is_build_time);
|
||||
$this->dispatch('saveKey', [
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public $parameters;
|
||||
public ModelsEnvironmentVariable $env;
|
||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||
public ?string $modalId = null;
|
||||
public bool $isDisabled = false;
|
||||
public bool $isLocked = false;
|
||||
public bool $isSharedVariable = false;
|
||||
public string $type;
|
||||
|
||||
protected $rules = [
|
||||
@@ -20,16 +22,20 @@ class Show extends Component
|
||||
'env.value' => 'nullable',
|
||||
'env.is_build_time' => 'required|boolean',
|
||||
'env.is_shown_once' => 'required|boolean',
|
||||
'env.real_value' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'key' => 'Key',
|
||||
'value' => 'Value',
|
||||
'is_build_time' => 'Build Time',
|
||||
'is_shown_once' => 'Shown Once',
|
||||
'env.key' => 'Key',
|
||||
'env.value' => 'Value',
|
||||
'env.is_build_time' => 'Build Time',
|
||||
'env.is_shown_once' => 'Shown Once',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||
$this->isSharedVariable = true;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->checkEnvs();
|
||||
@@ -44,9 +50,16 @@ class Show extends Component
|
||||
$this->isLocked = true;
|
||||
}
|
||||
}
|
||||
public function serialize() {
|
||||
data_forget($this->env, 'real_value');
|
||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||
data_forget($this->env, 'is_build_time');
|
||||
}
|
||||
}
|
||||
public function lock()
|
||||
{
|
||||
$this->env->is_shown_once = true;
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->checkEnvs();
|
||||
$this->dispatch('refreshEnvs');
|
||||
@@ -57,10 +70,23 @@ class Show extends Component
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
try {
|
||||
if ($this->isSharedVariable) {
|
||||
$this->validate([
|
||||
'env.key' => 'required|string',
|
||||
'env.value' => 'nullable',
|
||||
'env.is_shown_once' => 'required|boolean',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch(\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
@@ -79,21 +79,21 @@ class ExecuteContainerCommand extends Component
|
||||
$this->resource = $resource;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
if (str(data_get($this,'resource.status'))->startsWith('running')) {
|
||||
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
|
||||
$this->containers->push($this->container);
|
||||
}
|
||||
// }
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
if (str(data_get($application, 'status'))->contains('running')) {
|
||||
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
}
|
||||
// }
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
if (str(data_get($database, 'status'))->contains('running')) {
|
||||
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
}
|
||||
// }
|
||||
});
|
||||
|
||||
$this->server = $this->resource->server;
|
||||
|
||||
@@ -70,21 +70,21 @@ class Logs extends Component
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
||||
// if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
||||
$this->containers->push($this->container);
|
||||
}
|
||||
// }
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
if (str(data_get($application, 'status'))->contains('running')) {
|
||||
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
}
|
||||
// }
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
if (str(data_get($database, 'status'))->contains('running')) {
|
||||
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
}
|
||||
// }
|
||||
});
|
||||
|
||||
$this->server = $this->resource->server;
|
||||
|
||||
178
app/Livewire/Project/Shared/ResourceOperations.php
Normal file
178
app/Livewire/Project/Shared/ResourceOperations.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ResourceOperations extends Component
|
||||
{
|
||||
public $resource;
|
||||
public $projectUuid;
|
||||
public $environmentName;
|
||||
public $projects;
|
||||
public $servers;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$parameters = get_route_parameters();
|
||||
$this->projectUuid = $parameters['project_uuid'];
|
||||
$this->environmentName = $parameters['environment_name'];
|
||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||
$this->servers = currentTeam()->servers;
|
||||
}
|
||||
public function cloneTo($destination_id)
|
||||
{
|
||||
$new_destination = StandaloneDocker::find($destination_id);
|
||||
if (!$new_destination) {
|
||||
$new_destination = SwarmDocker::find($destination_id);
|
||||
}
|
||||
if (!$new_destination) {
|
||||
return $this->addError('destination_id', 'Destination not found.');
|
||||
}
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$server = $new_destination->server;
|
||||
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'fqdn' => generateFqdn($server, $uuid),
|
||||
'status' => 'exited',
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||
$new_resource->custom_labels = base64_encode($customLabels);
|
||||
$new_resource->save();
|
||||
}
|
||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||
foreach ($environmentVaribles as $environmentVarible) {
|
||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||
'application_id' => $new_resource->id,
|
||||
]);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
$persistentVolumes = $this->resource->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
$newPersistentVolume = $volume->replicate()->fill([
|
||||
'name' => $new_resource->uuid . '-' . str($volume->name)->afterLast('-'),
|
||||
'resource_id' => $new_resource->id,
|
||||
]);
|
||||
$newPersistentVolume->save();
|
||||
}
|
||||
$route = route('project.application.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'application_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if (
|
||||
$this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis'
|
||||
) {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||
foreach ($environmentVaribles as $environmentVarible) {
|
||||
$payload = [];
|
||||
if ($this->resource->type() === 'standalone-postgresql') {
|
||||
$payload['standalone_postgresql_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-redis') {
|
||||
$payload['standalone_redis_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mongodb') {
|
||||
$payload['standalone_mongodb_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mysql') {
|
||||
$payload['standalone_mysql_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mariadb') {
|
||||
$payload['standalone_mariadb_id'] = $new_resource->id;
|
||||
}
|
||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
$route = route('project.database.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'database_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if ($this->resource->type() === 'service') {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
foreach ($new_resource->applications() as $application) {
|
||||
$application->update([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
}
|
||||
foreach ($new_resource->databases() as $database) {
|
||||
$database->update([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
}
|
||||
$new_resource->parse();
|
||||
$route = route('project.service.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'service_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
}
|
||||
return;
|
||||
}
|
||||
public function moveTo($environment_id)
|
||||
{
|
||||
try {
|
||||
$new_environment = Environment::findOrFail($environment_id);
|
||||
$this->resource->update([
|
||||
'environment_id' => $environment_id
|
||||
]);
|
||||
if ($this->resource->type() === 'application') {
|
||||
$route = route('project.application.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'application_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if (str($this->resource->type())->startsWith('standalone-')) {
|
||||
$route = route('project.database.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'database_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if ($this->resource->type() === 'service') {
|
||||
$route = route('project.service.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'service_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.resource-operations');
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@ class Webhooks extends Component
|
||||
public ?string $deploywebhook = null;
|
||||
public ?string $githubManualWebhook = null;
|
||||
public ?string $gitlabManualWebhook = null;
|
||||
public ?string $bitbucketManualWebhook = null;
|
||||
protected $rules = [
|
||||
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
|
||||
];
|
||||
public function saveSecret()
|
||||
{
|
||||
@@ -29,6 +31,7 @@ class Webhooks extends Component
|
||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ class Form extends Component
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_build_server' => 'required|boolean',
|
||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -40,6 +41,7 @@ class Form extends Component
|
||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
'server.settings.is_build_server' => 'Build Server',
|
||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
|
||||
@@ -11,6 +11,7 @@ class Show extends Component
|
||||
use AuthorizesRequests;
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
protected $listeners = ['proxyStatusUpdated' => '$refresh'];
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
@@ -19,14 +20,13 @@ class Show extends Component
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->dispatch('serverRefresh',false);
|
||||
$this->dispatch('serverRefresh', false);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ class Change extends Component
|
||||
public ?bool $preview_deployment_permissions = true;
|
||||
|
||||
public $parameters;
|
||||
public GithubApp $github_app;
|
||||
public ?GithubApp $github_app;
|
||||
public string $name;
|
||||
public bool $is_system_wide;
|
||||
|
||||
|
||||
36
app/Livewire/TeamSharedVariablesIndex.php
Normal file
36
app/Livewire/TeamSharedVariablesIndex.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Team;
|
||||
use Livewire\Component;
|
||||
|
||||
class TeamSharedVariablesIndex extends Component
|
||||
{
|
||||
public Team $team;
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->team->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'team',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->team->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = currentTeam();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.team-shared-variables-index');
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,9 @@ use App\Enums\ApplicationDeploymentStatus;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
@@ -39,6 +37,7 @@ class Application extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
$application->update(['fqdn' => null]);
|
||||
$application->settings()->delete();
|
||||
$storages = $application->persistentStorages()->get();
|
||||
$server = data_get($application, 'destination.server');
|
||||
@@ -52,65 +51,14 @@ class Application extends BaseModel
|
||||
$application->environment_variables_preview()->delete();
|
||||
});
|
||||
}
|
||||
// Build packs / deployment types
|
||||
|
||||
|
||||
public function servers(): Collection
|
||||
public function is_github_based(): bool
|
||||
{
|
||||
$mainServer = data_get($this, 'destination.server');
|
||||
$additionalDestinations = data_get($this, 'additional_destinations', null);
|
||||
$additionalServers = collect([]);
|
||||
if ($this->isMultipleServerDeployment()) {
|
||||
ray('asd');
|
||||
if (str($additionalDestinations)->isNotEmpty()) {
|
||||
$additionalDestinations = str($additionalDestinations)->explode(',');
|
||||
foreach ($additionalDestinations as $destinationId) {
|
||||
$destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first();
|
||||
$server = data_get($destination, 'server');
|
||||
$additionalServers->push($server);
|
||||
}
|
||||
}
|
||||
if (data_get($this, 'source')) {
|
||||
return true;
|
||||
}
|
||||
return collect([$mainServer])->merge($additionalServers);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function generateImageNames(string $commit, int $pullRequestId)
|
||||
{
|
||||
if ($this->dockerfile) {
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:latest");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:latest");
|
||||
}
|
||||
} else if ($this->build_pack === 'dockerimage') {
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}");
|
||||
} else if ($pullRequestId === 0) {
|
||||
$dockerImageTag = str($commit)->substr(0, 128);
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}");
|
||||
}
|
||||
} else if ($pullRequestId !== 0) {
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}");
|
||||
}
|
||||
}
|
||||
return [
|
||||
'buildImageName' => $buildImageName,
|
||||
'productionImageName' => $productionImageName,
|
||||
];
|
||||
}
|
||||
// End of build packs / deployment types
|
||||
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
@@ -263,6 +211,10 @@ class Application extends BaseModel
|
||||
: explode(',', $this->ports_exposes)
|
||||
);
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function serviceType()
|
||||
{
|
||||
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {
|
||||
@@ -431,7 +383,7 @@ class Application extends BaseModel
|
||||
{
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables->all());
|
||||
$newConfigHash .= json_encode($this->environment_variables());
|
||||
} else {
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->all());
|
||||
}
|
||||
@@ -454,31 +406,6 @@ class Application extends BaseModel
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isMultipleServerDeployment()
|
||||
{
|
||||
return false;
|
||||
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function healthCheckUrl()
|
||||
{
|
||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
return null;
|
||||
}
|
||||
if (!$this->health_check_port) {
|
||||
$health_check_port = $this->ports_exposes_array[0];
|
||||
} else {
|
||||
$health_check_port = $this->health_check_port;
|
||||
}
|
||||
if ($this->health_check_path) {
|
||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
||||
} else {
|
||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
||||
}
|
||||
return $full_healthcheck_url;
|
||||
}
|
||||
function customRepository()
|
||||
{
|
||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
||||
@@ -500,296 +427,7 @@ class Application extends BaseModel
|
||||
{
|
||||
return "/artifacts/{$uuid}";
|
||||
}
|
||||
function generateHealthCheckCommands()
|
||||
{
|
||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||
return 'exit 0';
|
||||
}
|
||||
if (!$this->health_check_port) {
|
||||
$health_check_port = $this->ports_exposes_array[0];
|
||||
} else {
|
||||
$health_check_port = $this->health_check_port;
|
||||
}
|
||||
if ($this->health_check_path) {
|
||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null"
|
||||
];
|
||||
} else {
|
||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"
|
||||
];
|
||||
}
|
||||
return implode(' ', $generated_healthchecks_commands);
|
||||
}
|
||||
function generateLocalPersistentVolumes(int $pullRequestId)
|
||||
{
|
||||
$persistentStorages = [];
|
||||
$volumeNames = [];
|
||||
foreach ($this->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
if ($pullRequestId !== 0) {
|
||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
$persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $persistentStorage->name;
|
||||
|
||||
if ($pullRequestId !== 0) {
|
||||
$name = $name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
|
||||
$volumeNames[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'persistentStorages' => $persistentStorages,
|
||||
'volumeNames' => $volumeNames,
|
||||
];
|
||||
}
|
||||
public function generateEnvironmentVariables($ports)
|
||||
{
|
||||
$environmentVariables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($this->pull_request_id === 0) {
|
||||
// ray($this->runtime_environment_variables)->green();
|
||||
foreach ($this->runtime_environment_variables as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($this->nixpacks_environment_variables as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->runtime_environment_variables_preview)->green();
|
||||
foreach ($this->runtime_environment_variables_preview as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($this->nixpacks_environment_variables_preview as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||
$environmentVariables->push("PORT={$ports[0]}");
|
||||
}
|
||||
return $environmentVariables->all();
|
||||
}
|
||||
function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
||||
{
|
||||
$pullRequestId = $deployment->pull_request_id;
|
||||
$ports = $this->settings->is_static ? [80] : $this->ports_exposes_array;
|
||||
$container_name = generateApplicationContainerName($this, $this->pull_request_id);
|
||||
$commit = str($deployment->getOutput('git_commit_sha'))->before("\t");
|
||||
|
||||
[
|
||||
'productionImageName' => $productionImageName
|
||||
] = $this->generateImageNames($commit, $pullRequestId);
|
||||
|
||||
[
|
||||
'persistentStorages' => $persistentStorages,
|
||||
'volumeNames' => $volumeNames
|
||||
] = $this->generateLocalPersistentVolumes($pullRequestId);
|
||||
|
||||
$environmentVariables = $this->generateEnvironmentVariables($ports);
|
||||
|
||||
if (data_get($this, 'custom_labels')) {
|
||||
$labels = collect(str($this->custom_labels)->explode(','));
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !Str::startsWith($value, 'coolify.');
|
||||
});
|
||||
$this->custom_labels = $labels->implode(',');
|
||||
$this->save();
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $productionImageName,
|
||||
'container_name' => $container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environmentVariables,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generateHealthCheckCommands()
|
||||
],
|
||||
'interval' => $this->health_check_interval . 's',
|
||||
'timeout' => $this->health_check_timeout . 's',
|
||||
'retries' => $this->health_check_retries,
|
||||
'start_period' => $this->health_check_start_period . 's'
|
||||
],
|
||||
'mem_limit' => $this->limits_memory,
|
||||
'memswap_limit' => $this->limits_memory_swap,
|
||||
'mem_swappiness' => $this->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->limits_memory_reservation,
|
||||
'cpus' => (float) $this->limits_cpus,
|
||||
'cpu_shares' => $this->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->destination->network,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($this->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.expose');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.restart');
|
||||
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_limit');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpus');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpuset');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares');
|
||||
|
||||
$docker_compose['services'][$container_name]['deploy'] = [
|
||||
'placement' => [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
],
|
||||
'mode' => 'replicated',
|
||||
'replicas' => 1,
|
||||
'update_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
'rollback_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
'labels' => $labels,
|
||||
'resources' => [
|
||||
'limits' => [
|
||||
'cpus' => $this->limits_cpus,
|
||||
'memory' => $this->limits_memory,
|
||||
],
|
||||
'reservations' => [
|
||||
'cpus' => $this->limits_cpus,
|
||||
'memory' => $this->limits_memory,
|
||||
]
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$docker_compose['services'][$container_name]['labels'] = $labels;
|
||||
}
|
||||
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($this->settings->is_gpu_enabled) {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [
|
||||
[
|
||||
'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'),
|
||||
'capabilities' => ['gpu'],
|
||||
'options' => data_get($this, 'settings.gpu_options', [])
|
||||
]
|
||||
];
|
||||
if (data_get($this, 'settings.gpu_count')) {
|
||||
$count = data_get($this, 'settings.gpu_count');
|
||||
if ($count === 'all') {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
||||
} else {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
||||
}
|
||||
} else if (data_get($this, 'settings.gpu_device_ids')) {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids');
|
||||
}
|
||||
}
|
||||
if ($this->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.healthcheck');
|
||||
}
|
||||
if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array;
|
||||
}
|
||||
if (count($persistentStorages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistentStorages;
|
||||
}
|
||||
if (count($volumeNames) > 0) {
|
||||
$docker_compose['volumes'] = $volumeNames;
|
||||
}
|
||||
|
||||
$docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name];
|
||||
|
||||
data_forget($docker_compose, 'services.' . $container_name);
|
||||
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"),
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
||||
{
|
||||
$pullRequestId = $deployment->pull_request_id;
|
||||
$containerName = generateApplicationContainerName($this, $pullRequestId);
|
||||
// if (count($this->ports_mappings_array) > 0) {
|
||||
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
|
||||
// if ($pullRequestId === 0) {
|
||||
// $containers = $containers->filter(function ($container) use ($containerName) {
|
||||
// return data_get($container, 'Names') !== $containerName;
|
||||
// });
|
||||
// }
|
||||
$containers->each(function ($container) use ($server, $deployment) {
|
||||
$removingContainerName = data_get($container, 'Names');
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => "docker rm -f $removingContainerName",
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
});
|
||||
// }
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"),
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
$deployment->addLogEntry("New container started.");
|
||||
}
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||
{
|
||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||
if ($this->git_commit_sha !== 'HEAD') {
|
||||
@@ -803,7 +441,7 @@ class Application extends BaseModel
|
||||
}
|
||||
return $git_clone_command;
|
||||
}
|
||||
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null)
|
||||
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
||||
{
|
||||
$branch = $this->git_branch;
|
||||
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
||||
@@ -816,7 +454,6 @@ class Application extends BaseModel
|
||||
if ($pull_request_id !== 0) {
|
||||
$pr_branch_name = "pr-{$pull_request_id}-coolify";
|
||||
}
|
||||
|
||||
if ($this->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this, 'source.html_url');
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||
@@ -895,8 +532,7 @@ class Application extends BaseModel
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
}
|
||||
if ($git_type === 'github') {
|
||||
} else if ($git_type === 'github') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
@@ -904,6 +540,13 @@ class Application extends BaseModel
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,6 +565,34 @@ class Application extends BaseModel
|
||||
$fullRepoUrl = $customRepository;
|
||||
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
||||
|
||||
if ($pull_request_id !== 0) {
|
||||
if ($git_type === 'gitlab') {
|
||||
$branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'github') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||
}
|
||||
}
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
} else {
|
||||
@@ -934,34 +605,6 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
}
|
||||
public function prepareHelperImage(string $deploymentUuid)
|
||||
{
|
||||
$basedir = $this->generateBaseDir($deploymentUuid);
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$server = data_get($this, 'destination.server');
|
||||
$network = data_get($this, 'destination.network');
|
||||
|
||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
||||
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||
"hidden" => true,
|
||||
]);
|
||||
}
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
function parseCompose(int $pull_request_id = 0)
|
||||
{
|
||||
if ($this->docker_compose_raw) {
|
||||
@@ -1002,7 +645,7 @@ class Application extends BaseModel
|
||||
if (!$composeFileContent) {
|
||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||
$this->save();
|
||||
throw new \Exception("Could not load base compose file from $workdir$composeFile");
|
||||
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
|
||||
} else {
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
@@ -1072,4 +715,12 @@ class Application extends BaseModel
|
||||
$this->save();
|
||||
return $customLabels;
|
||||
}
|
||||
public function fqdns(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->fqdn)
|
||||
? []
|
||||
: explode(',', $this->fqdn),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ class Environment extends Model
|
||||
$this->services()->count() == 0;
|
||||
}
|
||||
|
||||
public function environment_variables() {
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasMany(Application::class);
|
||||
|
||||
@@ -15,6 +15,7 @@ class EnvironmentVariable extends Model
|
||||
'value' => 'encrypted',
|
||||
'is_build_time' => 'boolean',
|
||||
];
|
||||
protected $appends = ['real_value', 'is_shared'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -48,24 +49,78 @@ class EnvironmentVariable extends Model
|
||||
set: fn (?string $value = null) => $this->set_environment_variables($value),
|
||||
);
|
||||
}
|
||||
|
||||
private function get_environment_variables(?string $environment_variable = null): string|null
|
||||
public function realValue(): Attribute
|
||||
{
|
||||
$resource = null;
|
||||
if ($this->application_id) {
|
||||
$resource = Application::find($this->application_id);
|
||||
} else if ($this->service_id) {
|
||||
$resource = Service::find($this->service_id);
|
||||
} else if ($this->database_id) {
|
||||
$resource = StandalonePostgresql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMysql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneRedis::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMongodb::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMariadb::find($this->database_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Attribute::make(
|
||||
get: function () use ($resource) {
|
||||
return $this->get_real_environment_variables($this->value, $resource);
|
||||
}
|
||||
);
|
||||
}
|
||||
protected function isShared(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$type = str($this->value)->after("{{")->before(".")->value;
|
||||
if (str($this->value)->startsWith('{{' . $type) && str($this->value)->endsWith('}}')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
||||
{
|
||||
// $team_id = currentTeam()->id;
|
||||
if (!$environment_variable) {
|
||||
return null;
|
||||
}
|
||||
$environment_variable = trim(decrypt($environment_variable));
|
||||
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
|
||||
$variable = Str::after($environment_variable, 'global.');
|
||||
$environment_variable = trim($environment_variable);
|
||||
$type = str($environment_variable)->after("{{")->before(".")->value;
|
||||
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
|
||||
$variable = Str::after($environment_variable, "{$type}.");
|
||||
$variable = Str::before($variable, '}}');
|
||||
$variable = Str::of($variable)->trim()->value;
|
||||
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
||||
ray('global env variable');
|
||||
return $environment_variable;
|
||||
if ($type === 'environment') {
|
||||
$id = $resource->environment->id;
|
||||
} else if ($type === 'project') {
|
||||
$id = $resource->environment->project->id;
|
||||
} else {
|
||||
$id = $resource->team()->id;
|
||||
}
|
||||
$environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
|
||||
if ($environment_variable_found) {
|
||||
return $environment_variable_found->value;
|
||||
}
|
||||
}
|
||||
return $environment_variable;
|
||||
}
|
||||
private function get_environment_variables(?string $environment_variable = null): string|null
|
||||
{
|
||||
if (!$environment_variable) {
|
||||
return null;
|
||||
}
|
||||
return trim(decrypt($environment_variable));
|
||||
}
|
||||
|
||||
private function set_environment_variables(?string $environment_variable = null): string|null
|
||||
{
|
||||
@@ -73,6 +128,10 @@ class EnvironmentVariable extends Model
|
||||
return null;
|
||||
}
|
||||
$environment_variable = trim($environment_variable);
|
||||
$type = str($environment_variable)->after("{{")->before(".")->value;
|
||||
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
|
||||
return encrypt((string) str($environment_variable)->replace(' ', ''));
|
||||
}
|
||||
return encrypt($environment_variable);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ class Project extends BaseModel
|
||||
$project->settings()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function environment_variables() {
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function environments()
|
||||
{
|
||||
return $this->hasMany(Environment::class);
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -335,20 +331,6 @@ class Server extends BaseModel
|
||||
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
||||
return false;
|
||||
}
|
||||
// foreach ($this->applications() as $application) {
|
||||
// if (data_get($application, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// ray($this->services()->get());
|
||||
|
||||
// if ($this->id === 0) {
|
||||
// $settings = InstanceSettings::get();
|
||||
// if (data_get($settings, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
public function isFunctional()
|
||||
@@ -398,6 +380,8 @@ class Server extends BaseModel
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
$server = Server::find($this->id);
|
||||
if (!$server) {
|
||||
return false;
|
||||
@@ -481,153 +465,4 @@ class Server extends BaseModel
|
||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||
}
|
||||
}
|
||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
||||
{
|
||||
static::$batch_counter++;
|
||||
foreach ($commands as $command) {
|
||||
$realCommand = data_get($command, 'command');
|
||||
if (is_null($realCommand)) {
|
||||
throw new \RuntimeException('Command is not set');
|
||||
}
|
||||
$hidden = data_get($command, 'hidden', false);
|
||||
$ignoreErrors = data_get($command, 'ignoreErrors', false);
|
||||
$customOutputType = data_get($command, 'customOutputType');
|
||||
$name = data_get($command, 'name');
|
||||
$remoteCommand = generateSshCommand($this, $realCommand);
|
||||
|
||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remoteCommand, function (string $type, string $output) use ($realCommand, $hidden, $customOutputType, $loggingModel, $name) {
|
||||
$output = str($output)->trim();
|
||||
if ($output->startsWith('╔')) {
|
||||
$output = "\n" . $output;
|
||||
}
|
||||
$newLogEntry = [
|
||||
'command' => remove_iip($realCommand),
|
||||
'output' => remove_iip($output),
|
||||
'type' => $customOutputType ?? $type === 'err' ? 'stderr' : 'stdout',
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => $hidden,
|
||||
'batch' => static::$batch_counter,
|
||||
];
|
||||
if ($loggingModel) {
|
||||
if (!$loggingModel->logs) {
|
||||
$newLogEntry['order'] = 1;
|
||||
} else {
|
||||
$previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
||||
}
|
||||
if ($name) {
|
||||
$newLogEntry['name'] = $name;
|
||||
}
|
||||
|
||||
$previousLogs[] = $newLogEntry;
|
||||
$loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
||||
$loggingModel->save();
|
||||
}
|
||||
});
|
||||
if ($loggingModel) {
|
||||
$loggingModel->update([
|
||||
'current_process_id' => $process->id(),
|
||||
]);
|
||||
}
|
||||
$processResult = $process->wait();
|
||||
if ($processResult->exitCode() !== 0) {
|
||||
if (!$ignoreErrors) {
|
||||
if ($loggingModel) {
|
||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$loggingModel->status = $status;
|
||||
$loggingModel->save();
|
||||
}
|
||||
throw new \RuntimeException($processResult->errorOutput());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function stopApplicationRelatedRunningContainers(string $applicationId, string $containerName)
|
||||
{
|
||||
$containers = getCurrentApplicationContainerStatus($this, $applicationId, 0);
|
||||
$containers = $containers->filter(function ($container) use ($containerName) {
|
||||
return data_get($container, 'Names') !== $containerName;
|
||||
});
|
||||
$containers->each(function ($container) {
|
||||
$removableContainer = data_get($container, 'Names');
|
||||
$this->server->executeRemoteCommand(
|
||||
commands: collect([
|
||||
'command' => "docker rm -f $removableContainer >/dev/null 2>&1",
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $this->deploymentQueueEntry
|
||||
);
|
||||
});
|
||||
}
|
||||
public function getHostIPMappings($network)
|
||||
{
|
||||
$addHosts = null;
|
||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this);
|
||||
if (!is_null($allContainers)) {
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
$ips = collect([]);
|
||||
if (count($allContainers) > 0) {
|
||||
$allContainers = $allContainers[0];
|
||||
foreach ($allContainers as $container) {
|
||||
$containerName = data_get($container, 'Name');
|
||||
if ($containerName === 'coolify-proxy') {
|
||||
continue;
|
||||
}
|
||||
$containerIp = data_get($container, 'IPv4Address');
|
||||
if ($containerName && $containerIp) {
|
||||
$containerIp = str($containerIp)->before('/');
|
||||
$ips->put($containerName, $containerIp->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
$addHosts = $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
}
|
||||
return $addHosts;
|
||||
}
|
||||
public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment)
|
||||
{
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"name" => "local_image_found",
|
||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
||||
"hidden" => true,
|
||||
]
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
if (str($deployment->getOutput('local_image_found'))->isEmpty()) {
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"command" => "docker pull {$imageName} 2>/dev/null",
|
||||
"ignoreErrors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
[
|
||||
"name" => "local_image_found",
|
||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
||||
"hidden" => true,
|
||||
]
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
}
|
||||
public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment)
|
||||
{
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"),
|
||||
"ignoreErrors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Service\DeleteService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -17,6 +16,10 @@ class Service extends BaseModel
|
||||
{
|
||||
return 'service';
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function extraFields()
|
||||
{
|
||||
$fields = collect([]);
|
||||
@@ -423,7 +426,7 @@ class Service extends BaseModel
|
||||
$envs = $this->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
||||
}
|
||||
if ($envs->count() === 0) {
|
||||
$commands[] = "touch .env";
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServiceApplication extends BaseModel
|
||||
{
|
||||
@@ -15,6 +14,7 @@ class ServiceApplication extends BaseModel
|
||||
protected static function booted()
|
||||
{
|
||||
static::deleting(function ($service) {
|
||||
$service->update(['fqdn' => null]);
|
||||
$service->persistentStorages()->delete();
|
||||
$service->fileStorages()->delete();
|
||||
});
|
||||
@@ -55,7 +55,6 @@ class ServiceApplication extends BaseModel
|
||||
get: fn () => is_null($this->fqdn)
|
||||
? []
|
||||
: explode(',', $this->fqdn),
|
||||
|
||||
);
|
||||
}
|
||||
public function getFilesFromServer(bool $isInit = false)
|
||||
|
||||
14
app/Models/SharedEnvironmentVariable.php
Normal file
14
app/Models/SharedEnvironmentVariable.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SharedEnvironmentVariable extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'key' => 'string',
|
||||
'value' => 'encrypted',
|
||||
];
|
||||
}
|
||||
@@ -42,6 +42,10 @@ class StandaloneMariadb extends BaseModel
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
|
||||
@@ -45,6 +45,10 @@ class StandaloneMongodb extends BaseModel
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
|
||||
@@ -42,6 +42,10 @@ class StandaloneMysql extends BaseModel
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
|
||||
@@ -74,7 +74,10 @@ class StandalonePostgresql extends BaseModel
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-postgresql';
|
||||
|
||||
@@ -37,6 +37,10 @@ class StandaloneRedis extends BaseModel
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
|
||||
@@ -70,7 +70,9 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function environment_variables() {
|
||||
return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id');
|
||||
}
|
||||
public function members()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role');
|
||||
|
||||
@@ -38,7 +38,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||
}
|
||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
||||
@@ -38,7 +38,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||
}
|
||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
||||
@@ -31,7 +31,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||
}
|
||||
$this->resource_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->resource->uuid}";
|
||||
$this->resource_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->resource->uuid}";
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
|
||||
@@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
|
||||
$message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "Coolify: A resource has been stopped unexpectedly on {$this->server->name}";
|
||||
$message = "Coolify: A resource ($this->name) has been stopped unexpectedly on {$this->server->name}";
|
||||
$payload = [
|
||||
"message" => $message,
|
||||
];
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
trait ExecuteRemoteCommandNew
|
||||
{
|
||||
public static $batch_counter = 0;
|
||||
public function executeRemoteCommand(Server $server, $logModel, $commands)
|
||||
{
|
||||
static::$batch_counter++;
|
||||
if ($commands instanceof Collection) {
|
||||
$commandsText = $commands;
|
||||
} else {
|
||||
$commandsText = collect($commands);
|
||||
}
|
||||
$commandsText->each(function ($singleCommand) use ($server, $logModel) {
|
||||
$command = data_get($singleCommand, 'command') ?? $singleCommand[0] ?? null;
|
||||
if ($command === null) {
|
||||
throw new \RuntimeException('Command is not set');
|
||||
}
|
||||
$hidden = data_get($singleCommand, 'hidden', false);
|
||||
$customType = data_get($singleCommand, 'type');
|
||||
$ignoreErrors = data_get($singleCommand, 'ignore_errors', false);
|
||||
$save = data_get($singleCommand, 'save');
|
||||
|
||||
$remote_command = generateSshCommand($server, $command);
|
||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $logModel, $save) {
|
||||
$output = str($output)->trim();
|
||||
if ($output->startsWith('╔')) {
|
||||
$output = "\n" . $output;
|
||||
}
|
||||
$newLogEntry = [
|
||||
'command' => remove_iip($command),
|
||||
'output' => remove_iip($output),
|
||||
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => $hidden,
|
||||
'batch' => static::$batch_counter,
|
||||
];
|
||||
|
||||
if (!$logModel->logs) {
|
||||
$newLogEntry['order'] = 1;
|
||||
} else {
|
||||
$previousLogs = json_decode($logModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
||||
}
|
||||
|
||||
$previousLogs[] = $newLogEntry;
|
||||
$logModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
||||
$logModel->save();
|
||||
|
||||
if ($save) {
|
||||
$this->remoteCommandOutputs[$save] = str($output)->trim();
|
||||
}
|
||||
});
|
||||
$logModel->update([
|
||||
'current_process_id' => $process->id(),
|
||||
]);
|
||||
|
||||
$processResult = $process->wait();
|
||||
if ($processResult->exitCode() !== 0) {
|
||||
if (!$ignoreErrors) {
|
||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$logModel->status = $status;
|
||||
$logModel->save();
|
||||
throw new \RuntimeException($processResult->errorOutput());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class Checkbox extends Component
|
||||
public string|null $helper = null,
|
||||
public string|bool $instantSave = false,
|
||||
public bool $disabled = false,
|
||||
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700"
|
||||
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700",
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class Select extends Component
|
||||
public string|null $label = null,
|
||||
public string|null $helper = null,
|
||||
public bool $required = false,
|
||||
public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
|
||||
public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeployDockerImageJob;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Jobs\ApplicationDeploymentNewJob;
|
||||
use App\Jobs\ApplicationDeploySimpleDockerfileJob;
|
||||
use App\Jobs\ApplicationRestartJob;
|
||||
use App\Jobs\MultipleApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $is_new_deployment = false)
|
||||
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null)
|
||||
{
|
||||
$application_id = $application->id;
|
||||
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
|
||||
$deployment_url = $deployment_link->getPath();
|
||||
$server_id = $application->destination->server->id;
|
||||
$server_name = $application->destination->server->name;
|
||||
$deployment = ApplicationDeploymentQueue::create([
|
||||
'application_id' => $application_id,
|
||||
'application_name' => $application->name,
|
||||
'server_id' => $server_id,
|
||||
'server_name' => $server_name,
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'deployment_url' => $deployment_url,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'force_rebuild' => $force_rebuild,
|
||||
'is_webhook' => $is_webhook,
|
||||
@@ -24,305 +27,42 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
||||
'commit' => $commit,
|
||||
'git_type' => $git_type
|
||||
]);
|
||||
$queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at');
|
||||
$running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at');
|
||||
ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild);
|
||||
if ($queued_deployments->count() > 1) {
|
||||
$queued_deployments = $queued_deployments->skip(1);
|
||||
$queued_deployments->each(function ($queued_deployment, $key) {
|
||||
$queued_deployment->status = 'cancelled by system';
|
||||
$queued_deployment->save();
|
||||
});
|
||||
}
|
||||
if ($running_deployments->count() > 0) {
|
||||
return;
|
||||
}
|
||||
if ($is_new_deployment) {
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
deployment: $deployment,
|
||||
application: Application::find($application_id)
|
||||
));
|
||||
} else {
|
||||
|
||||
if (next_queuable($server_id, $application_id)) {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
function queue_next_deployment(Application $application, bool $isNew = false)
|
||||
function queue_next_deployment(Application $application)
|
||||
{
|
||||
$next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first();
|
||||
$server_id = $application->destination->server_id;
|
||||
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();
|
||||
if ($next_found) {
|
||||
if ($isNew) {
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
deployment: $next_found,
|
||||
application: $application
|
||||
));
|
||||
} else {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
));
|
||||
}
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
));
|
||||
}
|
||||
}
|
||||
// Deployment things
|
||||
function generateHostIpMapping(Server $server, string $network)
|
||||
{
|
||||
// Generate custom host<->ip hostnames
|
||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $server);
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
$ips = collect([]);
|
||||
if (count($allContainers) > 0) {
|
||||
$allContainers = $allContainers[0];
|
||||
foreach ($allContainers as $container) {
|
||||
$containerName = data_get($container, 'Name');
|
||||
if ($containerName === 'coolify-proxy') {
|
||||
continue;
|
||||
}
|
||||
$containerIp = data_get($container, 'IPv4Address');
|
||||
if ($containerName && $containerIp) {
|
||||
$containerIp = str($containerIp)->before('/');
|
||||
$ips->put($containerName, $containerIp->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
}
|
||||
|
||||
function generateBaseDir(string $deplyomentUuid)
|
||||
function next_queuable(string $server_id, string $application_id): bool
|
||||
{
|
||||
return "/artifacts/$deplyomentUuid";
|
||||
}
|
||||
function generateWorkdir(string $deplyomentUuid, Application $application)
|
||||
{
|
||||
return generateBaseDir($deplyomentUuid) . rtrim($application->base_directory, '/');
|
||||
}
|
||||
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
|
||||
$same_application_deployments = $deployments->where('application_id', $application_id);
|
||||
$in_progress = $same_application_deployments->filter(function ($value, $key) {
|
||||
return $value->status === 'in_progress';
|
||||
});
|
||||
if ($in_progress->count() > 0) {
|
||||
return false;
|
||||
}
|
||||
$server = Server::find($server_id);
|
||||
$concurrent_builds = $server->settings->concurrent_builds;
|
||||
|
||||
function prepareHelperContainer(Server $server, string $network, string $deploymentUuid)
|
||||
{
|
||||
$basedir = generateBaseDir($deploymentUuid);
|
||||
$helperImage = config('coolify.helper_image');
|
||||
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
|
||||
|
||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
||||
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||
"hidden" => true,
|
||||
]);
|
||||
if ($deployments->count() > $concurrent_builds) {
|
||||
return false;
|
||||
}
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
|
||||
function generateComposeFile(string $deploymentUuid, Server $server, string $network, Application $application, string $containerName, string $imageName, ?ApplicationPreview $preview = null, int $pullRequestId = 0)
|
||||
{
|
||||
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
||||
$persistent_storages = generateLocalPersistentVolumes($application, $pullRequestId);
|
||||
$volume_names = generateLocalPersistentVolumesOnlyVolumeNames($application, $pullRequestId);
|
||||
$environment_variables = generateEnvironmentVariables($application, $ports, $pullRequestId);
|
||||
|
||||
if (data_get($application, 'custom_labels')) {
|
||||
$labels = collect(str($application->custom_labels)->explode(','));
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !str($value)->startsWith('coolify.');
|
||||
});
|
||||
$application->custom_labels = $labels->implode(',');
|
||||
$application->save();
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($application, $preview));
|
||||
}
|
||||
if ($pullRequestId !== 0) {
|
||||
$labels = collect(generateLabelsApplication($application, $preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($application->id, $application->uuid, 0))->toArray();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$containerName => [
|
||||
'image' => $imageName,
|
||||
'container_name' => $containerName,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => $labels,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$network,
|
||||
],
|
||||
'mem_limit' => $application->limits_memory,
|
||||
'memswap_limit' => $application->limits_memory_swap,
|
||||
'mem_swappiness' => $application->limits_memory_swappiness,
|
||||
'mem_reservation' => $application->limits_memory_reservation,
|
||||
'cpus' => (int) $application->limits_cpus,
|
||||
'cpu_shares' => $application->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$network => [
|
||||
'external' => true,
|
||||
'name' => $network,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($application->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$containerName}.cpuset", $application->limits_cpuset);
|
||||
}
|
||||
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$containerName]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($application->settings->is_gpu_enabled) {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'] = [
|
||||
[
|
||||
'driver' => data_get($application, 'settings.gpu_driver', 'nvidia'),
|
||||
'capabilities' => ['gpu'],
|
||||
'options' => data_get($application, 'settings.gpu_options', [])
|
||||
]
|
||||
];
|
||||
if (data_get($application, 'settings.gpu_count')) {
|
||||
$count = data_get($application, 'settings.gpu_count');
|
||||
if ($count === 'all') {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
||||
} else {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
||||
}
|
||||
} else if (data_get($application, 'settings.gpu_device_ids')) {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($application, 'settings.gpu_device_ids');
|
||||
}
|
||||
}
|
||||
if ($application->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $containerName . '.healthcheck');
|
||||
}
|
||||
if (count($application->ports_mappings_array) > 0 && $pullRequestId === 0) {
|
||||
$docker_compose['services'][$containerName]['ports'] = $application->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$containerName]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$commands = collect([]);
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "echo '{$docker_compose_base64}' | base64 -d > {$workDir}/docker-compose.yml"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
function generateLocalPersistentVolumes(Application $application, int $pullRequestId = 0)
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($application->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
if ($pullRequestId !== 0) {
|
||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
function generateLocalPersistentVolumesOnlyVolumeNames(Application $application, int $pullRequestId = 0)
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($application->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
|
||||
if ($pullRequestId !== 0) {
|
||||
$name = $name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
function generateEnvironmentVariables(Application $application, $ports, int $pullRequestId = 0)
|
||||
{
|
||||
$environment_variables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($pullRequestId === 0) {
|
||||
// ray($this->application->runtime_environment_variables)->green();
|
||||
foreach ($application->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($application->nixpacks_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||
foreach ($application->runtime_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($application->nixpacks_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('PORT'))->isEmpty()) {
|
||||
$environment_variables->push("PORT={$ports[0]}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
function startNewApplication(Application $application, string $deploymentUuid, ApplicationDeploymentQueue $loggingModel)
|
||||
{
|
||||
$commands = collect([]);
|
||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
||||
if ($application->build_pack === 'dockerimage') {
|
||||
$loggingModel->addLogEntry('Pulling latest images from the registry.');
|
||||
$commands->push(
|
||||
[
|
||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} pull"),
|
||||
"hidden" => true
|
||||
],
|
||||
[
|
||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
||||
"hidden" => true
|
||||
],
|
||||
);
|
||||
} else {
|
||||
$commands->push(
|
||||
[
|
||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
||||
"hidden" => true
|
||||
],
|
||||
);
|
||||
}
|
||||
return $commands;
|
||||
}
|
||||
function removeOldDeployment(string $containerName)
|
||||
{
|
||||
$commands = collect([]);
|
||||
$commands->push(
|
||||
["docker rm -f $containerName >/dev/null 2>&1"],
|
||||
);
|
||||
return $commands;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
@@ -323,3 +321,89 @@ function isDatabaseImage(?string $image = null)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function convert_docker_run_to_compose(?string $custom_docker_run_options = null)
|
||||
{
|
||||
$options = [];
|
||||
$compose_options = collect([]);
|
||||
preg_match_all('/(--\w+(?:-\w+)*)(?:\s|=)?([^\s-]+)?/', $custom_docker_run_options, $matches, PREG_SET_ORDER);
|
||||
$list_options = collect([
|
||||
'--cap-add',
|
||||
'--cap-drop',
|
||||
'--security-opt',
|
||||
'--sysctl',
|
||||
'--ulimit',
|
||||
'--device'
|
||||
]);
|
||||
$mapping = collect([
|
||||
'--cap-add' => 'cap_add',
|
||||
'--cap-drop' => 'cap_drop',
|
||||
'--security-opt' => 'security_opt',
|
||||
'--sysctl' => 'sysctls',
|
||||
'--device' => 'devices',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--init' => 'init',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--privileged' => 'privileged',
|
||||
]);
|
||||
foreach ($matches as $match) {
|
||||
$option = $match[1];
|
||||
$value = isset($match[2]) && $match[2] !== '' ? $match[2] : true;
|
||||
if ($list_options->contains($option)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
if (array_key_exists($option, $options)) {
|
||||
if (is_array($options[$option])) {
|
||||
$options[$option][] = $value;
|
||||
} else {
|
||||
$options[$option] = [$options[$option], $value];
|
||||
}
|
||||
} else {
|
||||
$options[$option] = $value;
|
||||
}
|
||||
}
|
||||
$options = collect($options);
|
||||
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||
foreach ($options as $option => $value) {
|
||||
if (!data_get($mapping, $option)) {
|
||||
continue;
|
||||
}
|
||||
if ($option === '--ulimit') {
|
||||
$ulimits = collect([]);
|
||||
collect($value)->map(function ($ulimit) use ($ulimits){
|
||||
$ulimit = explode('=', $ulimit);
|
||||
$type = $ulimit[0];
|
||||
$limits = explode(':', $ulimit[1]);
|
||||
if (count($limits) == 2) {
|
||||
$soft_limit = $limits[0];
|
||||
$hard_limit = $limits[1];
|
||||
$ulimits->put($type, [
|
||||
'soft' => $soft_limit,
|
||||
'hard' => $hard_limit
|
||||
]);
|
||||
|
||||
} else {
|
||||
$soft_limit = $ulimit[1];
|
||||
$ulimits->put($type, [
|
||||
'soft' => $soft_limit,
|
||||
]);
|
||||
}
|
||||
});
|
||||
$compose_options->put($mapping[$option], $ulimits);
|
||||
} else {
|
||||
if ($list_options->contains($option)) {
|
||||
if ($compose_options->has($mapping[$option])) {
|
||||
$compose_options->put($mapping[$option], $options->get($mapping[$option]) . ',' . $value);
|
||||
} else {
|
||||
$compose_options->put($mapping[$option], $value);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
$compose_options->put($mapping[$option], $value);
|
||||
continue;
|
||||
}
|
||||
$compose_options->forget($option);
|
||||
}
|
||||
}
|
||||
return $compose_options->toArray();
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
|
||||
}
|
||||
$json = $response->json();
|
||||
if ($response->failed() && $throwError) {
|
||||
ray($json);
|
||||
throw new \Exception("Failed to get data from {$source->name} with error:<br><br>" . $json['message'] . "<br><br>Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC');
|
||||
}
|
||||
return [
|
||||
|
||||
@@ -108,7 +108,7 @@ function instant_scp(string $source, string $dest, Server $server, $throwError =
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
||||
function generateSshCommand(Server $server, string $command)
|
||||
{
|
||||
$user = $server->user;
|
||||
$port = $server->port;
|
||||
@@ -120,7 +120,7 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
||||
$delimiter = 'EOF-COOLIFY-SSH';
|
||||
$ssh_command = "timeout $timeout ssh ";
|
||||
|
||||
if ($isMux && config('coolify.mux_enabled')) {
|
||||
if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) {
|
||||
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
|
||||
}
|
||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||
|
||||
@@ -22,6 +22,7 @@ use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use App\Notifications\Internal\GeneralNotification;
|
||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use Illuminate\Database\UniqueConstraintViolationException;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -107,6 +108,12 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
||||
}
|
||||
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
||||
}
|
||||
if ($error instanceof UniqueConstraintViolationException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "A resource with the same name already exists.");
|
||||
}
|
||||
return "A resource with the same name already exists.";
|
||||
}
|
||||
|
||||
if ($error instanceof Throwable) {
|
||||
$message = $error->getMessage();
|
||||
@@ -1036,6 +1043,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if (!data_get($service, 'restart')) {
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
}
|
||||
if (data_get($service, 'restart') === 'no') {
|
||||
$savedService->update(['exclude_from_status' => true]);
|
||||
}
|
||||
data_set($service, 'container_name', $containerName);
|
||||
data_forget($service, 'volumes.*.content');
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
@@ -1657,3 +1667,33 @@ function ip_match($ip, $cidrs, &$match = null)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function check_fqdn_usage(ServiceApplication|Application $own_resource)
|
||||
{
|
||||
$domains = collect($own_resource->fqdns)->map(function ($domain) {
|
||||
return Url::fromString($domain)->getHost();
|
||||
});
|
||||
$apps = Application::all();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
$naked_domain = Url::fromString($domain)->getHost();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if ($app->uuid !== $own_resource->uuid ) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$apps = ServiceApplication::all();
|
||||
foreach ($apps as $app) {
|
||||
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||
foreach ($list_of_domains as $domain) {
|
||||
$naked_domain = Url::fromString($domain)->getHost();
|
||||
if ($domains->contains($naked_domain)) {
|
||||
if ($app->uuid !== $own_resource->uuid) {
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"league/flysystem-sftp-v3": "^3.0",
|
||||
"livewire/livewire": "^3.0",
|
||||
"lorisleiva/laravel-actions": "^2.7",
|
||||
"masmerise/livewire-toaster": "^2.0",
|
||||
"nubs/random-name-generator": "^2.2",
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"poliander/cron": "^3.0",
|
||||
|
||||
567
composer.lock
generated
567
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'docs' => 'https://coolify.io/docs/contact',
|
||||
'docs' => 'https://coolify.io/docs/',
|
||||
'contact' => 'https://coolify.io/docs/contact',
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
'waitlist' => env('WAITLIST', false),
|
||||
'license_url' => 'https://licenses.coollabs.io',
|
||||
'mux_enabled' => env('MUX_ENABLED', true),
|
||||
'dev_webhook' => env('SERVEO_URL'),
|
||||
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
|
||||
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
|
||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.197',
|
||||
'release' => '4.0.0-beta.203',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.197';
|
||||
return '4.0.0-beta.203';
|
||||
|
||||
@@ -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('services', function (Blueprint $table) {
|
||||
$table->boolean('connect_to_docker_network')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->dropColumn('connect_to_docker_network');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('applications', function (Blueprint $table) {
|
||||
$table->string('manual_webhook_secret_bitbucket')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('manual_webhook_secret_bitbucket');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('shared_environment_variables', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('key');
|
||||
$table->string('value')->nullable();
|
||||
$table->boolean('is_shown_once')->default(false);
|
||||
$table->enum('type', ['team', 'project', 'environment'])->default('team');
|
||||
|
||||
$table->foreignId('team_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('project_id')->nullable()->constrained()->onDelete('cascade');
|
||||
$table->foreignId('environment_id')->nullable()->constrained()->onDelete('cascade');
|
||||
$table->unique(['key', 'project_id', 'team_id']);
|
||||
$table->unique(['key', 'environment_id', 'team_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('shared_environment_variables');
|
||||
}
|
||||
};
|
||||
@@ -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->integer('concurrent_builds')->default(2);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('concurrent_builds');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->integer('server_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->dropColumn('server_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->string('application_name')->nullable();
|
||||
$table->string('server_name')->nullable();
|
||||
$table->string('deployment_url')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->dropColumn('application_name');
|
||||
$table->dropColumn('server_name');
|
||||
$table->dropColumn('deployment_url');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('shared_environment_variables', function (Blueprint $table) {
|
||||
$table->text('value')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shared_environment_variables', function (Blueprint $table) {
|
||||
$table->string('value')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('applications', function (Blueprint $table) {
|
||||
$table->string('custom_docker_run_options')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('custom_docker_run_options');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder
|
||||
ProjectSeeder::class,
|
||||
ProjectSettingSeeder::class,
|
||||
EnvironmentSeeder::class,
|
||||
TeamEnvironmentVariableSeeder::class,
|
||||
StandaloneDockerSeeder::class,
|
||||
SwarmDockerSeeder::class,
|
||||
KubernetesSeeder::class,
|
||||
|
||||
@@ -14,7 +14,6 @@ use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -67,7 +66,8 @@ class ProductionSeeder extends Seeder
|
||||
]);
|
||||
}
|
||||
|
||||
if (!isCloud()) {
|
||||
if (!isCloud() && config('coolify.is_windows_docker_desktop') == false) {
|
||||
echo "Checking localhost key.\n";
|
||||
// Save SSH Keys for the Coolify Host
|
||||
$coolify_key_name = "id.root@host.docker.internal";
|
||||
$coolify_key = Storage::disk('ssh-keys')->get("{$coolify_key_name}");
|
||||
@@ -123,6 +123,59 @@ class ProductionSeeder extends Seeder
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (config('coolify.is_windows_docker_desktop')) {
|
||||
PrivateKey::updateOrCreate(
|
||||
[
|
||||
'id' => 0,
|
||||
'team_id' => 0,
|
||||
],
|
||||
[
|
||||
"name" => "Testing-host",
|
||||
"description" => "This is a a docker container with SSH access",
|
||||
"private_key" => "-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevAAAAJi/QySHv0Mk
|
||||
hwAAAAtzc2gtZWQyNTUxOQAAACBbhpqHhqv6aI67Mj9abM3DVbmcfYhZAhC7ca4d9UCevA
|
||||
AAAECBQw4jg1WRT2IGHMncCiZhURCts2s24HoDS0thHnnRKVuGmoeGq/pojrsyP1pszcNV
|
||||
uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
"
|
||||
]
|
||||
);
|
||||
if (Server::find(0) == null) {
|
||||
$server_details = [
|
||||
'id' => 0,
|
||||
'uuid' => 'coolify-testing-host',
|
||||
'name' => "localhost",
|
||||
'description' => "This is the server where Coolify is running on. Don't delete this!",
|
||||
'user' => 'root',
|
||||
'ip' => "coolify-testing-host",
|
||||
'team_id' => 0,
|
||||
'private_key_id' => 0
|
||||
];
|
||||
$server_details['proxy'] = ServerMetadata::from([
|
||||
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||
'status' => ProxyStatus::EXITED->value
|
||||
]);
|
||||
$server = Server::create($server_details);
|
||||
$server->settings->is_reachable = true;
|
||||
$server->settings->is_usable = true;
|
||||
$server->settings->save();
|
||||
} else {
|
||||
$server = Server::find(0);
|
||||
$server->settings->is_reachable = true;
|
||||
$server->settings->is_usable = true;
|
||||
$server->settings->save();
|
||||
}
|
||||
if (StandaloneDocker::find(0) == null) {
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'localhost-coolify',
|
||||
'network' => 'coolify',
|
||||
'server_id' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
36
database/seeders/SharedEnvironmentVariableSeeder.php
Normal file
36
database/seeders/SharedEnvironmentVariableSeeder.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SharedEnvironmentVariableSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
SharedEnvironmentVariable::create([
|
||||
'key' => 'NODE_ENV',
|
||||
'value' => 'team_env',
|
||||
'type' => 'team',
|
||||
'team_id' => 0,
|
||||
]);
|
||||
SharedEnvironmentVariable::create([
|
||||
'key' => 'NODE_ENV',
|
||||
'value' => 'env_env',
|
||||
'type' => 'environment',
|
||||
'environment_id' => 1,
|
||||
'team_id' => 0,
|
||||
]);
|
||||
SharedEnvironmentVariable::create([
|
||||
'key' => 'NODE_ENV',
|
||||
'value' => 'project_env',
|
||||
'type' => 'project',
|
||||
'project_id' => 1,
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
version: "3.8"
|
||||
|
||||
x-testing-host: &testing-host-base
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: ./docker/testing-host
|
||||
networks:
|
||||
- coolify
|
||||
init: true
|
||||
|
||||
services:
|
||||
coolify:
|
||||
build:
|
||||
@@ -30,6 +22,7 @@ services:
|
||||
volumes:
|
||||
- .:/var/www/html/:cached
|
||||
postgres:
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "${FORWARD_DB_PORT:-5432}:5432"
|
||||
env_file:
|
||||
@@ -43,6 +36,7 @@ services:
|
||||
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||
# - coolify-pg-data-dev:/var/lib/postgresql/data
|
||||
redis:
|
||||
pull_policy: always
|
||||
ports:
|
||||
- "${FORWARD_REDIS_PORT:-6379}:6379"
|
||||
env_file:
|
||||
@@ -62,6 +56,7 @@ services:
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
vite:
|
||||
image: node:20
|
||||
pull_policy: always
|
||||
working_dir: /var/www/html
|
||||
# environment:
|
||||
# VITE_PUSHER_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
@@ -73,7 +68,9 @@ services:
|
||||
networks:
|
||||
- coolify
|
||||
testing-host:
|
||||
<<: *testing-host-base
|
||||
image: "ghcr.io/coollabsio/coolify-testing-host:latest"
|
||||
pull_policy: always
|
||||
init: true
|
||||
container_name: coolify-testing-host
|
||||
volumes:
|
||||
- /:/host
|
||||
@@ -83,6 +80,7 @@ services:
|
||||
- coolify
|
||||
mailpit:
|
||||
image: "axllent/mailpit:latest"
|
||||
pull_policy: always
|
||||
container_name: coolify-mail
|
||||
ports:
|
||||
- "${FORWARD_MAILPIT_PORT:-1025}:1025"
|
||||
@@ -91,6 +89,7 @@ services:
|
||||
- coolify
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
pull_policy: always
|
||||
container_name: coolify-minio
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
coolify:
|
||||
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-4.0.0-beta.190}"
|
||||
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:latest}"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: /data/coolify/source/.env
|
||||
|
||||
128
docker-compose.windows.yml
Normal file
128
docker-compose.windows.yml
Normal file
@@ -0,0 +1,128 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
coolify-testing-host:
|
||||
init: true
|
||||
image: "ghcr.io/coollabsio/coolify-testing-host:latest"
|
||||
pull_policy: always
|
||||
container_name: coolify-testing-host
|
||||
volumes:
|
||||
- //var/run/docker.sock://var/run/docker.sock
|
||||
- ./:/data/coolify
|
||||
coolify:
|
||||
image: "ghcr.io/coollabsio/coolify:latest"
|
||||
pull_policy: always
|
||||
container_name: coolify
|
||||
restart: always
|
||||
working_dir: /var/www/html
|
||||
extra_hosts:
|
||||
- 'host.docker.internal:host-gateway'
|
||||
volumes:
|
||||
- type: bind
|
||||
source: .env
|
||||
target: /var/www/html/.env
|
||||
read_only: true
|
||||
- ./ssh:/var/www/html/storage/app/ssh
|
||||
- ./applications:/var/www/html/storage/app/applications
|
||||
- ./databases:/var/www/html/storage/app/databases
|
||||
- ./services:/var/www/html/storage/app/services
|
||||
- ./backups:/var/www/html/storage/app/backups
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- APP_ID
|
||||
- APP_ENV=production
|
||||
- APP_NAME
|
||||
- APP_KEY
|
||||
- DB_PASSWORD
|
||||
- REDIS_PASSWORD
|
||||
- SSL_MODE=off
|
||||
- PHP_PM_CONTROL=dynamic
|
||||
- PHP_PM_START_SERVERS=1
|
||||
- PHP_PM_MIN_SPARE_SERVERS=1
|
||||
- PHP_PM_MAX_SPARE_SERVERS=10
|
||||
- PUSHER_APP_ID
|
||||
- PUSHER_APP_KEY
|
||||
- PUSHER_APP_SECRET
|
||||
- AUTOUPDATE=true
|
||||
- SELF_HOSTED=true
|
||||
- MUX_ENABLED=false
|
||||
- IS_WINDOWS_DOCKER_DESKTOP=true
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
expose:
|
||||
- "${APP_PORT:-8000}"
|
||||
healthcheck:
|
||||
test: curl --fail http://localhost:80/api/health || exit 1
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
pull_policy: always
|
||||
container_name: coolify-db
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- coolify-db:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: "${DB_USERNAME:-coolify}"
|
||||
POSTGRES_PASSWORD: "${DB_PASSWORD}"
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"pg_isready -U ${DB_USERNAME:-coolify}",
|
||||
"-d",
|
||||
"${DB_DATABASE:-coolify}"
|
||||
]
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
redis:
|
||||
image: redis:alpine
|
||||
pull_policy: always
|
||||
container_name: coolify-redis
|
||||
restart: always
|
||||
command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD}
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
REDIS_PASSWORD: "${REDIS_PASSWORD}"
|
||||
volumes:
|
||||
- coolify-redis:/data
|
||||
healthcheck:
|
||||
test: redis-cli ping
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
soketi:
|
||||
image: 'quay.io/soketi/soketi:1.6-16-alpine'
|
||||
pull_policy: always
|
||||
container_name: coolify-realtime
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${SOKETI_PORT:-6001}:6001"
|
||||
environment:
|
||||
SOKETI_DEBUG: "${SOKETI_DEBUG:-false}"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}"
|
||||
healthcheck:
|
||||
test: wget -qO- http://localhost:6001/ready || exit 1
|
||||
interval: 5s
|
||||
retries: 10
|
||||
timeout: 2s
|
||||
volumes:
|
||||
coolify-db:
|
||||
name: coolify-db
|
||||
coolify-redis:
|
||||
name: coolify-redis
|
||||
230
package-lock.json
generated
230
package-lock.json
generated
@@ -6,22 +6,22 @@
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"alpinejs": "3.13.3",
|
||||
"alpinejs": "3.13.5",
|
||||
"daisyui": "4.4.19",
|
||||
"ioredis": "5.3.2",
|
||||
"tailwindcss-scrollbar": "0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "4.5.1",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.6.5",
|
||||
"autoprefixer": "10.4.17",
|
||||
"axios": "1.6.7",
|
||||
"laravel-echo": "1.15.3",
|
||||
"laravel-vite-plugin": "0.8.1",
|
||||
"postcss": "8.4.33",
|
||||
"pusher-js": "8.4.0-rc2",
|
||||
"tailwindcss": "3.4.1",
|
||||
"vite": "4.5.1",
|
||||
"vue": "3.4.13"
|
||||
"vite": "4.5.2",
|
||||
"vue": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -36,9 +36,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.23.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
|
||||
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz",
|
||||
"integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -524,77 +524,77 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.13.tgz",
|
||||
"integrity": "sha512-zGUdmB3j3Irn9z51GXLJ5s0EAHxmsm5/eXl0y6MBaajMeOAaiT4+zaDoxui4Ets98dwIRr8BBaqXXHtHSfm+KA==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz",
|
||||
"integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.6",
|
||||
"@vue/shared": "3.4.13",
|
||||
"@vue/shared": "3.4.15",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.13.tgz",
|
||||
"integrity": "sha512-XSNbpr5Rs3kCfVAmBqMu/HDwOS+RL6y28ZZjDlnDUuf146pRWt2sQkwhsOYc9uu2lxjjJy2NcyOkK7MBLVEc7w==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz",
|
||||
"integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.4.13",
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/compiler-core": "3.4.15",
|
||||
"@vue/shared": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.13.tgz",
|
||||
"integrity": "sha512-SkpmQN8xIFBd5onT413DFSDdjxULJf6jmJg/t3w/DZ9I8ZzyNlLIBLO0qFLewVHyHCiAgpPZlWqSRZXYrawk3Q==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz",
|
||||
"integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.23.6",
|
||||
"@vue/compiler-core": "3.4.13",
|
||||
"@vue/compiler-dom": "3.4.13",
|
||||
"@vue/compiler-ssr": "3.4.13",
|
||||
"@vue/shared": "3.4.13",
|
||||
"@vue/compiler-core": "3.4.15",
|
||||
"@vue/compiler-dom": "3.4.15",
|
||||
"@vue/compiler-ssr": "3.4.15",
|
||||
"@vue/shared": "3.4.15",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.5",
|
||||
"postcss": "^8.4.32",
|
||||
"postcss": "^8.4.33",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.13.tgz",
|
||||
"integrity": "sha512-rwnw9SVBgD6eGKh8UucnwztieQo/R3RQrEGpE0b0cxb2xxvJeLs/fe7DoYlhEfaSyzM/qD5odkK87hl3G3oW+A==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz",
|
||||
"integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.13",
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/compiler-dom": "3.4.15",
|
||||
"@vue/shared": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
@@ -606,64 +606,64 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.13.tgz",
|
||||
"integrity": "sha512-Ov4d4At7z3goxqzSqQxdfVYEcN5HY4dM1uDYL6Hu/Es9Za9BEN602zyjWhhi2+BEki5F9NizRSvn02k/tqNWlg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz",
|
||||
"integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.4.13",
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/reactivity": "3.4.15",
|
||||
"@vue/shared": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core/node_modules/@vue/reactivity": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.13.tgz",
|
||||
"integrity": "sha512-/ZdUOrGKkGVONzVJkfDqNcn2fLMvaa5VlYx2KwTbnRbX06YZ4GJE0PVTmWzIxtBYdpSTLLXgw3pDggO+96KXzg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz",
|
||||
"integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/shared": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.13.tgz",
|
||||
"integrity": "sha512-ynde9p16eEV3u1VCxUre2e0nKzD0l3NzH0r599+bXeLT1Yhac8Atcot3iL9XNqwolxYCI89KBII+2MSVzfrz6w==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz",
|
||||
"integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "3.4.13",
|
||||
"@vue/shared": "3.4.13",
|
||||
"@vue/runtime-core": "3.4.15",
|
||||
"@vue/shared": "3.4.15",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.13.tgz",
|
||||
"integrity": "sha512-hkw+UQyDZZtSn1q30nObMfc8beVEQv2pG08nghigxGw+iOWodR+tWSuJak0mzWAHlP/xt/qLc//dG6igfgvGEA==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz",
|
||||
"integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.4.13",
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/compiler-ssr": "3.4.15",
|
||||
"@vue/shared": "3.4.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.4.13"
|
||||
"vue": "3.4.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
@@ -672,9 +672,9 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.13.3",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.3.tgz",
|
||||
"integrity": "sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==",
|
||||
"version": "3.13.5",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz",
|
||||
"integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
@@ -708,9 +708,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.16",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
|
||||
"integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
|
||||
"version": "10.4.17",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz",
|
||||
"integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -727,9 +727,9 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"browserslist": "^4.21.10",
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"fraction.js": "^4.3.6",
|
||||
"browserslist": "^4.22.2",
|
||||
"caniuse-lite": "^1.0.30001578",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
@@ -745,9 +745,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"version": "1.6.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
|
||||
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
@@ -789,9 +789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.11",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz",
|
||||
"integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==",
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -808,9 +808,9 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"electron-to-chromium": "^1.4.526",
|
||||
"node-releases": "^2.0.13",
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
@@ -829,9 +829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001539",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
|
||||
"integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
|
||||
"version": "1.0.30001580",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz",
|
||||
"integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1014,9 +1014,9 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.528",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz",
|
||||
"integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==",
|
||||
"version": "1.4.647",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz",
|
||||
"integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/entities": {
|
||||
@@ -1168,9 +1168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
|
||||
"integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
"integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -1506,9 +1506,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@@ -2041,9 +2041,9 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
@@ -2106,16 +2106,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.13.tgz",
|
||||
"integrity": "sha512-FE3UZ0p+oUZTwz+SzlH/hDFg+XsVRFvwmx0LXjdD1pRK/cO4fu5v6ltAZji4za4IBih3dV78elUK3di8v3pWIg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz",
|
||||
"integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.4.13",
|
||||
"@vue/compiler-sfc": "3.4.13",
|
||||
"@vue/runtime-dom": "3.4.13",
|
||||
"@vue/server-renderer": "3.4.13",
|
||||
"@vue/shared": "3.4.13"
|
||||
"@vue/compiler-dom": "3.4.15",
|
||||
"@vue/compiler-sfc": "3.4.15",
|
||||
"@vue/runtime-dom": "3.4.15",
|
||||
"@vue/server-renderer": "3.4.15",
|
||||
"@vue/shared": "3.4.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -2127,9 +2127,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue/node_modules/@vue/shared": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.13.tgz",
|
||||
"integrity": "sha512-56crFKLPpzk85WXX1L1c0QzPOuoapWlPVys8eMG8kkRmqdMjWUqK8KpFdE2d7BQA4CEbXwyyHPq6MpFr8H9rcg==",
|
||||
"version": "3.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz",
|
||||
"integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user