mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-26 12:33:25 +00:00
Compare commits
160 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc8f09f05e | ||
|
|
2beda08717 | ||
|
|
b455d153ae | ||
|
|
5fddf01820 | ||
|
|
c80434141d | ||
|
|
d1128c7a1e | ||
|
|
aae81313a6 | ||
|
|
4667f96b40 | ||
|
|
28c320ae97 | ||
|
|
45017efe00 | ||
|
|
a20290cac8 | ||
|
|
31e02a154c | ||
|
|
023ee5db99 | ||
|
|
05d2e15ab5 | ||
|
|
7d6590c60a | ||
|
|
3152ce183b | ||
|
|
d9f1a7c4d0 | ||
|
|
952aed3c49 | ||
|
|
ab3c433450 | ||
|
|
2b5e4a34d4 | ||
|
|
35cea852ca | ||
|
|
88581c8983 | ||
|
|
a7a9aab189 | ||
|
|
370c9b63cf | ||
|
|
7cb08849de | ||
|
|
7f052163e3 | ||
|
|
277d939033 | ||
|
|
26fbdcfab0 | ||
|
|
6d63ba9d4d | ||
|
|
8963f4fd62 | ||
|
|
463021a9f3 | ||
|
|
f71a8e9fef | ||
|
|
2dd5be1b4e | ||
|
|
608838045f | ||
|
|
899d506faa | ||
|
|
21b3e3ea05 | ||
|
|
a68951541c | ||
|
|
7fd0deedb1 | ||
|
|
e9e12ad843 | ||
|
|
4fd3185d12 | ||
|
|
f5ccebfd41 | ||
|
|
294721eef9 | ||
|
|
8af509992d | ||
|
|
11fccb8e89 | ||
|
|
1e126dd2c3 | ||
|
|
cfe2f889a4 | ||
|
|
1bd76b0e07 | ||
|
|
6d8c935cc7 | ||
|
|
7144cee0f6 | ||
|
|
f75a8d56f2 | ||
|
|
2f321bcfd9 | ||
|
|
a157f4f17b | ||
|
|
7723c623d5 | ||
|
|
bc3bb78916 | ||
|
|
0ebf5e49fb | ||
|
|
c340921fbb | ||
|
|
30e26b101c | ||
|
|
1b17cab663 | ||
|
|
ab039adf97 | ||
|
|
62fe10df31 | ||
|
|
2004a751dd | ||
|
|
ace127acf4 | ||
|
|
82c5497a06 | ||
|
|
dbb7989027 | ||
|
|
103f677a93 | ||
|
|
cb6bf78595 | ||
|
|
62334ddef7 | ||
|
|
778f67f2e4 | ||
|
|
48737f8e60 | ||
|
|
94de62e503 | ||
|
|
0445052898 | ||
|
|
ccde90ea91 | ||
|
|
ed94355019 | ||
|
|
02fcd1b5fc | ||
|
|
ca934e7cdf | ||
|
|
0ddd5f0a79 | ||
|
|
099d6801b7 | ||
|
|
62293926ec | ||
|
|
c96daad12c | ||
|
|
9cc3be152f | ||
|
|
f841c0d4ba | ||
|
|
3cd1d8135e | ||
|
|
86474d9f90 | ||
|
|
9bf87a3033 | ||
|
|
71e32520cf | ||
|
|
e3e938c8eb | ||
|
|
03aa440424 | ||
|
|
85ca38be90 | ||
|
|
7c9790dff0 | ||
|
|
40a71a11cb | ||
|
|
46a500f5e5 | ||
|
|
2d1d03bf8e | ||
|
|
1092d00c7a | ||
|
|
cd58e0d01e | ||
|
|
30a9783348 | ||
|
|
c839cf50af | ||
|
|
f8d607b06f | ||
|
|
cfadeb07b1 | ||
|
|
072850be0b | ||
|
|
71d120bc4e | ||
|
|
68d3cea528 | ||
|
|
07e801f44d | ||
|
|
ee5c694aa2 | ||
|
|
efa5eb1770 | ||
|
|
42e37246f3 | ||
|
|
dabb08ff4a | ||
|
|
66b0e04cc6 | ||
|
|
74824b7737 | ||
|
|
df2bcdb854 | ||
|
|
a8e9ee2e95 | ||
|
|
5093697b27 | ||
|
|
5bacd63805 | ||
|
|
1d5932e63f | ||
|
|
022762c0c9 | ||
|
|
e16bd194a3 | ||
|
|
a3765c19e3 | ||
|
|
a845d92d88 | ||
|
|
668c9e5a64 | ||
|
|
aaa06f4120 | ||
|
|
e26f4ce707 | ||
|
|
a8c3a2d991 | ||
|
|
6d52cef73a | ||
|
|
edacfcdec7 | ||
|
|
683872ef4e | ||
|
|
7a299ba1f9 | ||
|
|
f5eaedfc72 | ||
|
|
11b6afbe09 | ||
|
|
cd7340915b | ||
|
|
b38bb3df5d | ||
|
|
1f7725ada3 | ||
|
|
622095ebc4 | ||
|
|
397b7fefe3 | ||
|
|
10d38b709b | ||
|
|
98985690f0 | ||
|
|
f50c483c64 | ||
|
|
a15eca137d | ||
|
|
ba4be02e75 | ||
|
|
d4f6a86a57 | ||
|
|
6a058372bb | ||
|
|
e6cce350bd | ||
|
|
7c0c1e6cf8 | ||
|
|
39f787b7db | ||
|
|
424437446d | ||
|
|
60a1859d89 | ||
|
|
ad5c1639e8 | ||
|
|
92828b22fa | ||
|
|
e470096e4e | ||
|
|
3c41608ee9 | ||
|
|
035e145cd1 | ||
|
|
2f621279c2 | ||
|
|
c30185c6ae | ||
|
|
908c74eb27 | ||
|
|
10c17fc9a9 | ||
|
|
a6a0cb928a | ||
|
|
8bca988520 | ||
|
|
97da13c3c4 | ||
|
|
7134b46cdc | ||
|
|
de3b8a10a0 | ||
|
|
8feece702c | ||
|
|
f3fe4433ae |
19
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
19
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,17 +1,28 @@
|
||||
name: Bug report
|
||||
description: Create a new bug report
|
||||
description: 'Create a new bug report.'
|
||||
title: '[Bug]: '
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >-
|
||||
# 💎 Bounty program (with
|
||||
[algora.io](https://console.algora.io/org/coollabsio/bounties/new))
|
||||
|
||||
|
||||
If you would like to prioritize the issue resolution, you can add bounty
|
||||
to this issue.
|
||||
|
||||
|
||||
Click [here](https://console.algora.io/org/coollabsio/bounties/new) to
|
||||
get started.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Minimal Reproduction (if possible, example repository)
|
||||
description: Please provide a step by step guide to reproduce the issue
|
||||
description: Please provide a step by step guide to reproduce the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
4
.github/workflows/development-build.yml
vendored
4
.github/workflows/development-build.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||
|
||||
4
.github/workflows/production-build.yml
vendored
4
.github/workflows/production-build.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
file: docker/prod/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||
[](https://console.algora.io/org/coollabsio/bounties?status=open)
|
||||
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
||||
# About the Project
|
||||
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
@@ -39,9 +42,11 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
|
||||
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
|
||||
<a href="https://www.runpod.io/?utm_source=coolify.io">
|
||||
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
|
||||
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
|
||||
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
||||
|
||||
@@ -165,7 +165,6 @@ class RunRemoteProcess
|
||||
public function encodeOutput($type, $output)
|
||||
{
|
||||
$outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$outputStack[] = [
|
||||
'type' => $type,
|
||||
'output' => $output,
|
||||
|
||||
@@ -29,6 +29,7 @@ class StartClickhouse
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
@@ -93,6 +94,11 @@ class StartClickhouse
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class StartDragonfly
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
@@ -94,6 +95,11 @@ class StartDragonfly
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class StartKeydb
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_keydb();
|
||||
@@ -92,6 +93,11 @@ class StartKeydb
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class StartMariadb
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_mysql();
|
||||
@@ -86,6 +87,11 @@ class StartMariadb
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class StartMongodb
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_mongo_conf();
|
||||
@@ -94,6 +95,11 @@ class StartMongodb
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class StartMysql
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_mysql();
|
||||
@@ -86,6 +87,11 @@ class StartMysql
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class StartPostgresql
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->generate_init_scripts();
|
||||
@@ -92,6 +93,11 @@ class StartPostgresql
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class StartRedis
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->database->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_redis();
|
||||
@@ -96,6 +97,11 @@ class StartRedis
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,10 @@ class UpdateCoolify
|
||||
public ?Server $server = null;
|
||||
public ?string $latestVersion = null;
|
||||
public ?string $currentVersion = null;
|
||||
public bool $async = false;
|
||||
|
||||
public function handle(bool $force = false, bool $async = false)
|
||||
public function handle($manual_update = false)
|
||||
{
|
||||
try {
|
||||
$this->async = $async;
|
||||
$settings = InstanceSettings::get();
|
||||
ray('Running InstanceAutoUpdateJob');
|
||||
$this->server = Server::find(0);
|
||||
@@ -27,28 +25,19 @@ class UpdateCoolify
|
||||
CleanupDocker::run($this->server, false);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
// if ($settings->next_channel) {
|
||||
// ray('next channel enabled');
|
||||
// $this->latestVersion = 'next';
|
||||
// }
|
||||
if ($force) {
|
||||
$this->update();
|
||||
} else {
|
||||
if (!$manual_update) {
|
||||
if (!$settings->is_auto_update_enabled) {
|
||||
return 'Auto update is disabled';
|
||||
return;
|
||||
}
|
||||
if ($this->latestVersion === $this->currentVersion) {
|
||||
return 'Already on latest version';
|
||||
return;
|
||||
}
|
||||
if (version_compare($this->latestVersion, $this->currentVersion, '<')) {
|
||||
return 'Latest version is lower than current version?!';
|
||||
return;
|
||||
}
|
||||
$this->update();
|
||||
}
|
||||
$this->update();
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -56,34 +45,15 @@ class UpdateCoolify
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray("Running update on local docker container. Updating to $this->latestVersion");
|
||||
if ($this->async) {
|
||||
ray('Running async update');
|
||||
remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
} else {
|
||||
instant_remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
}
|
||||
ray('Update done');
|
||||
return;
|
||||
} else {
|
||||
ray('Running update on production server');
|
||||
if ($this->async) {
|
||||
remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
} else {
|
||||
instant_remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
}
|
||||
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
|
||||
remote_process([
|
||||
"sleep 10"
|
||||
], $this->server);
|
||||
return;
|
||||
}
|
||||
remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
|
||||
], $this->server);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ class ServicesGenerate extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// ray()->clearAll();
|
||||
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
|
||||
$files = array_filter($files, function ($file) {
|
||||
return strpos($file, '.yaml') !== false;
|
||||
@@ -63,6 +62,7 @@ class ServicesGenerate extends Command
|
||||
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
|
||||
if ($documentation->count() > 0) {
|
||||
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
|
||||
$documentation = str($documentation)->append('?utm_source=coolify.io');
|
||||
} else {
|
||||
$documentation = 'https://coolify.io/docs';
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ use App\Jobs\CheckLogDrainContainerJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\PullCoolifyImageJob;
|
||||
use App\Jobs\PullHelperImageJob;
|
||||
use App\Jobs\PullSentinelImageJob;
|
||||
use App\Jobs\PullTemplatesAndVersions;
|
||||
use App\Jobs\PullTemplatesFromCDN;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Server;
|
||||
@@ -30,35 +29,33 @@ class Kernel extends ConsoleKernel
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
$schedule->job(new PullTemplatesAndVersions)->everyTenMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
// $this->pull_helper_image($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||
$schedule->job(new PullTemplatesAndVersions)->everyTenMinutes()->onOneServer();
|
||||
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->pull_helper_image($schedule);
|
||||
$this->pull_images($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
|
||||
$schedule->command('cleanup:database --yes')->daily();
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
}
|
||||
}
|
||||
private function pull_helper_image($schedule)
|
||||
private function pull_images($schedule)
|
||||
{
|
||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
@@ -89,16 +86,6 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
if ($settings->is_auto_update_enabled) {
|
||||
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function check_scheduled_backups($schedule)
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
|
||||
@@ -410,11 +410,12 @@ class Github extends Controller
|
||||
if ($action === 'closed' || $action === 'close') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
|
||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
@@ -430,7 +431,6 @@ class Github extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
ray($return_payloads);
|
||||
return response($return_payloads);
|
||||
} catch (Exception $e) {
|
||||
ray($e->getMessage());
|
||||
|
||||
@@ -202,7 +202,7 @@ class Gitlab extends Controller
|
||||
]);
|
||||
ray('Preview deployments disabled for ' . $application->name);
|
||||
}
|
||||
} else if ($action === 'closed' || $action === 'close') {
|
||||
} else if ($action === 'closed' || $action === 'close' || $action === 'merge') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
|
||||
@@ -150,6 +150,10 @@ class Stripe extends Controller
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
}
|
||||
if (!$subscription) {
|
||||
if ($status === 'incomplete_expired') {
|
||||
send_internal_notification('Subscription incomplete expired for customer: ' . $customerId);
|
||||
return response("Subscription incomplete expired", 200);
|
||||
}
|
||||
send_internal_notification('No subscription found for: ' . $customerId);
|
||||
return response("No subscription found", 400);
|
||||
}
|
||||
@@ -166,9 +170,11 @@ class Stripe extends Controller
|
||||
$quantity = data_get($data, 'items.data.0.quantity', 10);
|
||||
}
|
||||
$team = data_get($subscription, 'team');
|
||||
$team->update([
|
||||
'custom_server_limit' => $quantity,
|
||||
]);
|
||||
if ($team) {
|
||||
$team->update([
|
||||
'custom_server_limit' => $quantity,
|
||||
]);
|
||||
}
|
||||
ServerLimitCheckJob::dispatch($team);
|
||||
}
|
||||
$subscription->update([
|
||||
@@ -210,7 +216,9 @@ class Stripe extends Controller
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
$team->trialEnded();
|
||||
if ($team) {
|
||||
$team->trialEnded();
|
||||
}
|
||||
$subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
|
||||
@@ -67,6 +67,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// Save original server between phases
|
||||
private Server $original_server;
|
||||
private Server $mainServer;
|
||||
private bool $is_this_additional_server = false;
|
||||
private ?ApplicationPreview $preview = null;
|
||||
private ?string $git_type = null;
|
||||
private bool $only_this_server = false;
|
||||
@@ -123,6 +124,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->rollback = $this->application_deployment_queue->rollback;
|
||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
$this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile';
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
|
||||
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||
@@ -136,6 +138,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
|
||||
$this->server = $this->mainServer = $this->destination->server;
|
||||
$this->serverUser = $this->server->user;
|
||||
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
||||
|
||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
|
||||
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
|
||||
@@ -149,28 +153,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
// Set preview fqdn
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->fqdn) {
|
||||
if (str($this->application->fqdn)->contains(',')) {
|
||||
$url = Url::fromString(str($this->application->fqdn)->explode(',')[0]);
|
||||
$preview_fqdn = getFqdnWithoutPort(str($this->application->fqdn)->explode(',')[0]);
|
||||
} else {
|
||||
$url = Url::fromString($this->application->fqdn);
|
||||
if (data_get($this->preview, 'fqdn')) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
}
|
||||
}
|
||||
$template = $this->application->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$this->preview->fqdn = $preview_fqdn;
|
||||
$this->preview->save();
|
||||
}
|
||||
$this->preview = $this->application->generate_preview_fqdn($this->pull_request_id);
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
|
||||
}
|
||||
@@ -284,7 +267,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function decide_what_to_do()
|
||||
{
|
||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
|
||||
if ($this->restart_only) {
|
||||
$this->just_restart();
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
@@ -306,7 +289,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function post_deployment()
|
||||
{
|
||||
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
GetContainersStatus::dispatch($this->server);
|
||||
// dispatch(new ContainerStatusJob($this->server));
|
||||
@@ -334,18 +316,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
$this->generate_image_names();
|
||||
|
||||
// Always rebuild dockerfile based container.
|
||||
// if (!$this->force_rebuild) {
|
||||
// $this->check_image_locally_or_remotely();
|
||||
// if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
// $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
// $this->generate_compose_file();
|
||||
// $this->push_to_docker_registry();
|
||||
// $this->rolling_update();
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
@@ -375,9 +345,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_start_command')) {
|
||||
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
|
||||
if (!str($this->docker_compose_custom_start_command)->contains('--project-directory')) {
|
||||
$this->docker_compose_custom_start_command = str($this->docker_compose_custom_start_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
|
||||
}
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_build_command')) {
|
||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||
if (!str($this->docker_compose_custom_build_command)->contains('--project-directory')) {
|
||||
$this->docker_compose_custom_build_command = str($this->docker_compose_custom_build_command)->replaceFirst('compose', 'compose --project-directory ' . $this->workdir)->value();
|
||||
}
|
||||
}
|
||||
if ($this->pull_request_id === 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
|
||||
@@ -393,15 +369,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->parseRawCompose();
|
||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||
$this->save_environment_variables();
|
||||
} else {
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id'));
|
||||
$this->save_environment_variables();
|
||||
if (!is_null($this->env_filename)) {
|
||||
$services = collect($composeFile['services']);
|
||||
$services = $services->map(function ($service, $name) {
|
||||
$service['env_file'] = [$this->env_filename];
|
||||
return $service;
|
||||
});
|
||||
$composeFile['services'] = $services->toArray();
|
||||
}
|
||||
if (is_null($composeFile)) {
|
||||
$this->application_deployment_queue->addLogEntry("Failed to parse docker-compose file.");
|
||||
$this->fail("Failed to parse docker-compose file.");
|
||||
return;
|
||||
}
|
||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||
}
|
||||
$this->docker_compose_base64 = base64_encode($yaml);
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), "hidden" => true
|
||||
]);
|
||||
$this->save_environment_variables();
|
||||
// Build new container to limit downtime.
|
||||
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
|
||||
|
||||
@@ -410,13 +400,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$command = "{$this->coolify_variables} docker compose";
|
||||
if ($this->env_filename) {
|
||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||
}
|
||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
$this->stop_running_container(force: true);
|
||||
|
||||
$this->application_deployment_queue->addLogEntry("Starting new application.");
|
||||
$networkId = $this->application->uuid;
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$networkId = "{$this->application->uuid}-{$this->pull_request_id}";
|
||||
@@ -441,9 +436,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else {
|
||||
$this->write_deployment_configurations();
|
||||
$server_workdir = $this->application->workdir();
|
||||
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
|
||||
|
||||
$command = "{$this->coolify_variables} docker compose";
|
||||
if ($this->env_filename) {
|
||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||
}
|
||||
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
||||
|
||||
$this->execute_remote_command(
|
||||
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
|
||||
["command" => $command, "hidden" => true],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -453,8 +454,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
);
|
||||
$this->write_deployment_configurations();
|
||||
} else {
|
||||
$command = "{$this->coolify_variables} docker compose";
|
||||
if ($this->env_filename) {
|
||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||
}
|
||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, $command), "hidden" => true],
|
||||
);
|
||||
$this->write_deployment_configurations();
|
||||
}
|
||||
@@ -473,16 +479,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->clone_repository();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
if ($this->should_skip_build()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -502,21 +503,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
ray('pushing to docker registry');
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
if ($this->should_skip_build()) {
|
||||
return;
|
||||
}
|
||||
if ($this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
|
||||
}
|
||||
}
|
||||
$this->clone_repository();
|
||||
$this->cleanup_git();
|
||||
@@ -535,15 +527,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
if ($this->should_skip_build()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -611,7 +598,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ray('additional_servers');
|
||||
$forceFail = true;
|
||||
}
|
||||
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
|
||||
if ($this->is_this_additional_server) {
|
||||
ray('this is an additional_servers, no pushy pushy');
|
||||
return;
|
||||
}
|
||||
@@ -626,8 +613,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
if ($this->application->docker_registry_image_tag) {
|
||||
// Tag image with latest
|
||||
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
|
||||
// Tag image with docker_registry_image_tag
|
||||
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||
@@ -637,7 +624,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
|
||||
} catch (Exception $e) {
|
||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
|
||||
if ($forceFail) {
|
||||
@@ -668,9 +654,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
} else {
|
||||
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
||||
if ($this->application->docker_registry_image_tag) {
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
}
|
||||
// if ($this->application->docker_registry_image_tag) {
|
||||
// $this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
// }
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build";
|
||||
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$this->dockerImageTag}";
|
||||
@@ -685,19 +671,42 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->check_image_locally_or_remotely();
|
||||
if ($this->should_skip_build()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
private function should_skip_build()
|
||||
{
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
$this->post_deployment();
|
||||
if ($this->is_this_additional_server) {
|
||||
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
if ($this->restart_only) {
|
||||
$this->post_deployment();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!$this->application->isConfigurationChanged()) {
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
return true;
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Configuration changed. Rebuilding image.");
|
||||
}
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application.");
|
||||
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Building new image.");
|
||||
}
|
||||
if ($this->restart_only) {
|
||||
$this->restart_only = false;
|
||||
$this->decide_what_to_do();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private function check_image_locally_or_remotely()
|
||||
{
|
||||
@@ -754,7 +763,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($env->version === '4.0.0-beta.239') {
|
||||
$real_value = $env->real_value;
|
||||
} else {
|
||||
if ($env->is_literal) {
|
||||
if ($env->is_literal || $env->is_multiline) {
|
||||
$real_value = '\'' . $real_value . '\'';
|
||||
} else {
|
||||
$real_value = escapeEnvVariables($env->real_value);
|
||||
@@ -787,7 +796,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||
$envs->push("COOLIFY_URL={$url}");
|
||||
}
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
}
|
||||
foreach ($sorted_environment_variables as $env) {
|
||||
@@ -795,10 +804,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($env->version === '4.0.0-beta.239') {
|
||||
$real_value = $env->real_value;
|
||||
} else {
|
||||
if ($env->is_literal) {
|
||||
if ($env->is_literal || $env->is_multiline) {
|
||||
$real_value = '\'' . $real_value . '\'';
|
||||
} else {
|
||||
$real_value = escapeEnvVariables($env->real_value);
|
||||
ray($real_value);
|
||||
}
|
||||
}
|
||||
$envs->push($env->key . '=' . $real_value);
|
||||
@@ -817,16 +827,29 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_filename = null;
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true
|
||||
]
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true
|
||||
]
|
||||
);
|
||||
$this->server = $this->build_server;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true
|
||||
]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
@@ -838,38 +861,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
|
||||
]
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
|
||||
]
|
||||
);
|
||||
$this->server = $this->build_server;
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d | tee $this->configuration_dir/{$this->env_filename} > /dev/null"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
// $this->execute_remote_command([
|
||||
// executeInDocker($this->deployment_uuid, "cat $this->workdir/.env 2>/dev/null || true"),
|
||||
// "hidden" => true,
|
||||
// "save" => "dotenv"
|
||||
// ]);
|
||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
||||
// $base64_dotenv = base64_encode($this->saved_outputs->get('dotenv')->value());
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "echo '{$base64_dotenv}' | base64 -d | tee $this->configuration_dir/.env > /dev/null"
|
||||
// ]
|
||||
// );
|
||||
// } else {
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "command" => "rm -f $this->configuration_dir/.env",
|
||||
// "hidden" => true,
|
||||
// "ignore_errors" => true
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1035,7 +1040,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->clone_repository();
|
||||
$this->set_base_dir();
|
||||
$this->cleanup_git();
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->generate_nixpacks_confs();
|
||||
@@ -1052,14 +1056,32 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function create_workdir()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
||||
],
|
||||
[
|
||||
"command" => "mkdir -p {$this->configuration_dir}"
|
||||
],
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "mkdir -p {$this->configuration_dir}"
|
||||
],
|
||||
);
|
||||
$this->server = $this->build_server;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
||||
],
|
||||
[
|
||||
"command" => "mkdir -p {$this->configuration_dir}"
|
||||
],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
||||
],
|
||||
[
|
||||
"command" => "mkdir -p {$this->configuration_dir}"
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
private function prepare_builder_image()
|
||||
{
|
||||
@@ -1139,10 +1161,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]));
|
||||
}
|
||||
}
|
||||
private function set_base_dir()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
|
||||
}
|
||||
private function set_coolify_variables()
|
||||
{
|
||||
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
|
||||
@@ -1195,7 +1213,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
}
|
||||
ray("GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->fullRepoUrl} {$local_branch}");
|
||||
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||
$this->application_deployment_queue->commit = $this->commit;
|
||||
@@ -1211,7 +1228,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head.");
|
||||
}
|
||||
ray($importCommands);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
$importCommands, "hidden" => true
|
||||
@@ -1225,8 +1241,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"save" => "commit_message"
|
||||
]
|
||||
);
|
||||
ray($this->saved_outputs->get('commit_message'));
|
||||
raY($this->commit);
|
||||
if ($this->saved_outputs->get('commit_message')) {
|
||||
$commit_message = str($this->saved_outputs->get('commit_message'))->limit(47);
|
||||
$this->application_deployment_queue->commit_message = $commit_message->value();
|
||||
@@ -1359,6 +1373,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$onlyPort = $ports[0];
|
||||
}
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$persistent_file_volumes = $this->application->fileStorages()->get();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
// $environment_variables = $this->generate_environment_variables($ports);
|
||||
$this->save_environment_variables();
|
||||
@@ -1559,6 +1574,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($persistent_file_volumes) > 0) {
|
||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
||||
return "$item->fs_path:$item->mount_path";
|
||||
})->toArray();
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
@@ -1797,7 +1817,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
} else {
|
||||
// Pure Dockerfile based deployment
|
||||
if ($this->application->dockerfile) {
|
||||
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
if ($this->force_rebuild) {
|
||||
$build_command = "docker build --no-cache --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
} else {
|
||||
$build_command = "docker build --pull {$this->buildTarget} {$this->addHosts} --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||
}
|
||||
$base64_build_command = base64_encode($build_command);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -1925,11 +1949,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$value = escapeshellarg($env->real_value);
|
||||
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
|
||||
$value = str_replace("\n", "\\\n", $value);
|
||||
}
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$value = escapeshellarg($env->real_value);
|
||||
if (str($value)->contains("\n") && data_get($env, 'is_multiline') === true) {
|
||||
$value = str_replace("\n", "\\\n", $value);
|
||||
}
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
}
|
||||
@@ -1945,10 +1975,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
|
||||
$value = str_replace("\n", "\\\n", $env->real_value);
|
||||
} else {
|
||||
$value = $env->real_value;
|
||||
}
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
if (str($env->real_value)->contains("\n") && data_get($env, 'is_multiline') === true) {
|
||||
$value = str_replace("\n", "\\\n", $env->real_value);
|
||||
} else {
|
||||
$value = $env->real_value;
|
||||
}
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
@@ -1968,7 +2008,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if ($containers->count() == 0) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output).");
|
||||
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output/errors).");
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
@@ -1991,6 +2031,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if (empty($this->application->post_deployment_command)) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output).");
|
||||
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
@@ -1999,11 +2040,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
|
||||
$cmd = "sh -c '" . str_replace("'", "'\''", $this->application->post_deployment_command) . "'";
|
||||
$exec = "docker exec {$containerName} {$cmd}";
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
'command' => $exec, 'hidden' => true
|
||||
],
|
||||
);
|
||||
try {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output'
|
||||
],
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$post_deployment_command_output = $this->saved_outputs->get('post-deployment-command-output');
|
||||
if ($post_deployment_command_output) {
|
||||
$this->application_deployment_queue->addLogEntry("Post-deployment command failed.");
|
||||
$this->application_deployment_queue->addLogEntry($post_deployment_command_output, 'stderr');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
try {
|
||||
if ($this->application->is_public_repository()) {
|
||||
ray('Public repository. Skipping comment update.');
|
||||
return;
|
||||
}
|
||||
if ($this->status === ProcessStatus::CLOSED) {
|
||||
@@ -59,7 +60,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public function handle(): void
|
||||
public function handle()
|
||||
{
|
||||
// ray("checking log drain statuses for {$this->server->id}");
|
||||
try {
|
||||
@@ -80,9 +80,9 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
if (!isCloud()) send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
handleError($e);
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
|
||||
public $timeout = 600;
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(private bool $force = false)
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
UpdateCoolify::run(force: $this->force, async: false);
|
||||
UpdateCoolify::run();
|
||||
}
|
||||
}
|
||||
|
||||
59
app/Jobs/PullCoolifyImageJob.php
Normal file
59
app/Jobs/PullCoolifyImageJob.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PullCoolifyImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1000;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (isDev() || isCloud()) {
|
||||
return;
|
||||
}
|
||||
$server = Server::findOrFail(0);
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
}
|
||||
$latest_version = get_latest_version_of_coolify();
|
||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||
|
||||
$settings = InstanceSettings::get();
|
||||
$current_version = config('version');
|
||||
if (!$settings->is_auto_update_enabled) {
|
||||
return;
|
||||
}
|
||||
if ($latest_version === $current_version) {
|
||||
return;
|
||||
}
|
||||
if (version_compare($latest_version, $current_version, '<')) {
|
||||
return;
|
||||
}
|
||||
instant_remote_process([
|
||||
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
|
||||
"bash /data/coolify/source/upgrade.sh $latest_version"
|
||||
], $server);
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PullTemplatesAndVersions implements ShouldQueue, ShouldBeEncrypted
|
||||
class PullTemplatesFromCDN implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
@@ -23,21 +23,6 @@ class PullTemplatesAndVersions implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (!isDev() && !isCloud()) {
|
||||
ray('PullTemplatesAndVersions versions.json');
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
} else {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
}
|
||||
try {
|
||||
if (!isDev()) {
|
||||
ray('PullTemplatesAndVersions service-templates');
|
||||
40
app/Jobs/PullVersionsFromCDN.php
Normal file
40
app/Jobs/PullVersionsFromCDN.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class PullVersionsFromCDN implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 10;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (!isDev() && !isCloud()) {
|
||||
ray('PullTemplatesAndVersions versions.json');
|
||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||
} else {
|
||||
send_internal_notification('PullTemplatesAndVersions failed with: ' . $response->status() . ' ' . $response->body());
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,17 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (config('coolify.is_sentinel_enabled')) {
|
||||
$this->server->checkSentinel();
|
||||
}
|
||||
$this->check_docker_engine();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
try {
|
||||
// $this->check_docker_engine();
|
||||
} catch (\Throwable $e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
private function check_docker_engine()
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ class General extends Component
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
|
||||
$this->initLoadingCompose = true;
|
||||
$this->dispatch('info', 'Loading docker compose file...');
|
||||
$this->dispatch('info', 'Loading docker compose file.');
|
||||
}
|
||||
|
||||
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||
@@ -287,7 +287,7 @@ class General extends Component
|
||||
if ($this->application->additional_servers->count() === 0) {
|
||||
foreach ($domains as $domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +352,7 @@ class General extends Component
|
||||
$domain = data_get($service, 'domain');
|
||||
if ($domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$showToaster && $this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
}
|
||||
check_domain_usage(resource: $this->application);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ class Heading extends Component
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
|
||||
"compose_loaded" => '$refresh',
|
||||
"update_links" => '$refresh',
|
||||
];
|
||||
}
|
||||
public function mount()
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Previews extends Component
|
||||
@@ -16,6 +18,9 @@ class Previews extends Component
|
||||
public Collection $pull_requests;
|
||||
public int $rate_limit_remaining;
|
||||
|
||||
protected $rules = [
|
||||
'application.previews.*.fqdn' => 'string|nullable',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->pull_requests = collect();
|
||||
@@ -33,7 +38,88 @@ class Previews extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function save_preview($preview_id)
|
||||
{
|
||||
try {
|
||||
$success = true;
|
||||
$preview = $this->application->previews->find($preview_id);
|
||||
if (data_get_str($preview, 'fqdn')->isNotEmpty()) {
|
||||
$preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim();
|
||||
$preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim();
|
||||
$preview->fqdn = str($preview->fqdn)->trim()->lower();
|
||||
if (!validate_dns_entry($preview->fqdn, $this->application->destination->server)) {
|
||||
$this->dispatch('error', "Validating DNS failed.", "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$success = false;
|
||||
}
|
||||
check_domain_usage(resource: $this->application, domain: $preview->fqdn);
|
||||
}
|
||||
|
||||
if (!$preview) {
|
||||
throw new \Exception('Preview not found');
|
||||
}
|
||||
$success && $preview->save();
|
||||
$success && $this->dispatch('success', 'Preview saved.<br><br>Do not forget to redeploy the preview to apply the changes.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function generate_preview($preview_id)
|
||||
{
|
||||
$preview = $this->application->previews->find($preview_id);
|
||||
if (!$preview) {
|
||||
$this->dispatch('error', 'Preview not found.');
|
||||
return;
|
||||
}
|
||||
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
|
||||
|
||||
$url = Url::fromString($fqdn);
|
||||
$template = $this->application->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$preview->fqdn = $preview_fqdn;
|
||||
$preview->save();
|
||||
$this->dispatch('success', 'Domain generated.');
|
||||
}
|
||||
public function add(int $pull_request_id, string|null $pull_request_html_url = null)
|
||||
{
|
||||
try {
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found && !is_null($pull_request_html_url)) {
|
||||
$found = ApplicationPreview::create([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'docker_compose_domains' => $this->application->docker_compose_domains,
|
||||
]);
|
||||
}
|
||||
$found->generate_preview_fqdn_compose();
|
||||
$this->application->refresh();
|
||||
} else {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found && !is_null($pull_request_html_url)) {
|
||||
$found = ApplicationPreview::create([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
$this->application->generate_preview_fqdn($pull_request_id);
|
||||
$this->application->refresh();
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Preview added.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function deploy(int $pull_request_id, string|null $pull_request_html_url = null)
|
||||
{
|
||||
try {
|
||||
@@ -82,17 +168,31 @@ class Previews extends Component
|
||||
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();
|
||||
GetContainersStatus::dispatchSync($this->application->destination->server);
|
||||
$this->dispatch('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function previewRefresh()
|
||||
public function delete(int $pull_request_id)
|
||||
{
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
try {
|
||||
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();
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Preview deleted.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
app/Livewire/Project/Application/PreviewsCompose.php
Normal file
56
app/Livewire/Project/Application/PreviewsCompose.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\ApplicationPreview;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class PreviewsCompose extends Component
|
||||
{
|
||||
public $service;
|
||||
public $serviceName;
|
||||
public ApplicationPreview $preview;
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.previews-compose');
|
||||
}
|
||||
public function save()
|
||||
{
|
||||
$domain = data_get($this->service, 'domain');
|
||||
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
|
||||
$docker_compose_domains = json_decode($docker_compose_domains, true);
|
||||
$docker_compose_domains[$this->serviceName]['domain'] = $domain;
|
||||
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->preview->save();
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Domain saved.');
|
||||
}
|
||||
public function generate()
|
||||
{
|
||||
$domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect();
|
||||
$domain = $domains->first(function ($_, $key) {
|
||||
return $key === $this->serviceName;
|
||||
});
|
||||
if ($domain) {
|
||||
$domain = data_get($domain, 'domain');
|
||||
$url = Url::fromString($domain);
|
||||
$template = $this->preview->application->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$docker_compose_domains = data_get($this->preview, 'docker_compose_domains');
|
||||
$docker_compose_domains = json_decode($docker_compose_domains, true);
|
||||
$docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn;
|
||||
$this->preview->docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->preview->save();
|
||||
}
|
||||
$this->dispatch('update_links');
|
||||
$this->dispatch('success', 'Domain generated.');
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use Livewire\Component;
|
||||
class Index extends Component
|
||||
{
|
||||
public $database;
|
||||
public $s3s;
|
||||
public function mount()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
@@ -36,7 +35,6 @@ class Index extends Component
|
||||
]);
|
||||
}
|
||||
$this->database = $database;
|
||||
$this->s3s = currentTeam()->s3s;
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ class BackupExecutions extends Component
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
if ($this->backup) {
|
||||
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
|
||||
$this->executions = $this->backup->executions()->get()->sortBy('created_at');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,14 @@ namespace App\Livewire\Project\Database\Clickhouse;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public StandaloneClickhouse $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -43,10 +45,11 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,7 @@ class CreateScheduledBackup extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->s3s = currentTeam()->s3s;
|
||||
if ($this->s3s->count() > 0) {
|
||||
$this->s3_storage_id = $this->s3s->first()->id;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Dragonfly;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -12,6 +13,7 @@ class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public Server $server;
|
||||
public StandaloneDragonfly $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -41,10 +43,11 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Keydb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -12,6 +13,7 @@ class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public Server $server;
|
||||
public StandaloneKeydb $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -43,10 +45,12 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mariadb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -12,6 +13,7 @@ class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public Server $server;
|
||||
public StandaloneMariadb $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -50,10 +52,12 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mongodb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -12,6 +13,7 @@ class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public Server $server;
|
||||
public StandaloneMongodb $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -48,11 +50,13 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
|
||||
}
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Mysql;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneMysql;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -13,6 +14,7 @@ class General extends Component
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public StandaloneMysql $database;
|
||||
public Server $server;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
@@ -50,11 +52,12 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
}
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Postgresql;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -13,6 +14,7 @@ use function Aws\filter;
|
||||
class General extends Component
|
||||
{
|
||||
public StandalonePostgresql $database;
|
||||
public Server $server;
|
||||
public string $new_filename;
|
||||
public string $new_content;
|
||||
public ?string $db_url = null;
|
||||
@@ -57,11 +59,12 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
}
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Database\Redis;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -12,6 +13,7 @@ class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public Server $server;
|
||||
public StandaloneRedis $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
@@ -43,10 +45,12 @@ class General extends Component
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->server = data_get($this->database,'destination.server');
|
||||
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Application;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\GitlabApp;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Carbon\Carbon;
|
||||
@@ -33,6 +34,8 @@ class PublicGitRepository extends Component
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
public bool $new_compose_services = false;
|
||||
|
||||
protected $rules = [
|
||||
'repository_url' => 'required|url',
|
||||
'port' => 'required|numeric',
|
||||
@@ -177,6 +180,31 @@ class PublicGitRepository extends Component
|
||||
$project = Project::where('uuid', $project_uuid)->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $environment_name)->first();
|
||||
|
||||
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services ) {
|
||||
$server = $destination->server;
|
||||
$new_service = [
|
||||
'name' => 'service' . str()->random(10),
|
||||
'docker_compose_raw' => 'coolify',
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => $server->id,
|
||||
];
|
||||
if ($this->git_source === 'other') {
|
||||
$new_service['git_repository'] = $this->git_repository;
|
||||
$new_service['git_branch'] = $this->git_branch;
|
||||
} else {
|
||||
$new_service['git_repository'] = $this->git_repository;
|
||||
$new_service['git_branch'] = $this->git_branch;
|
||||
$new_service['source_id'] = $this->git_source->id;
|
||||
$new_service['source_type'] = $this->git_source->getMorphClass();
|
||||
}
|
||||
$service = Service::create($new_service);
|
||||
return redirect()->route('project.service.configuration', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
if ($this->git_source === 'other') {
|
||||
$application_init = [
|
||||
'name' => generate_random_name(),
|
||||
|
||||
@@ -91,7 +91,7 @@ class Select extends Component
|
||||
});
|
||||
} else {
|
||||
$this->search = null;
|
||||
$this->allServices = get_service_templates();
|
||||
$this->allServices = get_service_templates($force);
|
||||
$this->services = $this->allServices->filter(function ($service, $key) {
|
||||
return str_contains(strtolower($key), strtolower($this->search));
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ use Livewire\Component;
|
||||
class Create extends Component
|
||||
{
|
||||
public $type;
|
||||
public $project;
|
||||
public function mount()
|
||||
{
|
||||
$type = str(request()->query('type'));
|
||||
@@ -20,6 +21,7 @@ class Create extends Component
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->project = $project;
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first();
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
|
||||
@@ -7,13 +7,20 @@ use App\Models\LocalFileVolume;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
public LocalFileVolume $fileStorage;
|
||||
public ServiceApplication|ServiceDatabase|StandaloneClickhouse|Application $resource;
|
||||
public ServiceApplication|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase|Application $resource;
|
||||
public string $fs_path;
|
||||
public ?string $workdir = null;
|
||||
|
||||
@@ -27,7 +34,7 @@ class FileStorage extends Component
|
||||
{
|
||||
$this->resource = $this->fileStorage->service;
|
||||
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
|
||||
$this->workdir = $this->resource->service->workdir();
|
||||
$this->workdir = $this->resource->service?->workdir();
|
||||
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
|
||||
} else {
|
||||
$this->workdir = null;
|
||||
|
||||
@@ -42,7 +42,7 @@ class StackForm extends Component
|
||||
$this->validationAttributes["fields.$key.value"] = $fieldKey;
|
||||
}
|
||||
}
|
||||
$this->fields = $this->fields->sortDesc();
|
||||
$this->fields = $this->fields->sortBy('name');
|
||||
}
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
@@ -52,7 +52,7 @@ class StackForm extends Component
|
||||
public function instantSave()
|
||||
{
|
||||
$this->service->save();
|
||||
$this->dispatch('success', 'Service settings saved.');
|
||||
$this->dispatch('success', 'Service settings saved.');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
@@ -43,6 +43,11 @@ class GetLogs extends Component
|
||||
$this->showTimeStamps = $this->resource->is_include_timestamps;
|
||||
}
|
||||
}
|
||||
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
|
||||
if (str($this->container)->contains('-pr-')) {
|
||||
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function doSomethingWithThisChunkOfOutput($output)
|
||||
@@ -77,13 +82,6 @@ class GetLogs extends Component
|
||||
if (!$this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
|
||||
if (str($this->container)->contains('-pr-')) {
|
||||
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
|
||||
} else {
|
||||
$this->pull_request = 'branch';
|
||||
}
|
||||
}
|
||||
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
|
||||
if (!$this->numberOfLines) {
|
||||
$this->numberOfLines = 1000;
|
||||
|
||||
@@ -103,6 +103,14 @@ class Logs extends Component
|
||||
}
|
||||
}
|
||||
$this->containers = $this->containers->sort();
|
||||
if (data_get($this->query,'pull_request_id')) {
|
||||
$this->containers = $this->containers->filter(function ($container) {
|
||||
return str_contains($container, $this->query['pull_request_id']);
|
||||
});
|
||||
ray($this->containers);
|
||||
|
||||
}
|
||||
|
||||
$this->loadMetrics();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -3,21 +3,31 @@
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\LocalFileVolume;
|
||||
use Livewire\Component;
|
||||
|
||||
class Add extends Component
|
||||
{
|
||||
public $resource;
|
||||
public $uuid;
|
||||
public $parameters;
|
||||
public $isSwarm = false;
|
||||
public string $name;
|
||||
public string $mount_path;
|
||||
public ?string $host_path = null;
|
||||
public string $file_storage_path;
|
||||
public ?string $file_storage_content = null;
|
||||
public string $file_storage_directory_source;
|
||||
public string $file_storage_directory_destination;
|
||||
|
||||
public $rules = [
|
||||
'name' => 'required|string',
|
||||
'mount_path' => 'required|string',
|
||||
'host_path' => 'string|nullable',
|
||||
'file_storage_path' => 'string',
|
||||
'file_storage_content' => 'nullable|string',
|
||||
'file_storage_directory_source' => 'string',
|
||||
'file_storage_directory_destination' => 'string',
|
||||
];
|
||||
|
||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
||||
@@ -26,10 +36,16 @@ class Add extends Component
|
||||
'name' => 'name',
|
||||
'mount_path' => 'mount',
|
||||
'host_path' => 'host',
|
||||
'file_storage_path' => 'file storage path',
|
||||
'file_storage_content' => 'file storage content',
|
||||
'file_storage_directory_source' => 'file storage directory source',
|
||||
'file_storage_directory_destination' => 'file storage directory destination',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->file_storage_directory_source = application_configuration_dir() . "/{$this->resource->uuid}";
|
||||
$this->uuid = $this->resource->uuid;
|
||||
$this->parameters = get_route_parameters();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$applicationUuid = $this->parameters['application_uuid'];
|
||||
@@ -43,18 +59,75 @@ class Add extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
public function submitFileStorage()
|
||||
{
|
||||
try {
|
||||
$this->validate($this->rules);
|
||||
$this->validate([
|
||||
'file_storage_path' => 'string',
|
||||
'file_storage_content' => 'nullable|string',
|
||||
]);
|
||||
$this->file_storage_path = trim($this->file_storage_path);
|
||||
$this->file_storage_path = str($this->file_storage_path)->start('/')->value();
|
||||
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||
$fs_path = application_configuration_dir() . '/' . $this->resource->uuid . $this->file_storage_path;
|
||||
}
|
||||
LocalFileVolume::create(
|
||||
[
|
||||
'fs_path' => $fs_path,
|
||||
'mount_path' => $this->file_storage_path,
|
||||
'content' => $this->file_storage_content,
|
||||
'is_directory' => false,
|
||||
'resource_id' => $this->resource->id,
|
||||
'resource_type' => get_class($this->resource)
|
||||
],
|
||||
);
|
||||
$this->dispatch('refresh_storages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
}
|
||||
public function submitFileStorageDirectory()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'file_storage_directory_source' => 'string',
|
||||
'file_storage_directory_destination' => 'string',
|
||||
]);
|
||||
$this->file_storage_directory_source = trim($this->file_storage_directory_source);
|
||||
$this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value();
|
||||
$this->file_storage_directory_destination = trim($this->file_storage_directory_destination);
|
||||
$this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value();
|
||||
LocalFileVolume::create(
|
||||
[
|
||||
'fs_path' => $this->file_storage_directory_source,
|
||||
'mount_path' => $this->file_storage_directory_destination,
|
||||
'is_directory' => true,
|
||||
'resource_id' => $this->resource->id,
|
||||
'resource_type' => get_class($this->resource)
|
||||
],
|
||||
);
|
||||
$this->dispatch('refresh_storages');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
}
|
||||
public function submitPersistentVolume()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'name' => 'required|string',
|
||||
'mount_path' => 'required|string',
|
||||
'host_path' => 'string|nullable',
|
||||
]);
|
||||
$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);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ class Show extends Component
|
||||
public bool $isReadOnly = false;
|
||||
public ?string $modalId = null;
|
||||
public bool $isFirst = true;
|
||||
public bool $isService = false;
|
||||
public ?string $startedAt = null;
|
||||
|
||||
protected $rules = [
|
||||
'storage.name' => 'required|string',
|
||||
|
||||
@@ -70,9 +70,8 @@ class Configuration extends Component
|
||||
$this->validate();
|
||||
|
||||
if ($this->settings->is_dns_validation_enabled && $this->settings->fqdn) {
|
||||
ray('asdf');
|
||||
if (!validate_dns_entry($this->settings->fqdn, $this->server)) {
|
||||
$this->dispatch('error', "Validating DNS ({$this->settings->fqdn}) failed.<br><br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$this->dispatch('error', "Validating DNS failed.<br><br>Make sure you have added the DNS records correctly.<br><br>{$this->settings->fqdn}->{$this->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
|
||||
$error_show = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,16 @@ use Livewire\Component;
|
||||
class Change extends Component
|
||||
{
|
||||
public string $webhook_endpoint;
|
||||
public ?string $ipv4;
|
||||
public ?string $ipv6;
|
||||
public ?string $fqdn;
|
||||
public ?string $ipv4 = null;
|
||||
public ?string $ipv6 = null;
|
||||
public ?string $fqdn = null;
|
||||
|
||||
public ?bool $default_permissions = true;
|
||||
public ?bool $preview_deployment_permissions = true;
|
||||
public ?bool $administration = false;
|
||||
|
||||
public $parameters;
|
||||
public ?GithubApp $github_app;
|
||||
public ?GithubApp $github_app = null;
|
||||
public string $name;
|
||||
public bool $is_system_wide;
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ class InviteLink extends Component
|
||||
public string $email;
|
||||
public string $role = 'member';
|
||||
|
||||
protected $rules = [
|
||||
'email' => 'required|email',
|
||||
'role' => 'required|string',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->email = isDev() ? 'test3@example.com' : '';
|
||||
@@ -34,6 +38,7 @@ class InviteLink extends Component
|
||||
private function generate_invite_link(bool $sendEmail = false)
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$member_emails = currentTeam()->members()->get()->pluck('email');
|
||||
if ($member_emails->contains($this->email)) {
|
||||
return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
|
||||
|
||||
@@ -5,11 +5,9 @@ namespace App\Livewire;
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
|
||||
use Livewire\Component;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
|
||||
class Upgrade extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public bool $showProgress = false;
|
||||
public bool $updateInProgress = false;
|
||||
public bool $isUpgradeAvailable = false;
|
||||
@@ -31,9 +29,8 @@ class Upgrade extends Component
|
||||
if ($this->updateInProgress) {
|
||||
return;
|
||||
}
|
||||
$this->rateLimit(1, 60);
|
||||
$this->updateInProgress = true;
|
||||
UpdateCoolify::run(force: true, async: true);
|
||||
UpdateCoolify::run(manual_update: true);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -669,7 +669,9 @@ class Application extends BaseModel
|
||||
$git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}";
|
||||
$fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}";
|
||||
}
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
} else {
|
||||
@@ -859,14 +861,10 @@ class Application extends BaseModel
|
||||
|
||||
instant_remote_process($commands, $this->destination->server, false);
|
||||
}
|
||||
function parseCompose(int $pull_request_id = 0)
|
||||
function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
|
||||
{
|
||||
if ($this->docker_compose_raw) {
|
||||
$mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id);
|
||||
if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) {
|
||||
parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true);
|
||||
}
|
||||
return $mainCompose;
|
||||
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
|
||||
} else {
|
||||
return collect([]);
|
||||
}
|
||||
@@ -888,7 +886,8 @@ class Application extends BaseModel
|
||||
// }
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid}",
|
||||
"cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
"git sparse-checkout init --cone",
|
||||
"git sparse-checkout set {$fileList->implode(' ')}",
|
||||
@@ -899,29 +898,15 @@ class Application extends BaseModel
|
||||
if (!$composeFileContent) {
|
||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||
$this->save();
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
]);
|
||||
instant_remote_process($commands, $this->destination->server, false);
|
||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||
} 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}",
|
||||
@@ -1063,4 +1048,30 @@ class Application extends BaseModel
|
||||
}
|
||||
}
|
||||
}
|
||||
function generate_preview_fqdn(int $pull_request_id)
|
||||
{
|
||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id);
|
||||
if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) {
|
||||
if (str($this->fqdn)->contains(',')) {
|
||||
$url = Url::fromString(str($this->fqdn)->explode(',')[0]);
|
||||
$preview_fqdn = getFqdnWithoutPort(str($this->fqdn)->explode(',')[0]);
|
||||
} else {
|
||||
$url = Url::fromString($this->fqdn);
|
||||
if (data_get($preview, 'fqdn')) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($preview, 'fqdn'));
|
||||
}
|
||||
}
|
||||
$template = $this->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$preview->fqdn = $preview_fqdn;
|
||||
$preview->save();
|
||||
}
|
||||
return $preview;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ApplicationPreview extends BaseModel
|
||||
{
|
||||
protected $guarded = [];
|
||||
@@ -34,4 +37,26 @@ class ApplicationPreview extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
function generate_preview_fqdn_compose()
|
||||
{
|
||||
$domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect();
|
||||
foreach ($domains as $service_name => $domain) {
|
||||
$domain = data_get($domain, 'domain');
|
||||
$url = Url::fromString($domain);
|
||||
$template = $this->application->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$docker_compose_domains = data_get($this, 'docker_compose_domains');
|
||||
$docker_compose_domains = json_decode($docker_compose_domains, true);
|
||||
$docker_compose_domains[$service_name]['domain'] = $preview_fqdn;
|
||||
$docker_compose_domains = json_encode($docker_compose_domains);
|
||||
$this->docker_compose_domains = $docker_compose_domains;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ class EnvironmentVariable extends Model
|
||||
return true;
|
||||
}
|
||||
$found_in_compose = false;
|
||||
$found_in_args = false;
|
||||
$resource = $this->resource();
|
||||
$compose = data_get($resource, 'docker_compose_raw');
|
||||
if (!$compose) {
|
||||
@@ -102,21 +103,35 @@ class EnvironmentVariable extends Model
|
||||
}
|
||||
foreach ($services as $service) {
|
||||
$environments = collect(data_get($service, 'environment'));
|
||||
if ($environments->isEmpty()) {
|
||||
$args = collect(data_get($service, 'build.args'));
|
||||
if ($environments->isEmpty() && $args->isEmpty()) {
|
||||
$found_in_compose = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$found_in_compose = $environments->contains(function ($item) {
|
||||
if (str($item)->contains('=')) {
|
||||
$item = str($item)->before('=');
|
||||
}
|
||||
return strpos($item, $this->key) !== false;
|
||||
});
|
||||
|
||||
if ($found_in_compose) {
|
||||
break;
|
||||
}
|
||||
|
||||
$found_in_args = $args->contains(function ($item) {
|
||||
if (str($item)->contains('=')) {
|
||||
$item = str($item)->before('=');
|
||||
}
|
||||
return strpos($item, $this->key) !== false;
|
||||
});
|
||||
|
||||
if ($found_in_args) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $found_in_compose;
|
||||
return $found_in_compose || $found_in_args;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -897,7 +897,9 @@ $schema://$host {
|
||||
}
|
||||
public function validateDockerEngineVersion()
|
||||
{
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this, false);
|
||||
$dockerVersionRaw = instant_remote_process(["docker version --format json"], $this, false);
|
||||
$dockerVersionJson = json_decode($dockerVersionRaw, true);
|
||||
$dockerVersion = data_get($dockerVersionJson, 'Server.Version', '0.0.0');
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$this->settings->is_usable = false;
|
||||
|
||||
@@ -472,6 +472,72 @@ class Service extends BaseModel
|
||||
}
|
||||
$fields->put('Admin', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('vaultwarden'):
|
||||
$data = collect([]);
|
||||
|
||||
$DATABASE_URL = $this->environment_variables()->where('key', 'DATABASE_URL')->first();
|
||||
$ADMIN_TOKEN = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_ADMIN')->first();
|
||||
$SIGNUP_ALLOWED = $this->environment_variables()->where('key', 'SIGNUP_ALLOWED')->first();
|
||||
$PUSH_ENABLED = $this->environment_variables()->where('key', 'PUSH_ENABLED')->first();
|
||||
$PUSH_INSTALLATION_ID = $this->environment_variables()->where('key', 'PUSH_SERVICE_ID')->first();
|
||||
$PUSH_INSTALLATION_KEY = $this->environment_variables()->where('key', 'PUSH_SERVICE_KEY')->first();
|
||||
|
||||
if ($DATABASE_URL) {
|
||||
$data = $data->merge([
|
||||
'Database URL' => [
|
||||
'key' => data_get($DATABASE_URL, 'key'),
|
||||
'value' => data_get($DATABASE_URL, 'value'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($ADMIN_TOKEN) {
|
||||
$data = $data->merge([
|
||||
'Admin Password' => [
|
||||
'key' => data_get($ADMIN_TOKEN, 'key'),
|
||||
'value' => data_get($ADMIN_TOKEN, 'value'),
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($SIGNUP_ALLOWED) {
|
||||
$data = $data->merge([
|
||||
'Signup Allowed' => [
|
||||
'key' => data_get($SIGNUP_ALLOWED, 'key'),
|
||||
'value' => data_get($SIGNUP_ALLOWED, 'value'),
|
||||
'rules' => 'required|string|in:true,false',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($PUSH_ENABLED) {
|
||||
$data = $data->merge([
|
||||
'Push Enabled' => [
|
||||
'key' => data_get($PUSH_ENABLED, 'key'),
|
||||
'value' => data_get($PUSH_ENABLED, 'value'),
|
||||
'rules' => 'required|string|in:true,false',
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($PUSH_INSTALLATION_ID) {
|
||||
$data = $data->merge([
|
||||
'Push Installation ID' => [
|
||||
'key' => data_get($PUSH_INSTALLATION_ID, 'key'),
|
||||
'value' => data_get($PUSH_INSTALLATION_ID, 'value'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($PUSH_INSTALLATION_KEY) {
|
||||
$data = $data->merge([
|
||||
'Push Installation Key' => [
|
||||
'key' => data_get($PUSH_INSTALLATION_KEY, 'key'),
|
||||
'value' => data_get($PUSH_INSTALLATION_KEY, 'value'),
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$fields->put('Vaultwarden', $data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$databases = $this->databases()->get();
|
||||
|
||||
@@ -40,6 +40,10 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return 'service';
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function workdir() {
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ class ServiceDatabase extends BaseModel
|
||||
}
|
||||
return "{$realIp}:{$port}";
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function workdir() {
|
||||
return service_configuration_dir() . "/{$this->service->uuid}";
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ class TelegramChannel
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
|
||||
break;
|
||||
case 'App\Notifications\Application\StatusChanged':
|
||||
case 'App\Notifications\Container\ContainerRestarted':
|
||||
case 'App\Notifications\Container\ContainerStopped':
|
||||
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
|
||||
break;
|
||||
case 'App\Notifications\Application\DeploymentSuccess':
|
||||
|
||||
@@ -57,6 +57,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||
$deployment->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
));
|
||||
@@ -69,6 +70,7 @@ function queue_next_deployment(Application $application)
|
||||
$next_found->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
));
|
||||
|
||||
@@ -38,7 +38,7 @@ const SPECIFIC_SERVICES = [
|
||||
// Based on /etc/os-release
|
||||
const SUPPORTED_OS = [
|
||||
'ubuntu debian raspbian',
|
||||
'centos fedora rhel ol rocky amzn',
|
||||
'centos fedora rhel ol rocky amzn almalinux',
|
||||
'sles opensuse-leap opensuse-tumbleweed'
|
||||
];
|
||||
|
||||
|
||||
@@ -465,13 +465,34 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
$appUuid = $appUuid . '-pr-' . $pull_request_id;
|
||||
}
|
||||
$labels = collect([]);
|
||||
if ($application->fqdn) {
|
||||
if ($pull_request_id !== 0) {
|
||||
if ($pull_request_id === 0) {
|
||||
if ($application->fqdn) {
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
// Add Caddy labels
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if (data_get($preview,'fqdn')) {
|
||||
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
|
||||
} else {
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
$domains = collect([]);
|
||||
}
|
||||
// Add Traefik labels
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
@@ -490,6 +511,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
// ray($generatedEnv);
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $fqdn . ':' . $port . $path;
|
||||
$generatedEnv->value = $fqdn . $path;
|
||||
$generatedEnv->save();
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$variableName = $variableName . "_$port";
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $url . ':' . $port . $path;
|
||||
$generatedEnv->value = $url . $path;
|
||||
$generatedEnv->save();
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$env->value = $host . $path;
|
||||
$env->save();
|
||||
}
|
||||
$port_env->value = $host . ':' . $port . $path;
|
||||
$port_env->value = $host . $path;
|
||||
$port_env->save();
|
||||
}
|
||||
$port_envs_url = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'like', "SERVICE_URL_%_$port")->get();
|
||||
@@ -171,7 +171,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$env->value = $url . $path;
|
||||
$env->save();
|
||||
}
|
||||
$port_env_url->value = $url . ':' . $port . $path;
|
||||
$port_env_url->value = $url . $path;
|
||||
$port_env_url->save();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -465,25 +465,24 @@ function sslip(Server $server)
|
||||
return "http://{$server->ip}.sslip.io";
|
||||
}
|
||||
|
||||
function get_service_templates()
|
||||
function get_service_templates(bool $force = false): Collection
|
||||
{
|
||||
// if (isDev()) {
|
||||
// $services = File::get(base_path('templates/service-templates.json'));
|
||||
// $services = collect(json_decode($services))->sortKeys();
|
||||
// } else {
|
||||
// try {
|
||||
// $response = Http::retry(3, 50)->get(config('constants.services.official'));
|
||||
// if ($response->failed()) {
|
||||
// return collect([]);
|
||||
// }
|
||||
// $services = $response->json();
|
||||
// $services = collect($services)->sortKeys();
|
||||
// } catch (\Throwable $e) {
|
||||
// $services = collect([]);
|
||||
// }
|
||||
// }
|
||||
$services = File::get(base_path('templates/service-templates.json'));
|
||||
return collect(json_decode($services))->sortKeys();
|
||||
if ($force) {
|
||||
try {
|
||||
$response = Http::retry(3, 50)->get(config('constants.services.official'));
|
||||
if ($response->failed()) {
|
||||
return collect([]);
|
||||
}
|
||||
$services = $response->json();
|
||||
return collect($services);
|
||||
} catch (\Throwable $e) {
|
||||
$services = File::get(base_path('templates/service-templates.json'));
|
||||
return collect(json_decode($services))->sortKeys();
|
||||
}
|
||||
} else {
|
||||
$services = File::get(base_path('templates/service-templates.json'));
|
||||
return collect(json_decode($services))->sortKeys();
|
||||
}
|
||||
}
|
||||
|
||||
function getResourceByUuid(string $uuid, ?int $teamId = null)
|
||||
@@ -577,6 +576,13 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore alias
|
||||
if ($networkDetails['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
@@ -619,6 +625,13 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore alias
|
||||
if ($networkDetails['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
@@ -643,9 +656,9 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
return $topLevelNetworks->keys();
|
||||
}
|
||||
}
|
||||
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, bool $is_pr = false)
|
||||
|
||||
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
|
||||
{
|
||||
// ray()->clearAll();
|
||||
if ($resource->getMorphClass() === 'App\Models\Service') {
|
||||
if ($resource->docker_compose_raw) {
|
||||
try {
|
||||
@@ -667,6 +680,16 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
}
|
||||
}
|
||||
$definedNetwork = collect([$resource->uuid]);
|
||||
if ($topLevelVolumes->count() > 0) {
|
||||
$tempTopLevelVolumes = collect([]);
|
||||
foreach ($topLevelVolumes as $volumeName => $volume) {
|
||||
if (is_null($volume)) {
|
||||
continue;
|
||||
}
|
||||
$tempTopLevelVolumes->put($volumeName, $volume);
|
||||
}
|
||||
$topLevelVolumes = collect($tempTopLevelVolumes);
|
||||
}
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
|
||||
// Workarounds for beta users.
|
||||
if ($serviceName === 'registry') {
|
||||
@@ -765,6 +788,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore alias
|
||||
if ($networkDetails['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
@@ -816,7 +846,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
// default:
|
||||
// ipv4_address: 192.168.203.254
|
||||
// $networks->put($serviceNetwork, null);
|
||||
ray($key);
|
||||
$networks->put($key, $serviceNetwork);
|
||||
}
|
||||
}
|
||||
@@ -880,6 +909,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
]
|
||||
);
|
||||
} else if ($type->value() === 'volume') {
|
||||
if ($topLevelVolumes->has($source->value())) {
|
||||
$v = $topLevelVolumes->get($source->value());
|
||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||
return $volume;
|
||||
}
|
||||
}
|
||||
$slugWithoutUuid = Str::slug($source, '-');
|
||||
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
|
||||
if (is_string($volume)) {
|
||||
@@ -1247,26 +1282,29 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$isSameDockerComposeFile = false;
|
||||
if ($resource->dockerComposePrLocation() === $resource->dockerComposeLocation()) {
|
||||
$isSameDockerComposeFile = true;
|
||||
$is_pr = false;
|
||||
}
|
||||
if ($is_pr) {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_pr_raw);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$yaml = Yaml::parse($resource->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
$server = $resource->destination->server;
|
||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||
if ($pull_request_id !== 0) {
|
||||
$topLevelVolumes = collect([]);
|
||||
}
|
||||
|
||||
if ($topLevelVolumes->count() > 0) {
|
||||
$tempTopLevelVolumes = collect([]);
|
||||
foreach ($topLevelVolumes as $volumeName => $volume) {
|
||||
if (is_null($volume)) {
|
||||
continue;
|
||||
}
|
||||
$tempTopLevelVolumes->put($volumeName, $volume);
|
||||
}
|
||||
$topLevelVolumes = collect($tempTopLevelVolumes);
|
||||
}
|
||||
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$services = data_get($yaml, 'services');
|
||||
|
||||
@@ -1282,7 +1320,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($pull_request_id !== 0) {
|
||||
$definedNetwork = collect(["{$resource->uuid}-$pull_request_id"]);
|
||||
}
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id) {
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id, $preview_id) {
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
$servicePorts = collect(data_get($service, 'ports', []));
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
@@ -1330,13 +1368,36 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($pull_request_id !== 0) {
|
||||
$name = $name . "-pr-$pull_request_id";
|
||||
$volume = str("$name:$mount");
|
||||
$topLevelVolumes->put($name, [
|
||||
'name' => $name,
|
||||
]);
|
||||
if ($topLevelVolumes->has($name)) {
|
||||
$v = $topLevelVolumes->get($name);
|
||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||
// Do nothing
|
||||
} else {
|
||||
if (is_null(data_get($v, 'name'))) {
|
||||
data_set($v, 'name', $name);
|
||||
data_set($topLevelVolumes, $name, $v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$topLevelVolumes->put($name, [
|
||||
'name' => $name,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$topLevelVolumes->put($name->value(), [
|
||||
'name' => $name->value(),
|
||||
]);
|
||||
if ($topLevelVolumes->has($name->value())) {
|
||||
$v = $topLevelVolumes->get($name->value());
|
||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||
// Do nothing
|
||||
} else {
|
||||
if (is_null(data_get($v, 'name'))) {
|
||||
data_set($topLevelVolumes, $name->value(), $v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$topLevelVolumes->put($name->value(), [
|
||||
'name' => $name->value(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1380,9 +1441,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
data_set($volume, 'source', $source . ':' . $target);
|
||||
}
|
||||
if (!str($source)->startsWith('/')) {
|
||||
$topLevelVolumes->put($source, [
|
||||
'name' => $source,
|
||||
]);
|
||||
if ($topLevelVolumes->has($source)) {
|
||||
$v = $topLevelVolumes->get($source);
|
||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||
// Do nothing
|
||||
} else {
|
||||
if (is_null(data_get($v, 'name'))) {
|
||||
data_set($v, 'name', $source);
|
||||
data_set($topLevelVolumes, $source, $v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$topLevelVolumes->put($source, [
|
||||
'name' => $source,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1409,6 +1482,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
if ($networkName === 'default') {
|
||||
continue;
|
||||
}
|
||||
// ignore alias
|
||||
if ($networkDetails['aliases'] ?? false) {
|
||||
continue;
|
||||
}
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
@@ -1640,21 +1720,32 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($fqdns) {
|
||||
$fqdns = str($fqdns)->explode(',');
|
||||
if ($pull_request_id !== 0) {
|
||||
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
|
||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
|
||||
$url = Url::fromString($fqdn);
|
||||
$template = $resource->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$preview->fqdn = $preview_fqdn;
|
||||
$preview->save();
|
||||
return $preview_fqdn;
|
||||
});
|
||||
$preview = $resource->previews()->find($preview_id);
|
||||
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
|
||||
if ($docker_compose_domains->count() > 0) {
|
||||
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
|
||||
if ($found_fqdn) {
|
||||
$fqdns = collect($found_fqdn);
|
||||
} else {
|
||||
$fqdns = collect([]);
|
||||
}
|
||||
} else {
|
||||
$fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) {
|
||||
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id);
|
||||
$url = Url::fromString($fqdn);
|
||||
$template = $resource->preview_url_template;
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$random = new Cuid2(7);
|
||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||
$preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn);
|
||||
$preview_fqdn = "$schema://$preview_fqdn";
|
||||
$preview->fqdn = $preview_fqdn;
|
||||
$preview->save();
|
||||
return $preview_fqdn;
|
||||
});
|
||||
}
|
||||
}
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $resource->uuid,
|
||||
@@ -1721,13 +1812,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
} else {
|
||||
if ($is_pr) {
|
||||
$resource->docker_compose_pr_raw = Yaml::dump($yaml, 10, 2);
|
||||
$resource->docker_compose_pr = Yaml::dump($finalServices, 10, 2);
|
||||
} else {
|
||||
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
}
|
||||
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$resource->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
}
|
||||
$resource->save();
|
||||
return collect($finalServices);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"laravel/horizon": "^5.23.1",
|
||||
"laravel/prompts": "^0.1.6",
|
||||
"laravel/sanctum": "^v3.2.1",
|
||||
"laravel/socialite": "^5.12",
|
||||
"laravel/socialite": "^v5.14.0",
|
||||
"laravel/tinker": "^v2.8.1",
|
||||
"laravel/ui": "^4.2",
|
||||
"lcobucci/jwt": "^5.0.0",
|
||||
|
||||
1106
composer.lock
generated
1106
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.286',
|
||||
'release' => '4.0.0-beta.296',
|
||||
// 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.286';
|
||||
return '4.0.0-beta.296';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('application_previews', function (Blueprint $table) {
|
||||
$table->text('docker_compose_domains')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_previews', function (Blueprint $table) {
|
||||
$table->dropColumn('docker_compose_domains');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('application_previews', function (Blueprint $table) {
|
||||
$table->string('pull_request_issue_comment_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_previews', function (Blueprint $table) {
|
||||
$table->integer('pull_request_issue_comment_id')->nullable()->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
32
database/seeders/new_services.php
Normal file
32
database/seeders/new_services.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->string('git_repository')->nullable();
|
||||
$table->string('git_branch')->nullable();
|
||||
$table->nullableMorphs('source');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->dropColumn('git_repository');
|
||||
$table->dropColumn('git_branch');
|
||||
$table->dropMorphs('source');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2,7 +2,7 @@ services:
|
||||
coolify:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/dev-ssu/Dockerfile
|
||||
dockerfile: ./docker/dev/Dockerfile
|
||||
ports:
|
||||
- "${APP_PORT:-8000}:80"
|
||||
environment:
|
||||
|
||||
@@ -13,6 +13,7 @@ services:
|
||||
- /data/coolify/backups:/var/www/html/storage/app/backups
|
||||
- /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance
|
||||
environment:
|
||||
- PHP_MEMORY_LIMIT
|
||||
- APP_ID
|
||||
- APP_ENV=production
|
||||
- APP_DEBUG
|
||||
|
||||
@@ -2,15 +2,15 @@ FROM alpine:3.17
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
# https://download.docker.com/linux/static/stable/
|
||||
ARG DOCKER_VERSION=26.1.2
|
||||
ARG DOCKER_VERSION=26.1.3
|
||||
# https://github.com/docker/compose/releases
|
||||
ARG DOCKER_COMPOSE_VERSION=2.27.0
|
||||
ARG DOCKER_COMPOSE_VERSION=2.27.1
|
||||
# https://github.com/docker/buildx/releases
|
||||
ARG DOCKER_BUILDX_VERSION=0.14.0
|
||||
ARG DOCKER_BUILDX_VERSION=0.14.1
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=0.33.2
|
||||
ARG PACK_VERSION=0.34.1
|
||||
# https://github.com/railwayapp/nixpacks/releases
|
||||
ARG NIXPACKS_VERSION=1.21.3
|
||||
ARG NIXPACKS_VERSION=1.24.1
|
||||
|
||||
USER root
|
||||
WORKDIR /artifacts
|
||||
|
||||
@@ -18,9 +18,9 @@ RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
|
||||
# Coolify requirements
|
||||
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
|
||||
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
||||
COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/
|
||||
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
|
||||
|
||||
COPY docker/dev-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||
|
||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
||||
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
||||
@@ -31,10 +31,10 @@ RUN apt-get update
|
||||
RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
|
||||
|
||||
# Coolify requirements
|
||||
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
|
||||
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim
|
||||
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
||||
|
||||
COPY docker/prod-ssu/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||
COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||
|
||||
COPY --from=base --chown=9999:9999 /var/www/html .
|
||||
|
||||
@@ -42,7 +42,7 @@ COPY --chown=9999:9999 . .
|
||||
RUN composer dump-autoload
|
||||
|
||||
COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build
|
||||
COPY --chmod=755 docker/prod-ssu/etc/s6-overlay/ /etc/s6-overlay/
|
||||
COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/
|
||||
|
||||
RUN php artisan route:cache
|
||||
RUN php artisan view:cache
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user