mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
102 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe143ef8a5 | ||
|
|
5fb5845e90 | ||
|
|
794cfbd8eb | ||
|
|
29ee9915f3 | ||
|
|
331d485213 | ||
|
|
665e3761c4 | ||
|
|
ac19f0e34f | ||
|
|
d7cfa0578f | ||
|
|
694169bb84 | ||
|
|
ab853cac87 | ||
|
|
51db2f797d | ||
|
|
0c90d3d0a1 | ||
|
|
51efe23690 | ||
|
|
e3ee84105c | ||
|
|
6cbd61ac6c | ||
|
|
638d0c8c99 | ||
|
|
aecc81fe9d | ||
|
|
c9a1437870 | ||
|
|
66b41b3d4c | ||
|
|
c41cfe2a2f | ||
|
|
5f2ad56529 | ||
|
|
cd842bc1b2 | ||
|
|
27b6aad53a | ||
|
|
64b58b7661 | ||
|
|
94960d96a9 | ||
|
|
2549244f97 | ||
|
|
5bfffce33b | ||
|
|
3a4f19f368 | ||
|
|
50e17ed932 | ||
|
|
a8fcd7aee4 | ||
|
|
87036cc49b | ||
|
|
e48842c6ec | ||
|
|
b9e405c497 | ||
|
|
f75effe022 | ||
|
|
a745f568f3 | ||
|
|
e2e3ad0358 | ||
|
|
ba769f5fb7 | ||
|
|
0126286731 | ||
|
|
7952202435 | ||
|
|
798acb8ee5 | ||
|
|
ef595dd4c2 | ||
|
|
70c662daf8 | ||
|
|
8ae385b9f9 | ||
|
|
802a0f7684 | ||
|
|
62c38c9859 | ||
|
|
27c36bec83 | ||
|
|
c6b8eabe10 | ||
|
|
967fca9eca | ||
|
|
40a239ddda | ||
|
|
99d07981cf | ||
|
|
b3ee6b7144 | ||
|
|
468ad7d904 | ||
|
|
f1aa97e374 | ||
|
|
3b6d3343c7 | ||
|
|
ab2f9f073f | ||
|
|
67131152cc | ||
|
|
3bda289428 | ||
|
|
fadfa0ad8e | ||
|
|
11a957c6c9 | ||
|
|
b46de99af9 | ||
|
|
03420076c9 | ||
|
|
549446abdf | ||
|
|
06ab2145ca | ||
|
|
5d088e530e | ||
|
|
123e6eddd7 | ||
|
|
2c17e431ac | ||
|
|
fe6073ba7d | ||
|
|
8a9ad04744 | ||
|
|
75d1ec4f42 | ||
|
|
a0abde8652 | ||
|
|
db13dd9304 | ||
|
|
638bcf9732 | ||
|
|
b06b465ffa | ||
|
|
02c8b9f471 | ||
|
|
1ff1664b6c | ||
|
|
52d84c5e9e | ||
|
|
e0289e2949 | ||
|
|
ff8d8371ad | ||
|
|
69343f974a | ||
|
|
2dc175be63 | ||
|
|
d93bf97919 | ||
|
|
4ea8916d53 | ||
|
|
f7fca69a23 | ||
|
|
f954ee15c3 | ||
|
|
3c54e01d87 | ||
|
|
00d708610d | ||
|
|
f3b04c1ef9 | ||
|
|
6b751f965b | ||
|
|
d3c9894479 | ||
|
|
f68aace445 | ||
|
|
f042c70b3c | ||
|
|
2116d79aad | ||
|
|
4bc63e283c | ||
|
|
d5804f99c2 | ||
|
|
dfc353ce54 | ||
|
|
8f65ddb754 | ||
|
|
29e750f0b2 | ||
|
|
b24661b876 | ||
|
|
bbbd605f32 | ||
|
|
ff13cb4e26 | ||
|
|
5cb572b6a5 | ||
|
|
d8b97e06cf |
94
.github/workflows/development-build.yml
vendored
94
.github/workflows/development-build.yml
vendored
@@ -2,7 +2,7 @@ name: Development Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
branches-ignore: ["main", "v3"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
@@ -29,51 +29,51 @@ jobs:
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
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:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-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:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-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 }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
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 }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
|
||||
@@ -22,8 +22,6 @@ You can ask for guidance anytime on our
|
||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
||||
- If you see weird permission errors, especially on Mac, run `sudo spin up` instead.
|
||||
|
||||
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||
|
||||
### 4) Start development
|
||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||
|
||||
@@ -31,7 +29,6 @@ Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if y
|
||||
|
||||
Mails are caught by Mailpit: `localhost:8025`
|
||||
|
||||
|
||||
## New Service Contribution
|
||||
Check out the docs [here](https://coolify.io/docs/how-to-add-a-service).
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ class CheckProxy
|
||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||
$server->proxy->set('status', $status);
|
||||
$server->save();
|
||||
return false;
|
||||
if ($status === 'running') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
|
||||
@@ -18,7 +18,7 @@ class UpdateCoolify
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$this->server = Server::find(0)->first();
|
||||
$this->server = Server::find(0);
|
||||
if (!$this->server) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class StartService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Starting service: ' . $service->name);
|
||||
$network = $service->destination->network;
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
|
||||
@@ -10,6 +10,7 @@ class StopService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Stopping service: ' . $service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
|
||||
25
app/Actions/Shared/PullImage.php
Normal file
25
app/Actions/Shared/PullImage.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Shared;
|
||||
|
||||
use App\Models\Service;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class PullImage
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $resource)
|
||||
{
|
||||
$resource->saveComposeConfigs();
|
||||
|
||||
$commands[] = "cd " . $resource->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
||||
$commands[] = "docker compose pull";
|
||||
|
||||
$server = data_get($resource, 'server');
|
||||
|
||||
if (!$server) return;
|
||||
|
||||
instant_remote_process($commands, $resource->server);
|
||||
}
|
||||
}
|
||||
33
app/Console/Commands/Dev.php
Normal file
33
app/Console/Commands/Dev.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
|
||||
class Dev extends Command
|
||||
{
|
||||
protected $signature = 'dev:init';
|
||||
protected $description = 'Init the app in dev mode';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// Generate APP_KEY if not exists
|
||||
if (empty(env('APP_KEY'))) {
|
||||
echo "Generating APP_KEY.\n";
|
||||
Artisan::call('key:generate');
|
||||
}
|
||||
// Seed database if it's empty
|
||||
$settings = InstanceSettings::find(0);
|
||||
if (!$settings) {
|
||||
echo "Initializing instance, seeding database.\n";
|
||||
Artisan::call('migrate --seed');
|
||||
} else {
|
||||
echo "Instance already initialized.\n";
|
||||
}
|
||||
// Set permissions
|
||||
Process::run(['chmod', '-R', 'o+rwx', '.']);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,12 @@ class Init extends Command
|
||||
}
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
setup_dynamic_configuration();
|
||||
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
$settings = InstanceSettings::get();
|
||||
if (!is_null(env('AUTOUPDATE', null))) {
|
||||
@@ -94,7 +99,7 @@ class Init extends Command
|
||||
// Cleanup any failed deployments
|
||||
|
||||
try {
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', 'in_progress')->get();
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
||||
foreach ($halted_deployments as $deployment) {
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
@@ -110,16 +115,19 @@ class Init extends Command
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
echo 'Application without environment' . $application->name . 'deleting\n';
|
||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination' . $application->name . 'deleting\n';
|
||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server' . $application->name . 'deleting\n';
|
||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -129,16 +137,19 @@ class Init extends Command
|
||||
$postgresqls = StandalonePostgresql::all();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment' . $postgresql->name . 'deleting\n';
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination' . $postgresql->name . 'deleting\n';
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server' . $postgresql->name . 'deleting\n';
|
||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -148,16 +159,19 @@ class Init extends Command
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment' . $redis->name . 'deleting\n';
|
||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination' . $redis->name . 'deleting\n';
|
||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server' . $redis->name . 'deleting\n';
|
||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -168,16 +182,19 @@ class Init extends Command
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment' . $mongodb->name . 'deleting\n';
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination' . $mongodb->name . 'deleting\n';
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server' . $mongodb->name . 'deleting\n';
|
||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -188,16 +205,19 @@ class Init extends Command
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment' . $mysql->name . 'deleting\n';
|
||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination' . $mysql->name . 'deleting\n';
|
||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server' . $mysql->name . 'deleting\n';
|
||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -208,16 +228,19 @@ class Init extends Command
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment' . $mariadb->name . 'deleting\n';
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination' . $mariadb->name . 'deleting\n';
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server' . $mariadb->name . 'deleting\n';
|
||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -228,16 +251,19 @@ class Init extends Command
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment' . $service->name . 'deleting\n';
|
||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination' . $service->name . 'deleting\n';
|
||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server' . $service->name . 'deleting\n';
|
||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -247,8 +273,9 @@ class Init extends Command
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service' . $service->name . 'deleting\n';
|
||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
@@ -258,8 +285,9 @@ class Init extends Command
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service' . $service->name . 'deleting\n';
|
||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -56,16 +56,20 @@ class Kernel extends ConsoleKernel
|
||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||
$own = Team::find(0)->servers;
|
||||
$servers = $servers->merge($own);
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false);
|
||||
} else {
|
||||
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false);
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
|
||||
foreach ($containerServers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
if ($server->isLogDrainEnabled()) {
|
||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ class Controller extends BaseController
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
}
|
||||
if (is_null(data_get($user, 'email_verified_at'))){
|
||||
if (is_null(data_get($user, 'email_verified_at'))) {
|
||||
$user->email_verified_at = now();
|
||||
$user->save();
|
||||
}
|
||||
@@ -137,16 +137,28 @@ class Controller extends BaseController
|
||||
public function acceptInvitation()
|
||||
{
|
||||
try {
|
||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||
$resetPassword = request()->query('reset-password');
|
||||
$invitationUuid = request()->route('uuid');
|
||||
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
|
||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||
if (auth()->user()->id !== $user->id) {
|
||||
abort(401);
|
||||
}
|
||||
$invitationValid = $invitation->isValid();
|
||||
if ($invitationValid) {
|
||||
if ($resetPassword) {
|
||||
$user->update([
|
||||
'password' => Hash::make($invitationUuid),
|
||||
'force_password_reset' => true
|
||||
]);
|
||||
}
|
||||
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
|
||||
$invitation->delete();
|
||||
return redirect()->route('team.index');
|
||||
}
|
||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||
refreshSession($invitation->team);
|
||||
$invitation->delete();
|
||||
if (auth()->user()?->id !== $user->id) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
refreshSession($invitation->team);
|
||||
return redirect()->route('team.index');
|
||||
} else {
|
||||
abort(401);
|
||||
|
||||
@@ -24,7 +24,7 @@ class CheckForcePasswordReset
|
||||
}
|
||||
$force_password_reset = auth()->user()->force_password_reset;
|
||||
if ($force_password_reset) {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
|
||||
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('auth.force-password-reset');
|
||||
|
||||
@@ -11,6 +11,9 @@ class DecideWhatToDoWithUser
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if(auth()?->user()?->currentTeam()){
|
||||
refreshSession(auth()->user()->currentTeam());
|
||||
}
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect('boarding');
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsBoardingFlow
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
// ray()->showQueries()->color('orange');
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class IsSubscriptionValid
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (isInstanceAdmin()) {
|
||||
return $next($request);
|
||||
}
|
||||
if (!auth()->user() || !isCloud()) {
|
||||
if ($request->path() === 'subscription') {
|
||||
return redirect('/');
|
||||
} else {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||
// ray('active subscription Middleware');
|
||||
return redirect('/');
|
||||
}
|
||||
if (isSubscriptionOnGracePeriod()) {
|
||||
// ray('is_subscription_in_grace_period Middleware');
|
||||
return $next($request);
|
||||
}
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
// ray('SubscriptionValid Middleware');
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
} else {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private $docker_compose_base64;
|
||||
private string $dockerfile_location = '/Dockerfile';
|
||||
private string $docker_compose_location = '/docker-compose.yml';
|
||||
private ?string $docker_compose_custom_start_command = null;
|
||||
private ?string $docker_compose_custom_build_command = null;
|
||||
private ?string $addHosts = null;
|
||||
private ?string $buildTarget = null;
|
||||
private Collection $saved_outputs;
|
||||
@@ -215,19 +217,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
|
||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && !$this->application->destination->server->isSwarm()) {
|
||||
$this->push_to_docker_registry();
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}")
|
||||
],
|
||||
[
|
||||
"echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(true);
|
||||
@@ -267,7 +258,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application,'environment.project.team.id'));
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
private function push_to_docker_registry()
|
||||
@@ -299,6 +290,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"echo -n 'Image pushed to docker registry.'"
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
throw $e;
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"],
|
||||
);
|
||||
@@ -432,6 +426,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (data_get($this->application, 'docker_compose_location')) {
|
||||
$this->docker_compose_location = $this->application->docker_compose_location;
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_start_command')) {
|
||||
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_build_command')) {
|
||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||
}
|
||||
if ($this->pull_request_id === 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
} else {
|
||||
@@ -454,7 +454,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]);
|
||||
$this->save_environment_variables();
|
||||
// Build new container to limit downtime.
|
||||
$this->build_by_compose_file();
|
||||
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
|
||||
|
||||
if ($this->docker_compose_custom_build_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
$this->stop_running_container(force: true);
|
||||
|
||||
$networkId = $this->application->uuid;
|
||||
@@ -488,7 +499,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
);
|
||||
}
|
||||
$this->start_by_compose_file();
|
||||
// Start compose file
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("New container started.");
|
||||
}
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
@@ -575,7 +596,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function rolling_update()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
// Skip this.
|
||||
if ($this->build_pack !== 'dockerimage') {
|
||||
$this->push_to_docker_registry();
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update started.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}")
|
||||
],
|
||||
);
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
} else {
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
@@ -674,10 +704,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
ray("{$this->workdir}{$this->docker_compose_location}");
|
||||
$this->push_to_docker_registry();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
|
||||
],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
private function create_workdir()
|
||||
{
|
||||
@@ -874,11 +914,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$environment_variables = $this->generate_environment_variables($ports);
|
||||
|
||||
if (data_get($this->application, 'custom_labels')) {
|
||||
$labels = collect(str($this->application->custom_labels)->explode(','));
|
||||
$this->application->parseContainerLabels();
|
||||
$labels = collect(preg_split("/\r\n|\n|\r/", base64_decode($this->application->custom_labels)));
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !Str::startsWith($value, 'coolify.');
|
||||
});
|
||||
$this->application->custom_labels = $labels->implode(',');
|
||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||
$this->application->save();
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
@@ -940,13 +981,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
|
||||
|
||||
$docker_compose['services'][$this->container_name]['deploy'] = [
|
||||
'placement' => [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
],
|
||||
'mode' => 'replicated',
|
||||
'replicas' => 1,
|
||||
'replicas' => data_get($this->application, 'swarm_replicas', 1),
|
||||
'update_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
@@ -965,6 +1001,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
||||
}
|
||||
} else {
|
||||
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||
}
|
||||
@@ -1247,7 +1293,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
|
||||
private function build_by_compose_file() {
|
||||
private function build_by_compose_file()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
|
||||
@@ -1256,15 +1303,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
if ($this->docker_compose_location) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("New images built.");
|
||||
}
|
||||
@@ -1339,10 +1380,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
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));
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
// ray("checking log drain statuses for {$this->server->id}");
|
||||
try {
|
||||
if (!$this->server->isServerReady()) {
|
||||
if (!$this->server->isFunctional()) {
|
||||
return;
|
||||
};
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
@@ -63,19 +63,19 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
Sleep::for(10)->seconds();
|
||||
if ($this->healthcheck()) {
|
||||
if ($this->server->log_drain_notification_sent) {
|
||||
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->update(['log_drain_notification_sent' => false]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!$this->server->log_drain_notification_sent) {
|
||||
ray('Log drain container still unhealthy. Sending notification...');
|
||||
$this->server->team->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
|
||||
$this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null));
|
||||
$this->server->update(['log_drain_notification_sent' => true]);
|
||||
}
|
||||
} else {
|
||||
if ($this->server->log_drain_notification_sent) {
|
||||
$this->server->team->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
|
||||
$this->server->update(['log_drain_notification_sent' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
@@ -37,17 +42,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->handle();
|
||||
}
|
||||
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// ray("checking container statuses for {$this->server->id}");
|
||||
if (!$this->server->isServerReady($this->tries)) {
|
||||
throw new \RuntimeException('Server is not reachable.');
|
||||
};
|
||||
try {
|
||||
if (!$this->server->isServerReady()) {
|
||||
return;
|
||||
};
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
@@ -55,15 +58,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = null;
|
||||
$containerReplicates = null;
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return;
|
||||
}
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicase) {
|
||||
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
|
||||
foreach ($containerReplicase as $containerReplica) {
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
|
||||
@@ -286,7 +286,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
$this->team?->notify(new BackupSuccess($this->backup, $this->database));
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
@@ -302,7 +302,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]);
|
||||
}
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
$this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
try {
|
||||
$server = $this->resource->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
$this->resource->delete();
|
||||
$this->resource->forceDelete();
|
||||
return 'Server is not functional';
|
||||
}
|
||||
$this->resource->delete();
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
StopApplication::run($this->resource);
|
||||
@@ -56,10 +57,9 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
break;
|
||||
}
|
||||
if ($this->resource->type() === 'service') {
|
||||
$this->resource->delete();
|
||||
DeleteService::dispatch($this->resource);
|
||||
} else {
|
||||
$this->resource->delete();
|
||||
$this->resource->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
|
||||
@@ -17,30 +17,38 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public ?int $disk_usage = null;
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
public function handle()
|
||||
{
|
||||
ray("checking server status for {$this->server->id}");
|
||||
if (!$this->server->isServerReady(4)) {
|
||||
throw new \RuntimeException('Server is not reachable.');
|
||||
};
|
||||
try {
|
||||
if ($this->server->isServerReady()) {
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->cleanup(notify: false);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
handleError($e);
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
public function cleanup(bool $notify = false): void
|
||||
@@ -54,7 +62,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else {
|
||||
$this->server->high_disk_usage_notification_sent = true;
|
||||
$this->server->save();
|
||||
$this->server->team->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
|
||||
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
|
||||
}
|
||||
} else {
|
||||
DockerCleanupJob::dispatchSync($this->server);
|
||||
|
||||
@@ -4,29 +4,32 @@ namespace App\Livewire\Destination\New;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class StandaloneDocker extends Component
|
||||
class Docker extends Component
|
||||
{
|
||||
public string $name;
|
||||
public string $network;
|
||||
|
||||
public Collection $servers;
|
||||
public Server $server;
|
||||
public int|null $server_id = null;
|
||||
public ?int $server_id = null;
|
||||
public bool $is_swarm = false;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'network' => 'required|string',
|
||||
'server_id' => 'required|integer'
|
||||
'server_id' => 'required|integer',
|
||||
'is_swarm' => 'boolean'
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'network' => 'network',
|
||||
'server_id' => 'server'
|
||||
'server_id' => 'server',
|
||||
'is_swarm' => 'swarm'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -43,13 +46,13 @@ class StandaloneDocker extends Component
|
||||
} else {
|
||||
$this->network = new Cuid2(7);
|
||||
}
|
||||
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}");
|
||||
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
||||
}
|
||||
|
||||
public function generate_name()
|
||||
{
|
||||
$this->server = Server::find($this->server_id);
|
||||
$this->name = Str::kebab("{$this->server->name}-{$this->network}");
|
||||
$this->name = str("{$this->server->name}-{$this->network}")->kebab();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -57,17 +60,30 @@ class StandaloneDocker extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$this->server = Server::find($this->server_id);
|
||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
if ($this->is_swarm) {
|
||||
$found = $this->server->swarmDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
} else {
|
||||
$docker = SwarmDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$docker = ModelsStandaloneDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
} else {
|
||||
$docker = ModelsStandaloneDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
return $this->redirectRoute('destination.show', $docker->uuid, navigate: true);
|
||||
@@ -13,7 +13,11 @@ class Show extends Component
|
||||
|
||||
public function scan()
|
||||
{
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
if ($this->server->isSwarm()) {
|
||||
$alreadyAddedNetworks = $this->server->swarmDockers;
|
||||
} else {
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
}
|
||||
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
|
||||
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
|
||||
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
|
||||
|
||||
@@ -52,7 +52,7 @@ class DiscordSettings extends Component
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test());
|
||||
$this->team?->notify(new Test());
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class EmailSettings extends Component
|
||||
}
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test($this->emails));
|
||||
$this->team?->notify(new Test($this->emails));
|
||||
$this->dispatch('success', 'Test Email sent successfully.');
|
||||
}
|
||||
public function instantSaveInstance()
|
||||
|
||||
@@ -58,7 +58,7 @@ class TelegramSettings extends Component
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team->notify(new Test());
|
||||
$this->team?->notify(new Test());
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ class General extends Component
|
||||
'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',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.name' => 'name',
|
||||
@@ -94,8 +96,9 @@ class General extends Component
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -110,11 +113,7 @@ class General extends Component
|
||||
$this->application->isConfigurationChanged(true);
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
if (is_null(data_get($this->application, 'custom_labels'))) {
|
||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||
} else {
|
||||
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
|
||||
}
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
$this->checkLabelUpdates();
|
||||
}
|
||||
@@ -151,7 +150,6 @@ class General extends Component
|
||||
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
|
||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||
$this->application->save();
|
||||
$this->dispatch('success', 'Domain generated.');
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
@@ -201,7 +199,8 @@ class General extends Component
|
||||
public function submit($showToaster = true)
|
||||
{
|
||||
try {
|
||||
if ($this->application->build_pack === 'dockercompose' && ($this->initialDockerComposeLocation !== $this->application->docker_compose_location || $this->initialDockerComposePrLocation !== $this->application->docker_compose_pr_location)) {
|
||||
ray($this->initialDockerComposeLocation, $this->application->docker_compose_location);
|
||||
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
|
||||
$this->loadComposeFile();
|
||||
}
|
||||
$this->validate();
|
||||
@@ -233,14 +232,11 @@ class General extends Component
|
||||
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
||||
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
||||
}
|
||||
if (gettype($this->customLabels) === 'string') {
|
||||
$this->customLabels = str($this->customLabels)->replace(',', "\n");
|
||||
}
|
||||
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||
$this->parsedServices = $this->application->parseCompose();
|
||||
}
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
$showToaster && $this->dispatch('success', 'Application settings updated!');
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -73,6 +73,10 @@ class Heading extends Component
|
||||
$this->dispatch('error', 'Please load a Compose file first.');
|
||||
return;
|
||||
}
|
||||
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
|
||||
$this->dispatch('error', 'Please set a Docker image name first.');
|
||||
return;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
|
||||
@@ -72,10 +72,14 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
}
|
||||
}
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
|
||||
$this->application->refresh();
|
||||
|
||||
51
app/Livewire/Project/Application/Swarm.php
Normal file
51
app/Livewire/Project/Application/Swarm.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
|
||||
class Swarm extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public string $swarm_placement_constraints = '';
|
||||
|
||||
protected $rules = [
|
||||
'application.swarm_replicas' => 'required',
|
||||
'application.swarm_placement_constraints' => 'nullable',
|
||||
'application.settings.is_swarm_only_worker_nodes' => 'required',
|
||||
];
|
||||
public function mount() {
|
||||
if ($this->application->swarm_placement_constraints) {
|
||||
$this->swarm_placement_constraints = base64_decode($this->application->swarm_placement_constraints);
|
||||
}
|
||||
}
|
||||
public function instantSave() {
|
||||
try {
|
||||
$this->validate();
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Swarm settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit() {
|
||||
try {
|
||||
$this->validate();
|
||||
if ($this->swarm_placement_constraints) {
|
||||
$this->application->swarm_placement_constraints = base64_encode($this->swarm_placement_constraints);
|
||||
} else {
|
||||
$this->application->swarm_placement_constraints = null;
|
||||
}
|
||||
$this->application->save();
|
||||
|
||||
$this->dispatch('success', 'Swarm settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.swarm');
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,14 @@ class CloneProject extends Component
|
||||
public $servers;
|
||||
public ?Environment $environment = null;
|
||||
public ?int $selectedServer = null;
|
||||
public ?int $selectedDestination = null;
|
||||
public ?Server $server = null;
|
||||
public $resources = [];
|
||||
public string $newProjectName = '';
|
||||
|
||||
protected $messages = [
|
||||
'selectedServer' => 'Please select a server.',
|
||||
'selectedDestination' => 'Please select a server & destination.',
|
||||
'newProjectName' => 'Please enter a name for the new project.',
|
||||
];
|
||||
public function mount($project_uuid)
|
||||
@@ -34,7 +36,7 @@ class CloneProject extends Component
|
||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||
$this->project_id = $this->project->id;
|
||||
$this->servers = currentTeam()->servers;
|
||||
$this->newProjectName = $this->project->name . ' (clone)';
|
||||
$this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
||||
}
|
||||
|
||||
public function render()
|
||||
@@ -42,9 +44,10 @@ class CloneProject extends Component
|
||||
return view('livewire.project.clone-project');
|
||||
}
|
||||
|
||||
public function selectServer($server_id)
|
||||
public function selectServer($server_id, $destination_id)
|
||||
{
|
||||
$this->selectedServer = $server_id;
|
||||
$this->selectedDestination = $destination_id;
|
||||
$this->server = $this->servers->where('id', $server_id)->first();
|
||||
}
|
||||
|
||||
@@ -52,7 +55,7 @@ class CloneProject extends Component
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'selectedServer' => 'required',
|
||||
'selectedDestination' => 'required',
|
||||
'newProjectName' => 'required',
|
||||
]);
|
||||
$foundProject = Project::where('name', $this->newProjectName)->first();
|
||||
@@ -81,7 +84,8 @@ class CloneProject extends Component
|
||||
'fqdn' => generateFqdn($this->server, $uuid),
|
||||
'status' => 'exited',
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'destination_id' => $this->selectedServer,
|
||||
// This is not correct, but we need to set it to something
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
$newApplication->save();
|
||||
$environmentVaribles = $application->environment_variables()->get();
|
||||
@@ -107,7 +111,7 @@ class CloneProject extends Component
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'destination_id' => $this->selectedServer,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
$newDatabase->save();
|
||||
$environmentVaribles = $database->environment_variables()->get();
|
||||
@@ -133,7 +137,7 @@ class CloneProject extends Component
|
||||
$newService = $service->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'destination_id' => $this->selectedServer,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
$newService->save();
|
||||
foreach ($newService->applications() as $application) {
|
||||
|
||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
||||
class BackupExecutions extends Component
|
||||
{
|
||||
public $backup;
|
||||
public $executions;
|
||||
public $executions = [];
|
||||
public $setDeletableBackup;
|
||||
public function getListeners()
|
||||
{
|
||||
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
|
||||
}
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
$this->executions = $this->backup->executions;
|
||||
$this->executions = data_get($this->backup, 'executions', []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class DockerImage extends Component
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
], navigate: false);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,6 @@ class EmptyProject extends Component
|
||||
'name' => generate_random_name(),
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return $this->redirectRoute('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production'], navigate: true);
|
||||
return $this->redirectRoute('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production'], navigate: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ class GithubPrivateRepository extends Component
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
], navigate: false);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
], navigate: false);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ class PublicGitRepository extends Component
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
], navigate: false);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -6,22 +6,24 @@ use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Countable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Select extends Component
|
||||
{
|
||||
public $current_step = 'type';
|
||||
public ?int $server = null;
|
||||
public ?Server $server = null;
|
||||
public string $type;
|
||||
public string $server_id;
|
||||
public string $destination_uuid;
|
||||
public Countable|array|Server $allServers = [];
|
||||
public Countable|array|Server $servers = [];
|
||||
public Collection|array $standaloneDockers = [];
|
||||
public Collection|array $swarmDockers = [];
|
||||
public array $parameters;
|
||||
public Collection|array $services = [];
|
||||
public Collection|array $allServices = [];
|
||||
public bool $isDatabase = false;
|
||||
public bool $includeSwarm = true;
|
||||
|
||||
public bool $loadingServices = true;
|
||||
public bool $loading = false;
|
||||
@@ -31,7 +33,7 @@ class Select extends Component
|
||||
|
||||
public ?string $search = null;
|
||||
protected $queryString = [
|
||||
'server',
|
||||
'server_id',
|
||||
'search'
|
||||
];
|
||||
|
||||
@@ -97,21 +99,45 @@ class Select extends Component
|
||||
$this->loadingServices = false;
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->includeSwarm) {
|
||||
$this->servers = $this->allServers;
|
||||
} else {
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
}
|
||||
}
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
if ($this->loading) return;
|
||||
$this->loading = true;
|
||||
$this->type = $type;
|
||||
switch ($type) {
|
||||
case 'postgresql':
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'redis':
|
||||
case 'mongodb':
|
||||
$this->isDatabase = true;
|
||||
$this->includeSwarm = false;
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
break;
|
||||
}
|
||||
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
|
||||
$this->isDatabase = true;
|
||||
$this->includeSwarm = false;
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
}
|
||||
if ($type === "existing-postgresql") {
|
||||
$this->current_step = $type;
|
||||
return;
|
||||
}
|
||||
if (count($this->servers) === 1) {
|
||||
$server = $this->servers->first();
|
||||
$this->setServer($server);
|
||||
}
|
||||
// if (count($this->servers) === 1) {
|
||||
// $server = $this->servers->first();
|
||||
// $this->setServer($server);
|
||||
// }
|
||||
if (!is_null($this->server)) {
|
||||
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||
$foundServer = $this->servers->where('id', $this->server->id)->first();
|
||||
if ($foundServer) {
|
||||
return $this->setServer($foundServer);
|
||||
}
|
||||
@@ -122,6 +148,7 @@ class Select extends Component
|
||||
public function setServer(Server $server)
|
||||
{
|
||||
$this->server_id = $server->id;
|
||||
$this->server = $server;
|
||||
$this->standaloneDockers = $server->standaloneDockers;
|
||||
$this->swarmDockers = $server->swarmDockers;
|
||||
$this->current_step = 'destinations';
|
||||
@@ -142,5 +169,6 @@ class Select extends Component
|
||||
public function loadServers()
|
||||
{
|
||||
$this->servers = Server::isUsable()->get();
|
||||
$this->allServers = $this->servers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,6 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
], navigate: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Project\Service;
|
||||
|
||||
use App\Actions\Shared\PullImage;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Events\ServiceStatusChanged;
|
||||
@@ -69,4 +70,18 @@ class Navbar extends Component
|
||||
}
|
||||
ServiceStatusChanged::dispatch();
|
||||
}
|
||||
public function restart()
|
||||
{
|
||||
$this->checkDeployments();
|
||||
if ($this->isDeploymentProgress) {
|
||||
$this->dispatch('error', 'There is a deployment in progress.');
|
||||
return;
|
||||
}
|
||||
PullImage::run($this->service);
|
||||
$this->dispatch('image-pulled');
|
||||
StopService::run($this->service);
|
||||
$this->service->parse();
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,11 @@ class Show extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->env->delete();
|
||||
$this->dispatch('refreshEnvs');
|
||||
try {
|
||||
$this->env->delete();
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,17 @@ class GetLogs extends Component
|
||||
if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return;
|
||||
if ($this->container) {
|
||||
if ($this->showTimeStamps) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
if ($this->server->isSwarm()) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
}
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||
if ($this->server->isSwarm()) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} {$this->container}");
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||
}
|
||||
}
|
||||
if ($refresh) {
|
||||
$this->outputs = '';
|
||||
|
||||
@@ -34,7 +34,15 @@ class Logs extends Component
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = collect([
|
||||
[
|
||||
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
}
|
||||
if ($containers->count() > 0) {
|
||||
$containers->each(function ($container) {
|
||||
$this->containers->push(str_replace('/', '', $container['Names']));
|
||||
@@ -62,7 +70,7 @@ 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')) {
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
|
||||
class Add extends Component
|
||||
{
|
||||
public $uuid;
|
||||
public $parameters;
|
||||
public $isSwarm = false;
|
||||
public string $name;
|
||||
public string $mount_path;
|
||||
public string|null $host_path = null;
|
||||
public ?string $host_path = null;
|
||||
|
||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
||||
protected $rules = [
|
||||
public $rules = [
|
||||
'name' => 'required|string',
|
||||
'mount_path' => 'required|string',
|
||||
'host_path' => 'string|nullable',
|
||||
];
|
||||
|
||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'mount_path' => 'mount',
|
||||
@@ -27,17 +31,33 @@ class Add extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$applicationUuid = $this->parameters['application_uuid'];
|
||||
$application = Application::where('uuid', $applicationUuid)->first();
|
||||
if (!$application) {
|
||||
abort(404);
|
||||
}
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
$this->isSwarm = true;
|
||||
$this->rules['host_path'] = 'required|string';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->dispatch('addNewVolume', [
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
try {
|
||||
$this->validate($this->rules);
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->dispatch('addNewVolume', [
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
$this->dispatch('closeStorageModal');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function clear()
|
||||
|
||||
@@ -25,7 +25,7 @@ class Form extends Component
|
||||
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||
'server.settings.is_reachable' => 'required',
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
// 'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -37,16 +37,13 @@ class Form extends Component
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
'server.settings.is_reachable' => 'Is reachable',
|
||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||
// 'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
if (!$this->server->isFunctional()) {
|
||||
$this->validateServer();
|
||||
}
|
||||
}
|
||||
public function serverRefresh($install = true)
|
||||
{
|
||||
|
||||
@@ -22,7 +22,10 @@ class ByIp extends Component
|
||||
public string $user = 'root';
|
||||
public int $port = 22;
|
||||
public bool $is_swarm_manager = false;
|
||||
public bool $is_swarm_worker = false;
|
||||
public $selected_swarm_cluster = null;
|
||||
|
||||
public $swarm_managers = [];
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
@@ -30,6 +33,7 @@ class ByIp extends Component
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
'is_swarm_manager' => 'required|boolean',
|
||||
'is_swarm_worker' => 'required|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Name',
|
||||
@@ -38,12 +42,17 @@ class ByIp extends Component
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
'is_swarm_manager' => 'Swarm Manager',
|
||||
'is_swarm_worker' => 'Swarm Worker',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
$this->private_key_id = $this->private_keys->first()->id;
|
||||
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
|
||||
if ($this->swarm_managers->count() > 0) {
|
||||
$this->selected_swarm_cluster = $this->swarm_managers->first()->id;
|
||||
}
|
||||
}
|
||||
|
||||
public function setPrivateKey(string $private_key_id)
|
||||
@@ -53,7 +62,7 @@ class ByIp extends Component
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->dispatch('success', 'Application settings updated!');
|
||||
// $this->dispatch('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -63,7 +72,7 @@ class ByIp extends Component
|
||||
if (is_null($this->private_key_id)) {
|
||||
return $this->dispatch('error', 'You must select a private key');
|
||||
}
|
||||
$server = Server::create([
|
||||
$payload = [
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'ip' => $this->ip,
|
||||
@@ -75,11 +84,16 @@ class ByIp extends Component
|
||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||
"status" => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
];
|
||||
if ($this->is_swarm_worker) {
|
||||
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
|
||||
}
|
||||
$server = Server::create($payload);
|
||||
$server->settings->is_swarm_manager = $this->is_swarm_manager;
|
||||
$server->settings->is_swarm_worker = $this->is_swarm_worker;
|
||||
$server->settings->save();
|
||||
$server->addInitialNetwork();
|
||||
return $this->redirectRoute('server.show', $server->uuid, navigate: true);
|
||||
return $this->redirectRoute('server.show', $server->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ class Status extends Component
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function mount() {
|
||||
$this->checkProxy();
|
||||
}
|
||||
public function startProxyPolling()
|
||||
{
|
||||
|
||||
@@ -106,7 +106,7 @@ class Email extends Component
|
||||
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->settings->notify(new Test($this->emails));
|
||||
$this->settings?->notify(new Test($this->emails));
|
||||
$this->dispatch('success', 'Test email sent.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
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;
|
||||
@@ -13,6 +15,7 @@ use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
@@ -338,7 +341,7 @@ class Application extends BaseModel
|
||||
}
|
||||
public function isDeploymentInprogress()
|
||||
{
|
||||
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'in_progress')->count();
|
||||
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', ApplicationDeploymentStatus::QUEUED)->count();
|
||||
if ($deployments > 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -963,65 +966,98 @@ class Application extends BaseModel
|
||||
function loadComposeFile($isInit = false)
|
||||
{
|
||||
$initialDockerComposeLocation = $this->docker_compose_location;
|
||||
// $initialDockerComposePrLocation = $this->docker_compose_pr_location;
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
if ($isInit && $this->docker_compose_raw) {
|
||||
return;
|
||||
}
|
||||
$uuid = new Cuid2();
|
||||
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
|
||||
$workdir = rtrim($this->base_directory, '/');
|
||||
$composeFile = $this->docker_compose_location;
|
||||
// $prComposeFile = $this->docker_compose_pr_location;
|
||||
$fileList = collect([".$workdir$composeFile"]);
|
||||
// if ($composeFile !== $prComposeFile) {
|
||||
// $fileList->push(".$prComposeFile");
|
||||
// }
|
||||
$commands = collect([
|
||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
"git sparse-checkout init --cone",
|
||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||
"git read-tree -mu HEAD",
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
if (!$composeFileContent) {
|
||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||
$this->save();
|
||||
throw new \Exception("Could not load base compose file from $workdir$composeFile");
|
||||
} else {
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
}
|
||||
// if ($composeFile === $prComposeFile) {
|
||||
// $this->docker_compose_pr_raw = $composeFileContent;
|
||||
// $this->save();
|
||||
// } else {
|
||||
// $commands = collect([
|
||||
// "cd /tmp/{$uuid}",
|
||||
// "cat .$workdir$prComposeFile",
|
||||
// ]);
|
||||
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
// if (!$composePrFileContent) {
|
||||
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
||||
// $this->save();
|
||||
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
||||
// } else {
|
||||
// $this->docker_compose_pr_raw = $composePrFileContent;
|
||||
// $this->save();
|
||||
// }
|
||||
// }
|
||||
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
]);
|
||||
instant_remote_process($commands, $this->destination->server, false);
|
||||
return [
|
||||
'parsedServices' => $this->parseCompose(),
|
||||
'initialDockerComposeLocation' => $this->docker_compose_location,
|
||||
'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
|
||||
];
|
||||
if ($isInit && $this->docker_compose_raw) {
|
||||
return;
|
||||
}
|
||||
$uuid = new Cuid2();
|
||||
['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.');
|
||||
$workdir = rtrim($this->base_directory, '/');
|
||||
$composeFile = $this->docker_compose_location;
|
||||
// $prComposeFile = $this->docker_compose_pr_location;
|
||||
$fileList = collect([".$workdir$composeFile"]);
|
||||
// if ($composeFile !== $prComposeFile) {
|
||||
// $fileList->push(".$prComposeFile");
|
||||
// }
|
||||
$commands = collect([
|
||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
"git sparse-checkout init --cone",
|
||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||
"git read-tree -mu HEAD",
|
||||
"cat .$workdir$composeFile",
|
||||
]);
|
||||
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
if (!$composeFileContent) {
|
||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||
$this->save();
|
||||
throw new \Exception("Could not load base compose file from $workdir$composeFile");
|
||||
} else {
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
}
|
||||
// if ($composeFile === $prComposeFile) {
|
||||
// $this->docker_compose_pr_raw = $composeFileContent;
|
||||
// $this->save();
|
||||
// } else {
|
||||
// $commands = collect([
|
||||
// "cd /tmp/{$uuid}",
|
||||
// "cat .$workdir$prComposeFile",
|
||||
// ]);
|
||||
// $composePrFileContent = instant_remote_process($commands, $this->destination->server, false);
|
||||
// if (!$composePrFileContent) {
|
||||
// $this->docker_compose_pr_location = $initialDockerComposePrLocation;
|
||||
// $this->save();
|
||||
// throw new \Exception("Could not load compose file from $workdir$prComposeFile");
|
||||
// } else {
|
||||
// $this->docker_compose_pr_raw = $composePrFileContent;
|
||||
// $this->save();
|
||||
// }
|
||||
// }
|
||||
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
]);
|
||||
instant_remote_process($commands, $this->destination->server, false);
|
||||
$parsedServices = $this->parseCompose();
|
||||
if ($this->docker_compose_domains) {
|
||||
$json = collect(json_decode($this->docker_compose_domains));
|
||||
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
||||
$jsonNames = $json->keys()->toArray();
|
||||
$diff = array_diff($jsonNames, $names);
|
||||
$json = $json->filter(function ($value, $key) use ($diff) {
|
||||
return !in_array($key, $diff);
|
||||
});
|
||||
if ($json) {
|
||||
$this->docker_compose_domains = json_encode($json);
|
||||
} else {
|
||||
$this->docker_compose_domains = null;
|
||||
}
|
||||
$this->save();
|
||||
}
|
||||
return [
|
||||
'parsedServices' => $parsedServices,
|
||||
'initialDockerComposeLocation' => $this->docker_compose_location,
|
||||
'initialDockerComposePrLocation' => $this->docker_compose_pr_location,
|
||||
];
|
||||
}
|
||||
function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||
{
|
||||
$customLabels = data_get($this, 'custom_labels');
|
||||
if (!$customLabels) {
|
||||
return;
|
||||
}
|
||||
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
||||
ray('custom_labels is not base64 encoded');
|
||||
$this->custom_labels = str($customLabels)->replace(',', "\n");
|
||||
$this->custom_labels = base64_encode($customLabels);
|
||||
}
|
||||
$customLabels = base64_decode($this->custom_labels);
|
||||
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
|
||||
ray('custom_labels contains non-ascii characters');
|
||||
$customLabels = str(implode(",", generateLabelsApplication($this, $preview)))->replace(',', "\n");
|
||||
}
|
||||
$this->custom_labels = base64_encode($customLabels);
|
||||
$this->save();
|
||||
return $customLabels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -72,7 +71,7 @@ class Server extends BaseModel
|
||||
|
||||
static public function isUsable()
|
||||
{
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true);
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false);
|
||||
}
|
||||
|
||||
static public function destinationsByServer(string $server_id)
|
||||
@@ -150,29 +149,41 @@ class Server extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function isServerReady()
|
||||
public function isServerReady(int $tries = 3)
|
||||
{
|
||||
$serverUptimeCheckNumber = $this->unreachable_count;
|
||||
$serverUptimeCheckNumberMax = 3;
|
||||
if ($this->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
if ($this->unreachable_count < $tries) {
|
||||
$serverUptimeCheckNumber = $this->unreachable_count + 1;
|
||||
}
|
||||
$serverUptimeCheckNumberMax = $tries;
|
||||
|
||||
$currentTime = now()->timestamp;
|
||||
$runtime = 30;
|
||||
ray('server: ' . $this->name);
|
||||
ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
|
||||
ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
|
||||
|
||||
$isReady = false;
|
||||
// Run for 30 seconds max and check every 5 seconds for 3 times
|
||||
while ($currentTime + $runtime > now()->timestamp) {
|
||||
$result = $this->validateConnection();
|
||||
ray($result);
|
||||
if ($result) {
|
||||
if ($this->unreachable_notification_sent === true) {
|
||||
$this->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
// Reached max number of retries
|
||||
if ($this->unreachable_notification_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
$this->team->notify(new Unreachable($this));
|
||||
$this->team?->notify(new Unreachable($this));
|
||||
$this->update(['unreachable_notification_sent' => true]);
|
||||
}
|
||||
$this->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$this->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
if ($this->settings->is_reachable === true) {
|
||||
$this->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->applications() as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
@@ -189,23 +200,13 @@ class Server extends BaseModel
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
}
|
||||
$isReady = false;
|
||||
break;
|
||||
}
|
||||
$result = $this->validateConnection();
|
||||
// ray('validateConnection: ' . $result);
|
||||
if (!$result) {
|
||||
$serverUptimeCheckNumber++;
|
||||
} else {
|
||||
$this->update([
|
||||
'unreachable_count' => $serverUptimeCheckNumber,
|
||||
'unreachable_count' => $this->unreachable_count + 1,
|
||||
]);
|
||||
Sleep::for(5)->seconds();
|
||||
return;
|
||||
}
|
||||
$isReady = true;
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
return $isReady;
|
||||
}
|
||||
public function getDiskUsage()
|
||||
{
|
||||
@@ -379,9 +380,20 @@ class Server extends BaseModel
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
public function isSwarmManager()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager');
|
||||
}
|
||||
public function isSwarmWorker()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
$server = Server::find($this->id);
|
||||
if (!$server) {
|
||||
return false;
|
||||
}
|
||||
if ($server->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
@@ -401,7 +413,7 @@ class Server extends BaseModel
|
||||
}
|
||||
|
||||
if (data_get($server, 'unreachable_notification_sent') === true) {
|
||||
$server->team->notify(new Revived($server));
|
||||
$server->team?->notify(new Revived($server));
|
||||
$server->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ 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
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class ServiceDatabase extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -6,10 +6,11 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMariadb extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory,SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
|
||||
@@ -5,10 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMongodb extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -5,10 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneMysql extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
|
||||
@@ -5,10 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandalonePostgresql extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
|
||||
@@ -5,10 +5,11 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -98,7 +98,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
}
|
||||
public function sendPasswordResetNotification($token): void
|
||||
{
|
||||
$this->notify(new TransactionalEmailsResetPassword($token));
|
||||
$this?->notify(new TransactionalEmailsResetPassword($token));
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
|
||||
@@ -54,7 +54,7 @@ class Revived extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
|
||||
@@ -43,7 +43,7 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 3 times");
|
||||
$mail->subject("Coolify: Your server ({$this->server->name}) is unreachable.");
|
||||
$mail->view('emails.server-lost-connection', [
|
||||
'name' => $this->server->name,
|
||||
]);
|
||||
@@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
|
||||
$message = "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 3 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
|
||||
"message" => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,23 @@ use Visus\Cuid2\Cuid2;
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||
{
|
||||
$containers = collect([]);
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||
return $container;
|
||||
}
|
||||
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
if (!$server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||
return $container;
|
||||
}
|
||||
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return $containers;
|
||||
}
|
||||
return $containers;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,16 @@ function get_proxy_path()
|
||||
}
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
// Standalone networks
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
} else {
|
||||
// Standalone networks
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
}
|
||||
// Service networks
|
||||
foreach ($server->services()->get() as $service) {
|
||||
$networks->push($service->networks());
|
||||
@@ -41,16 +47,30 @@ function connectProxyToNetworks(Server $server)
|
||||
$networks->push($network);
|
||||
}
|
||||
$networks = collect($networks)->flatten()->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
if ($server->isSwarm()) {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify-overlay']);
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
} else {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
|
||||
return $commands->flatten();
|
||||
}
|
||||
function generate_default_proxy_configuration(Server $server)
|
||||
@@ -60,14 +80,18 @@ function generate_default_proxy_configuration(Server $server)
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify-overlay']);
|
||||
}
|
||||
} else {
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
}
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
|
||||
$array_of_networks = collect([]);
|
||||
$networks->map(function ($network) use ($array_of_networks) {
|
||||
$array_of_networks[$network] = [
|
||||
@@ -161,110 +185,112 @@ function setup_dynamic_configuration()
|
||||
{
|
||||
$dynamic_config_path = get_proxy_path() . "/dynamic";
|
||||
$settings = InstanceSettings::get();
|
||||
$server = Server::findOrFail(0);
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $server);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
$server = Server::find(0);
|
||||
if ($server) {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $server);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
'routers' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
'coolify-http' =>
|
||||
[
|
||||
'servers' =>
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
0 =>
|
||||
'servers' =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'servers' =>
|
||||
'loadBalancer' =>
|
||||
[
|
||||
0 =>
|
||||
'servers' =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https@docker',
|
||||
];
|
||||
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https@docker',
|
||||
];
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $server);
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($yaml);
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $server);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($yaml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,10 +969,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
if ($value->contains(':-')) {
|
||||
$key = $value->before(':');
|
||||
$defaultValue = $value->after(':-');
|
||||
@@ -989,6 +985,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$key = $value;
|
||||
$defaultValue = null;
|
||||
}
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
])->first();
|
||||
if ($foundEnv) {
|
||||
$defaultValue = data_get($foundEnv, 'value');
|
||||
}
|
||||
@@ -1330,7 +1330,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($value?->startsWith('$')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
])->first();
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
@@ -1397,14 +1398,22 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$defaultValue = data_get($foundEnv, 'value');
|
||||
}
|
||||
$isBuildTime = data_get($foundEnv, 'is_build_time', false);
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'application_id' => $resource->id,
|
||||
], [
|
||||
'value' => $defaultValue,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'application_id' => $resource->id,
|
||||
]);
|
||||
if ($foundEnv) {
|
||||
$foundEnv->update([
|
||||
'key' => $key,
|
||||
'application_id' => $resource->id,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'value' => $defaultValue,
|
||||
]);
|
||||
} else {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $defaultValue,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1544,6 +1553,9 @@ function generateEnvValue(string $command)
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
default:
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
}
|
||||
return $generatedValue;
|
||||
}
|
||||
|
||||
@@ -128,11 +128,6 @@ function allowedPathsForUnsubscribedAccounts()
|
||||
'logout',
|
||||
'waitlist',
|
||||
'force-password-reset',
|
||||
// 'livewire/message/force-password-reset',
|
||||
// 'livewire/message/check-license',
|
||||
// 'livewire/message/switch-team',
|
||||
// 'livewire/message/subscription.pricing-plans',
|
||||
// 'livewire/message/help',
|
||||
'livewire/update'
|
||||
];
|
||||
}
|
||||
@@ -141,8 +136,6 @@ function allowedPathsForBoardingAccounts()
|
||||
return [
|
||||
...allowedPathsForUnsubscribedAccounts(),
|
||||
'boarding',
|
||||
// 'livewire/message/boarding.index',
|
||||
// 'livewire/message/activity-monitor',
|
||||
'livewire/update'
|
||||
];
|
||||
}
|
||||
@@ -151,9 +144,6 @@ function allowedPathsForInvalidAccounts() {
|
||||
'logout',
|
||||
'verify',
|
||||
'force-password-reset',
|
||||
// 'livewire/message/force-password-reset',
|
||||
// 'livewire/message/verify-email',
|
||||
// 'livewire/message/help',
|
||||
'livewire/update'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ return [
|
||||
],
|
||||
'long-running' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
@@ -231,7 +231,7 @@ return [
|
||||
],
|
||||
'long-running' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
return [
|
||||
|
||||
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
|
||||
'dsn' => 'https://bea22abf110618b07252032aa2e07859@o1082494.ingest.sentry.io/4505347448045568',
|
||||
'dsn' => 'https://1bbc8f762199a52aee39196adb3e8d1a@o1082494.ingest.sentry.io/4505347448045568',
|
||||
|
||||
// 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.157',
|
||||
'release' => '4.0.0-beta.171',
|
||||
// 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.157';
|
||||
return '4.0.0-beta.171';
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\StandaloneMongodb>
|
||||
*/
|
||||
class StandaloneMongodbFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
71
database/migrations/2023_12_13_110214_add_soft_deletes.php
Normal file
71
database/migrations/2023_12_13_110214_add_soft_deletes.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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->softDeletes();
|
||||
});
|
||||
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('service_applications', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
Schema::table('service_databases', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('service_applications', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
Schema::table('service_databases', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->string('docker_compose_custom_start_command')->nullable();
|
||||
$table->string('docker_compose_custom_build_command')->nullable();
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('docker_compose_custom_start_command');
|
||||
$table->dropColumn('docker_compose_custom_build_command');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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->integer('swarm_replicas')->default(1);
|
||||
$table->text('swarm_placement_constraints')->nullable();
|
||||
});
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->boolean('is_swarm_only_worker_nodes')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('swarm_replicas');
|
||||
$table->dropColumn('swarm_placement_constraints');
|
||||
});
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('is_swarm_only_worker_nodes');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('servers', function (Blueprint $table) {
|
||||
$table->integer('swarm_cluster')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->dropColumn('swarm_cluster');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
ports:
|
||||
- "${FORWARD_SOKETI_PORT:-6001}:6001"
|
||||
environment:
|
||||
SOKETI_DEBUG: "true"
|
||||
SOKETI_DEBUG: "false"
|
||||
SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}"
|
||||
SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY:-coolify}"
|
||||
SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}"
|
||||
@@ -70,6 +70,8 @@ services:
|
||||
volumes:
|
||||
- .:/var/www/html:cached
|
||||
command: sh -c "npm install && npm run dev"
|
||||
networks:
|
||||
- coolify
|
||||
testing-host:
|
||||
<<: *testing-host-base
|
||||
container_name: coolify-testing-host
|
||||
@@ -77,15 +79,8 @@ services:
|
||||
- /:/host
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /data/coolify/:/data/coolify
|
||||
# - coolify-data-dev:/data/coolify
|
||||
# remote-host:
|
||||
# <<: *testing-host-base
|
||||
# container_name: coolify-remote-host
|
||||
# volumes:
|
||||
# - /:/host
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
# - /data/coolify/:/data/coolify
|
||||
# # - coolify-data-dev:/data/coolify
|
||||
networks:
|
||||
- coolify
|
||||
mailpit:
|
||||
image: "axllent/mailpit:latest"
|
||||
container_name: coolify-mail
|
||||
@@ -115,3 +110,9 @@ volumes:
|
||||
coolify-pg-data-dev:
|
||||
coolify-redis-data-dev:
|
||||
coolify-minio-data-dev:
|
||||
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
name: coolify
|
||||
external: false
|
||||
|
||||
@@ -2,7 +2,7 @@ FROM serversideup/php:8.2-fpm-nginx
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# https://github.com/cloudflare/cloudflared/releases
|
||||
ARG CLOUDFLARED_VERSION=2023.8.2
|
||||
ARG CLOUDFLARED_VERSION=2023.10.0
|
||||
|
||||
ARG POSTGRES_VERSION=15
|
||||
RUN apt-get update
|
||||
|
||||
1
docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/type
Normal file
1
docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/type
Normal file
@@ -0,0 +1 @@
|
||||
oneshot
|
||||
5
docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/up
Normal file
5
docker/dev-ssu/etc/s6-overlay/s6-rc.d/init-setup/up
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/command/execlineb -P
|
||||
foreground { composer -d /var/www/html/ install }
|
||||
foreground { php /var/www/html/artisan migrate --step }
|
||||
foreground { php /var/www/html/artisan dev:init }
|
||||
|
||||
@@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# https://github.com/cloudflare/cloudflared/releases
|
||||
ARG CLOUDFLARED_VERSION=2023.8.2
|
||||
ARG CLOUDFLARED_VERSION=2023.10.0
|
||||
ARG POSTGRES_VERSION=15
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#!/command/execlineb -P
|
||||
php /var/www/html/artisan app:init
|
||||
s6-setuidgid webuser
|
||||
php /var/www/html/artisan app:init --cleanup
|
||||
|
||||
@@ -5,25 +5,32 @@
|
||||
html {
|
||||
@apply text-neutral-400;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply text-sm antialiased scrollbar;
|
||||
}
|
||||
|
||||
button[isError] {
|
||||
@apply bg-red-600 hover:bg-red-700;
|
||||
}
|
||||
|
||||
.scrollbar {
|
||||
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
|
||||
}
|
||||
|
||||
.main {
|
||||
@apply pt-4 pl-24 pr-10 mx-auto;
|
||||
}
|
||||
|
||||
.custom-modal {
|
||||
@apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200;
|
||||
}
|
||||
|
||||
.label-text,
|
||||
label {
|
||||
@apply text-neutral-400;
|
||||
}
|
||||
|
||||
.navbar-main {
|
||||
@apply flex items-end gap-6 py-2 border-b-2 border-solid border-coolgray-200;
|
||||
}
|
||||
@@ -31,89 +38,117 @@ label {
|
||||
.loading {
|
||||
@apply w-4 text-warning;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl font-bold text-white;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl font-bold text-white;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl font-bold text-white;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-base font-bold text-white;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent;
|
||||
}
|
||||
|
||||
.kbd-custom {
|
||||
@apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply w-6 h-6;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
.box {
|
||||
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
||||
}
|
||||
|
||||
.box-without-bg {
|
||||
@apply flex p-2 transition-colors h-16 min-h-full hover:text-white hover:no-underline min-h-[4rem];
|
||||
@apply flex p-2 transition-colors min-h-full hover:text-white hover:no-underline min-h-[4rem];
|
||||
}
|
||||
|
||||
.description {
|
||||
@apply pt-2 text-xs font-bold text-neutral-500 group-hover:text-white;
|
||||
}
|
||||
|
||||
.lds-heart {
|
||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
|
||||
@keyframes lds-heart {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
5% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
39% {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
45% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-coollabs-gradient {
|
||||
@apply text-transparent text-white bg-gradient-to-r from-purple-500 via-pink-500 to-red-500;
|
||||
}
|
||||
|
||||
.text-helper {
|
||||
@apply inline-block font-bold text-warning;
|
||||
}
|
||||
|
||||
table {
|
||||
@apply min-w-full divide-y divide-coolgray-200;
|
||||
}
|
||||
|
||||
thead {
|
||||
@apply uppercase;
|
||||
}
|
||||
|
||||
tbody {
|
||||
@apply divide-y divide-coolgray-200;
|
||||
}
|
||||
|
||||
tr {
|
||||
@apply text-neutral-400;
|
||||
}
|
||||
|
||||
tr th {
|
||||
@apply px-3 py-3.5 text-left text-white;
|
||||
}
|
||||
|
||||
tr th:first-child {
|
||||
@apply py-3.5 pl-4 pr-3 sm:pl-6;
|
||||
}
|
||||
|
||||
tr td {
|
||||
@apply px-3 py-4 whitespace-nowrap;
|
||||
}
|
||||
|
||||
tr td:first-child {
|
||||
@apply pl-4 pr-3 font-bold sm:pl-6;
|
||||
}
|
||||
@@ -121,12 +156,15 @@ tr td:first-child {
|
||||
.buyme {
|
||||
@apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
@apply pt-2 pb-10;
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4 ;
|
||||
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4;
|
||||
}
|
||||
|
||||
input.input-sm {
|
||||
@apply pr-10;
|
||||
}
|
||||
|
||||
@@ -24,20 +24,20 @@
|
||||
<form action="/login" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
@env('local')
|
||||
<x-forms.input value="test@example.com" type="email" name="email" required
|
||||
label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input value="test@example.com" type="email" name="email" required
|
||||
label="{{ __('input.email') }}" autofocus />
|
||||
|
||||
<x-forms.input value="password" type="password" name="password" required
|
||||
label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
@else
|
||||
<x-forms.input type="email" name="email" required label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input type="password" name="password" required label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
<x-forms.input value="password" type="password" name="password" required
|
||||
label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
@else
|
||||
<x-forms.input type="email" name="email" required label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input type="password" name="password" required label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
@endenv
|
||||
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
|
||||
@if (!$is_registration_enabled)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<x-layout-simple>
|
||||
<div class="min-h-screen hero">
|
||||
<div>
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="flex flex-col items-center ">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pb-4 text-center">
|
||||
<h2>{{ __('auth.reset_password') }}</h2>
|
||||
{{ __('auth.reset_password') }}
|
||||
</div>
|
||||
<div>
|
||||
<form action="/reset-password" method="POST" class="flex flex-col gap-2">
|
||||
|
||||
@@ -23,20 +23,22 @@
|
||||
@if (data_get($application, 'build_pack') === 'dockercompose')
|
||||
@foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
|
||||
@if (data_get($fqdn, 'domain'))
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ getFqdnWithoutPort(data_get($fqdn, 'domain')) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort(data_get($fqdn, 'domain')) }}
|
||||
</a>
|
||||
</li>
|
||||
@foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort($domain) }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
@if(!$application->destination->server->isSwarm())
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
@endif
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
@@ -19,24 +21,26 @@
|
||||
<div class="flex-1"></div>
|
||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||
<div>Please load a Compose file.</div>
|
||||
@elseif ($application->destination->server->isSwarm() && str($application->docker_registry_image_name)->isEmpty())
|
||||
Swarm Deployments requires a Docker Image in a Registry.
|
||||
@else
|
||||
<x-applications.advanced :application="$application" />
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-applications.advanced :application="$application" />
|
||||
@endif
|
||||
@if ($application->status !== 'exited')
|
||||
<button title="With rolling update if possible" wire:click='deploy'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</button>
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<button title="With rolling update if possible" wire:click='deploy'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</button>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
<button title="Restart without rebuilding" wire:click='restart'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
@@ -47,9 +51,13 @@
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
@if ($application->destination->server->isSwarm())
|
||||
Update Service
|
||||
@else
|
||||
Restart
|
||||
@endif
|
||||
</button>
|
||||
@if (isDev())
|
||||
{{-- @if (isDev())
|
||||
<button title="Restart without rebuilding" wire:click='restartNew'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -61,7 +69,7 @@
|
||||
</svg>
|
||||
Restart (new)
|
||||
</button>
|
||||
@endif
|
||||
@endif --}}
|
||||
@endif
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
@@ -83,7 +91,7 @@
|
||||
</svg>
|
||||
Deploy
|
||||
</button>
|
||||
@if (isDev())
|
||||
{{-- @if (isDev())
|
||||
<button wire:click='deployNew'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24"
|
||||
@@ -94,7 +102,7 @@
|
||||
</svg>
|
||||
Deploy (new)
|
||||
</button>
|
||||
@endif
|
||||
@endif --}}
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<label class="flex gap-4 px-0 cursor-pointer label">
|
||||
<span class="flex gap-2 label-text min-w-fit">
|
||||
@if ($label)
|
||||
{{ $label }}
|
||||
{!! $label !!}
|
||||
@else
|
||||
{{ $id }}
|
||||
@endif
|
||||
|
||||
@@ -22,18 +22,20 @@
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
<input {{ $attributes->merge(['class' => $defaultClass . ' pl-10']) }} @required($required)
|
||||
wire:model={{ $id }} wire:dirty.class.remove='text-white' wire:dirty.class="input-warning"
|
||||
wire:loading.attr="disabled" type="{{ $type }}" @readonly($readonly) @disabled($disabled)
|
||||
id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
|
||||
<input value="{{ $value }}" {{ $attributes->merge(['class' => $defaultClass . ' pl-10']) }}
|
||||
@required($required) @if ($id !== 'null') wire:model={{ $id }} @endif
|
||||
wire:dirty.class.remove='text-white' wire:dirty.class="input-warning" wire:loading.attr="disabled"
|
||||
type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
|
||||
name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
|
||||
aria-placeholder="{{ $attributes->get('placeholder') }}">
|
||||
|
||||
</div>
|
||||
@else
|
||||
<input {{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
|
||||
wire:model={{ $id }} wire:dirty.class.remove='text-white' wire:dirty.class="input-warning"
|
||||
<input @if ($value) value="{{ $value }}" @endif
|
||||
{{ $attributes->merge(['class' => $defaultClass]) }} @required($required) @readonly($readonly)
|
||||
@if ($id !== 'null') wire:model={{ $id }} @endif wire:dirty.class.remove='text-white' wire:dirty.class="input-warning"
|
||||
wire:loading.attr="disabled" type="{{ $type }}" @disabled($disabled)
|
||||
id="{{ $id }}" name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}">
|
||||
@if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}">
|
||||
@endif
|
||||
@if (!$label && $helper)
|
||||
<x-helper :helper="$helper" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<livewire:server.proxy.modal :server="$server" />
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>Server</h1>
|
||||
@if ($server->proxyType() !== 'NONE')
|
||||
@if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker())
|
||||
<livewire:server.proxy.status :server="$server" />
|
||||
@endif
|
||||
</div>
|
||||
@@ -20,25 +20,30 @@
|
||||
]) }}">
|
||||
<button>Private Key</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Proxy</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.destinations', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Destinations</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.log-drains', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Log Drains</button>
|
||||
</a>
|
||||
@if (!$server->isSwarmWorker())
|
||||
<a wire:navigate class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Proxy</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.destinations', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Destinations</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.log-drains', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Log Drains</button>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<div class="flex-1"></div>
|
||||
<livewire:server.proxy.deploy :server="$server" />
|
||||
@if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker())
|
||||
<livewire:server.proxy.deploy :server="$server" />
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="navbar-main">
|
||||
<div class="navbar-main" x-data>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.service.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
@@ -27,6 +27,16 @@
|
||||
</button>
|
||||
@endif
|
||||
@if (serviceStatus($service) === 'running')
|
||||
<button wire:click='restart' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Pull Latest Images & Restart
|
||||
</button>
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -58,3 +68,11 @@
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
$wire.on('image-pulled', () => {
|
||||
startService.showModal();
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@if (Str::of($status)->startsWith('running'))
|
||||
<x-status.running :status="$status" />
|
||||
@elseif(Str::of($status)->startsWith('restarting'))
|
||||
@elseif(Str::of($status)->startsWith('restarting') || Str::of($status)->startsWith('starting'))
|
||||
<x-status.restarting :status="$status" />
|
||||
@else
|
||||
<x-status.stopped :status="$status" />
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<x-layout>
|
||||
<livewire:destination.new.standalone-docker :servers="$servers" :server_id="$server_id" />
|
||||
<livewire:destination.new.docker :servers="$servers" :server_id="$server_id" />
|
||||
</x-layout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@extends('layouts.base')
|
||||
@section('body')
|
||||
@parent
|
||||
<x-navbar-subscription />
|
||||
<main>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
@if ($servers->count() === 1)
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
@else
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
|
||||
@endif
|
||||
@foreach ($servers as $server)
|
||||
<a wire:navigate href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<option value="{{ $server->id }}">{{ $server->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
{{-- <x-forms.checkbox type="checkbox" id="is_swarm" label="Is it a Swarm network?" /> --}}
|
||||
<x-forms.button type="submit">
|
||||
Save Destination
|
||||
</x-forms.button>
|
||||
@@ -11,7 +11,16 @@
|
||||
<div class="flex gap-2 ">
|
||||
Available for using:
|
||||
@forelse ($server->standaloneDockers as $docker)
|
||||
<a wire:navigate href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
||||
<a wire:navigate
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
||||
<button class="text-white btn-link">{{ data_get($docker, 'network') }} </button>
|
||||
</a>
|
||||
@empty
|
||||
<div class="">N/A</div>
|
||||
@endforelse
|
||||
@forelse ($server->swarmDockers as $docker)
|
||||
<a wire:navigate
|
||||
href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
||||
<button class="text-white btn-link">{{ data_get($docker, 'network') }} </button>
|
||||
</a>
|
||||
@empty
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="min-h-screen hero">
|
||||
<div class="w-96 min-w-fit">
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="flex flex-col items-center">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pb-4 text-center">
|
||||
<h2>Set your initial password</h2>
|
||||
Set your initial password
|
||||
</div>
|
||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user