mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-24 12:33:17 +00:00
Compare commits
272 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b983b23e7e | ||
|
|
0b81e77a94 | ||
|
|
b8cf314bfe | ||
|
|
4a3338e59c | ||
|
|
5b8538c0f4 | ||
|
|
88d6320d08 | ||
|
|
651c9c2c9b | ||
|
|
8dd45cd388 | ||
|
|
126ac354d5 | ||
|
|
024769c402 | ||
|
|
5acf141669 | ||
|
|
92e3e8ab7b | ||
|
|
187a29c666 | ||
|
|
4c24631795 | ||
|
|
7d6bd10cca | ||
|
|
f8c86769a7 | ||
|
|
e0b0dda382 | ||
|
|
b8708f086e | ||
|
|
3539e4dce9 | ||
|
|
5fdadcf557 | ||
|
|
acb3f01f79 | ||
|
|
83becdb19d | ||
|
|
e3e8fe7895 | ||
|
|
6ddff8fae1 | ||
|
|
f5cb2dbdcf | ||
|
|
fe19769d82 | ||
|
|
45e404b15b | ||
|
|
5bdaa68368 | ||
|
|
d903a377bf | ||
|
|
8e7745f4c1 | ||
|
|
a9ea6330d9 | ||
|
|
bfb0260550 | ||
|
|
bba1cb3832 | ||
|
|
29ad2144b7 | ||
|
|
38d367e709 | ||
|
|
0e81ff970f | ||
|
|
00feef40a3 | ||
|
|
dfba593072 | ||
|
|
c770c8d988 | ||
|
|
0f071031a9 | ||
|
|
99efa857f4 | ||
|
|
80035395ff | ||
|
|
d3490e1c95 | ||
|
|
dab13c92eb | ||
|
|
1f18542960 | ||
|
|
8f21ea9367 | ||
|
|
73e64d9052 | ||
|
|
6cdd87da41 | ||
|
|
2a5d49f9b3 | ||
|
|
cc7ba9eb9f | ||
|
|
c4cc42c8d5 | ||
|
|
2d0838b112 | ||
|
|
07b94a8e48 | ||
|
|
4b08abc144 | ||
|
|
93e4fc2f32 | ||
|
|
6dd86eec30 | ||
|
|
a7ab5d55d3 | ||
|
|
689547463c | ||
|
|
8a0046c571 | ||
|
|
fb3991321a | ||
|
|
ca6543a919 | ||
|
|
364a6aa3a2 | ||
|
|
82b0667277 | ||
|
|
74c126c731 | ||
|
|
d87a0fe74f | ||
|
|
9a858f628d | ||
|
|
fed01fa9d2 | ||
|
|
e1468da36a | ||
|
|
ddfc1440cd | ||
|
|
5fc46384e6 | ||
|
|
48d9df1e43 | ||
|
|
6b62d91f82 | ||
|
|
e6ca8cd167 | ||
|
|
059748ad3b | ||
|
|
a334f998a2 | ||
|
|
53a5ccef31 | ||
|
|
9eea73cefb | ||
|
|
b210e1f243 | ||
|
|
ef3202101c | ||
|
|
22d5159d16 | ||
|
|
1cbd30bd9e | ||
|
|
ad54358de7 | ||
|
|
3689b58b92 | ||
|
|
5a4180a750 | ||
|
|
047922b13a | ||
|
|
798d747164 | ||
|
|
29676ffb22 | ||
|
|
8a50b063d4 | ||
|
|
3d2444ab2e | ||
|
|
7c395edab4 | ||
|
|
7e7f322e21 | ||
|
|
9350fb4b97 | ||
|
|
59c3cc6ce1 | ||
|
|
d7001937ac | ||
|
|
3fe58ec66b | ||
|
|
ed12f73483 | ||
|
|
6914280fb1 | ||
|
|
3dd5546369 | ||
|
|
576bff1af9 | ||
|
|
3c4243d854 | ||
|
|
23d121d67a | ||
|
|
548304765c | ||
|
|
037ba3ff79 | ||
|
|
48b4c17391 | ||
|
|
6acc0e6025 | ||
|
|
43d7f746e4 | ||
|
|
146fee14e5 | ||
|
|
bde7fb2acb | ||
|
|
08a729dc7b | ||
|
|
7554de5993 | ||
|
|
3d7295fec3 | ||
|
|
fd814abd8a | ||
|
|
4c38a59995 | ||
|
|
642a6e3203 | ||
|
|
9edbc15828 | ||
|
|
43eb2fb00b | ||
|
|
9a899deeb8 | ||
|
|
9e1a7d5d9a | ||
|
|
5bdbab7276 | ||
|
|
13bceb934f | ||
|
|
78b194cb16 | ||
|
|
3616fc8ca9 | ||
|
|
10e307f92b | ||
|
|
01f027ac1b | ||
|
|
dadc7aaf08 | ||
|
|
b96807d34c | ||
|
|
45b736bb01 | ||
|
|
3d873a79a0 | ||
|
|
6869c582ff | ||
|
|
9b9e5e939c | ||
|
|
f626c15ecc | ||
|
|
8df1fe2e60 | ||
|
|
fd2a533057 | ||
|
|
0a6401f990 | ||
|
|
1326fcb345 | ||
|
|
8b8e534598 | ||
|
|
0b518a3b76 | ||
|
|
f357f40fc7 | ||
|
|
93fb14884e | ||
|
|
26ccc4afb4 | ||
|
|
5fda1bb932 | ||
|
|
409ba8a1bb | ||
|
|
49f5240ff8 | ||
|
|
0c3ed3d393 | ||
|
|
6e3dc474f2 | ||
|
|
d3eb87561e | ||
|
|
8b58c8f856 | ||
|
|
8c60ef5bd6 | ||
|
|
1d59383c78 | ||
|
|
60f590454d | ||
|
|
dcb61a553e | ||
|
|
e06e31642f | ||
|
|
9dfce48380 | ||
|
|
8eed87e2f7 | ||
|
|
d56d4eb8fc | ||
|
|
fd32cd04ab | ||
|
|
1d3b7ffd3b | ||
|
|
0b5baf60a5 | ||
|
|
bc31df6fb2 | ||
|
|
818399bc23 | ||
|
|
e7fdff0f69 | ||
|
|
6312c0ba84 | ||
|
|
44efe0b5e1 | ||
|
|
de7d584648 | ||
|
|
b9f12d2586 | ||
|
|
c76e8bb0de | ||
|
|
3b655f8e3f | ||
|
|
2b9df41444 | ||
|
|
628fec6904 | ||
|
|
f36135cbfc | ||
|
|
75fe005055 | ||
|
|
8ff7aeb78b | ||
|
|
f1a9e28d5a | ||
|
|
843cd90ee5 | ||
|
|
1cbfd03912 | ||
|
|
ce60a39dc5 | ||
|
|
f1e4395a83 | ||
|
|
52fd7ad571 | ||
|
|
5f797ec0ae | ||
|
|
21e77bf0c1 | ||
|
|
0686e48e89 | ||
|
|
1cef233db2 | ||
|
|
907e52572c | ||
|
|
795c8abf64 | ||
|
|
cc641d8cba | ||
|
|
d4668ef44a | ||
|
|
e8b539c3bd | ||
|
|
6555f0b50c | ||
|
|
bb05058dda | ||
|
|
9667cd4a7a | ||
|
|
3ae9501814 | ||
|
|
73d0948734 | ||
|
|
c3e2a741ea | ||
|
|
4792146f1d | ||
|
|
09b9305aa3 | ||
|
|
ff7d0d442d | ||
|
|
9a127bdc80 | ||
|
|
919e88afb4 | ||
|
|
1d1ec20cb7 | ||
|
|
5c29ecdf10 | ||
|
|
9e09c449cf | ||
|
|
09bcd693f5 | ||
|
|
0c15e45419 | ||
|
|
31cbf552a2 | ||
|
|
f7853ee174 | ||
|
|
de3a7b6eca | ||
|
|
b56c7c34cb | ||
|
|
49845f3da7 | ||
|
|
987409bae4 | ||
|
|
07d8461f96 | ||
|
|
f255a71434 | ||
|
|
2a2818ac0d | ||
|
|
fd3cdc2c7d | ||
|
|
84c3f832ae | ||
|
|
70c28fceeb | ||
|
|
f2c4f83f5a | ||
|
|
c8dd6f07ac | ||
|
|
561e424a7d | ||
|
|
c46d38907e | ||
|
|
5c334bbac6 | ||
|
|
9628072b0c | ||
|
|
39ecff9f90 | ||
|
|
d1daec060a | ||
|
|
fb5bea7f91 | ||
|
|
efc3ea6e40 | ||
|
|
ecefb9e1f5 | ||
|
|
a4dea2009a | ||
|
|
829e41f93f | ||
|
|
e7e3adc7fb | ||
|
|
a993fef235 | ||
|
|
81df71416c | ||
|
|
40af7aa025 | ||
|
|
050155328b | ||
|
|
376c081bed | ||
|
|
9f5e1fa9e3 | ||
|
|
7d139fd33b | ||
|
|
b75a2857a0 | ||
|
|
ab6c1ddc20 | ||
|
|
dc0b0980a9 | ||
|
|
788d1711db | ||
|
|
d92dc4c5e6 | ||
|
|
4bf34aea62 | ||
|
|
7e9a54ce67 | ||
|
|
f8c19e1fb3 | ||
|
|
27c1bda09b | ||
|
|
1af7ffcdc4 | ||
|
|
39647367a5 | ||
|
|
ae4b263810 | ||
|
|
8901bb5df8 | ||
|
|
053aa25d2c | ||
|
|
7a7157c155 | ||
|
|
0c5e8600bd | ||
|
|
048e153025 | ||
|
|
e7cafe6850 | ||
|
|
1385a86084 | ||
|
|
348923ae02 | ||
|
|
7d754558b0 | ||
|
|
744609e7e9 | ||
|
|
9bd05b65a3 | ||
|
|
d42934f258 | ||
|
|
ff752e2411 | ||
|
|
4120fba9a8 | ||
|
|
6ecb9c21ce | ||
|
|
01f7b07fa3 | ||
|
|
54d8cb9027 | ||
|
|
238337fecb | ||
|
|
7a51acbf8d | ||
|
|
fb478c79b3 | ||
|
|
abcc004953 | ||
|
|
2edf71a0dd | ||
|
|
cbec39099a | ||
|
|
dba5499182 |
12
.env.windows-docker-desktop.example
Normal file
12
.env.windows-docker-desktop.example
Normal file
@@ -0,0 +1,12 @@
|
||||
IS_WINDOWS_DOCKER_DESKTOP=true
|
||||
|
||||
APP_ID=coolify-windows-docker-desktop
|
||||
APP_NAME=Coolify
|
||||
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
||||
|
||||
DB_PASSWORD=coolify
|
||||
REDIS_PASSWORD=coolify
|
||||
|
||||
PUSHER_APP_ID=coolify
|
||||
PUSHER_APP_KEY=coolify
|
||||
PUSHER_APP_SECRET=coolify
|
||||
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
84
.github/workflows/coolify-testing-host.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Coolify Testing Host (v4-non-prod)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "next" ]
|
||||
paths:
|
||||
- .github/workflows/coolify-testing-host.yml
|
||||
- docker/testing-host/Dockerfile
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify-testing-host"
|
||||
|
||||
jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/testing-host/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
aarch64:
|
||||
runs-on: [ self-hosted, arm64 ]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
no-cache: true
|
||||
context: .
|
||||
file: docker/testing-host/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [ amd64, aarch64 ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
20
.github/workflows/development-build.yml
vendored
20
.github/workflows/development-build.yml
vendored
@@ -15,15 +15,15 @@ jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -36,15 +36,15 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -59,13 +59,13 @@ jobs:
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
||||
22
.github/workflows/production-build.yml
vendored
22
.github/workflows/production-build.yml
vendored
@@ -12,9 +12,9 @@ jobs:
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -34,9 +34,9 @@ jobs:
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
run: |
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
@@ -61,13 +61,13 @@ jobs:
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
21
README.md
21
README.md
@@ -10,6 +10,17 @@ No vendor lock-in, which means that all the configuration for your applications/
|
||||
|
||||
For more information, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
# Support
|
||||
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
# Donations
|
||||
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
||||
|
||||
@@ -66,16 +77,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
||||
- Better support
|
||||
- Less maintenance for you
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
# Support
|
||||
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
# Recognitions
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
@@ -10,10 +12,20 @@ class StopApplication
|
||||
use AsAction;
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||
} else {
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||
return;
|
||||
}
|
||||
|
||||
$servers = collect([]);
|
||||
$servers->push($application->destination->server);
|
||||
$application->additional_servers->map(function ($server) use ($servers) {
|
||||
$servers->push($server);
|
||||
});
|
||||
foreach ($servers as $server) {
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
@@ -25,20 +37,7 @@ class StopApplication
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: make notification for application
|
||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||
}
|
||||
// Delete Preview Deployments
|
||||
$previewDeployments = $application->previews;
|
||||
foreach ($previewDeployments as $previewDeployment) {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
38
app/Actions/Application/StopApplicationOneServer.php
Normal file
38
app/Actions/Application/StopApplicationOneServer.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplicationOneServer
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Application $application, Server $server)
|
||||
{
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
return;
|
||||
}
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
try {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$server
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class StartMariadb
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '{$database->name} started.'";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class StartMariadb
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
|
||||
@@ -122,7 +122,7 @@ class StartMongodb
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '{$database->name} started.'";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ class StartMongodb
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||
|
||||
@@ -106,7 +106,7 @@ class StartMysql
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '{$database->name} started.'";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class StartMysql
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||
|
||||
@@ -128,7 +128,7 @@ class StartPostgresql
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '{$database->name} started.'";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ class StartPostgresql
|
||||
ray('Generate Environment Variables')->green();
|
||||
ray($this->database->runtime_environment_variables)->green();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
|
||||
@@ -117,7 +117,7 @@ class StartRedis
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '{$database->name} started.'";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ class StartRedis
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
|
||||
@@ -17,6 +17,9 @@ class StopDatabase
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$server
|
||||
|
||||
@@ -27,7 +27,7 @@ class StartProxy
|
||||
$server->save();
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||
@@ -35,7 +35,7 @@ class StartProxy
|
||||
]);
|
||||
} else {
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
|
||||
@@ -43,6 +43,7 @@ class InstallDocker
|
||||
"echo 'Restarting Docker Engine...'",
|
||||
"ls -l /tmp"
|
||||
]);
|
||||
return remote_process($command, $server);
|
||||
} else {
|
||||
if ($supported_os_type->contains('debian')) {
|
||||
$command = $command->merge([
|
||||
@@ -89,7 +90,6 @@ class InstallDocker
|
||||
"echo 'Done!'",
|
||||
]);
|
||||
}
|
||||
|
||||
return remote_process($command, $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,9 +128,9 @@ class InstallLogDrain
|
||||
if ($type !== 'custom') {
|
||||
$parsers = base64_encode("
|
||||
[PARSER]
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
");
|
||||
}
|
||||
$compose = base64_encode("
|
||||
@@ -198,7 +198,7 @@ Files:
|
||||
}
|
||||
$restart_command = [
|
||||
"echo 'Stopping old Fluent Bit'",
|
||||
"cd $config_path && docker rm -f coolify-log-drain || true",
|
||||
"cd $config_path && docker compose down --remove-orphans || true",
|
||||
"echo 'Starting Fluent Bit'",
|
||||
"cd $config_path && docker compose up -d --remove-orphans",
|
||||
];
|
||||
|
||||
@@ -25,7 +25,6 @@ class UpdateCoolify
|
||||
CleanupDocker::run($this->server, false);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latestVersion = 'next';
|
||||
@@ -44,7 +43,7 @@ class UpdateCoolify
|
||||
}
|
||||
$this->update();
|
||||
}
|
||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
||||
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
|
||||
} catch (\Throwable $e) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($e->getMessage());
|
||||
|
||||
@@ -10,35 +10,45 @@ class DeleteService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
StopService::run($service);
|
||||
}
|
||||
$storagesToDelete = collect([]);
|
||||
try {
|
||||
$server = data_get($service, 'server');
|
||||
if ($server->isFunctional()) {
|
||||
$storagesToDelete = collect([]);
|
||||
|
||||
$service->environment_variables()->delete();
|
||||
$commands = [];
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
$service->environment_variables()->delete();
|
||||
$commands = [];
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
}
|
||||
$application->forceDelete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
} finally {
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
$application->forceDelete();
|
||||
}
|
||||
$database->forceDelete();
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$database->forceDelete();
|
||||
}
|
||||
foreach ($service->scheduled_tasks as $task) {
|
||||
$task->delete();
|
||||
}
|
||||
$service->tags()->detach();
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
}
|
||||
$commands[] = "docker rm -f $service->uuid";
|
||||
|
||||
instant_remote_process($commands, $server, false);
|
||||
|
||||
$service->forceDelete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class StartService
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
||||
$commands[] = "echo 'Starting service $service->name on {$service->server->name}.'";
|
||||
$commands[] = "echo Starting service.";
|
||||
$commands[] = "echo 'Pulling images.'";
|
||||
$commands[] = "docker compose pull";
|
||||
$commands[] = "echo 'Starting containers.'";
|
||||
|
||||
@@ -10,20 +10,31 @@ class StopService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
ray('Stopping service: ' . $service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
$application->update(['status' => 'exited']);
|
||||
try {
|
||||
$server = $service->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
ray('Stopping service: ' . $service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
// TODO: make notification for databases
|
||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||
} catch (\Exception $e) {
|
||||
echo $e->getMessage();
|
||||
ray($e->getMessage());
|
||||
return $e->getMessage();
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
// TODO: make notification for databases
|
||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
55
app/Actions/Shared/ComplexStatusCheck.php
Normal file
55
app/Actions/Shared/ComplexStatusCheck.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Shared;
|
||||
|
||||
use App\Models\Application;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class ComplexStatusCheck
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$servers = $application->additional_servers;
|
||||
$servers->push($application->destination->server);
|
||||
foreach ($servers as $server) {
|
||||
$is_main_server = $application->destination->server->id === $server->id;
|
||||
if (!$server->isFunctional()) {
|
||||
if ($is_main_server) {
|
||||
$application->update(['status' => 'exited:unhealthy']);
|
||||
continue;
|
||||
} else {
|
||||
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$container = instant_remote_process(["docker container inspect $(docker container ls -q --filter 'label=coolify.applicationId={$application->id}' --filter 'label=coolify.pullRequestId=0') --format '{{json .}}'"], $server, false);
|
||||
$container = format_docker_command_output_to_json($container);
|
||||
if ($container->count() === 1) {
|
||||
$container = $container->first();
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
if ($is_main_server) {
|
||||
$statusFromDb = $application->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$application->update(['status' => "$containerStatus:$containerHealth"]);
|
||||
}
|
||||
} else {
|
||||
$additional_server = $application->additional_servers()->wherePivot('server_id', $server->id);
|
||||
$statusFromDb = $additional_server->first()->pivot->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
$additional_server->updateExistingPivot($server->id, ['status' => "$containerStatus:$containerHealth"]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($is_main_server) {
|
||||
$application->update(['status' => 'exited:unhealthy']);
|
||||
continue;
|
||||
} else {
|
||||
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
308
app/Console/Commands/CleanupStuckedResources.php
Normal file
308
app/Console/Commands/CleanupStuckedResources.php
Normal file
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CleanupStuckedResources extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:stucked-resources';
|
||||
protected $description = 'Cleanup Stucked Resources';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
ray('Running cleanup stucked resources.');
|
||||
echo "Running cleanup stucked resources.\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
{
|
||||
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
echo "Deleting stuck application: {$application->name}\n";
|
||||
$application->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||
$postgresql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($redis as $redis) {
|
||||
echo "Deleting stuck redis: {$redis->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||
$mongodb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mysqls as $mysql) {
|
||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||
$mysql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||
$mariadb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($services as $service) {
|
||||
echo "Deleting stuck service: {$service->name}\n";
|
||||
$service->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceApps as $serviceApp) {
|
||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||
$serviceApp->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceDbs as $serviceDb) {
|
||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||
$serviceDb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$scheduled_tasks = ScheduledTask::all();
|
||||
foreach ($scheduled_tasks as $scheduled_task) {
|
||||
if (!$scheduled_task->service && !$scheduled_task->application) {
|
||||
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
|
||||
$scheduled_task->delete();
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
echo 'Application without environment: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in redis: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CleanupUnreachableServers extends Command
|
||||
{
|
||||
protected $signature = 'cleanup:unreachable-servers';
|
||||
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running unreachable server cleanup...\n";
|
||||
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
||||
if ($servers->count() > 0) {
|
||||
foreach ($servers as $server) {
|
||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||
$server->update([
|
||||
'ip' => '1.2.3.4'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,57 +4,54 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init {--cleanup}';
|
||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->alive();
|
||||
$cleanup = $this->option('cleanup');
|
||||
if ($cleanup) {
|
||||
echo "Running cleanups...\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
$full_cleanup = $this->option('full-cleanup');
|
||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||
if ($cleanup_deployments) {
|
||||
echo "Running cleanup deployments.\n";
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
return;
|
||||
}
|
||||
if ($full_cleanup) {
|
||||
// Required for falsely deleted coolify db
|
||||
$this->restore_coolify_db_backup();
|
||||
|
||||
// $this->cleanup_ssh();
|
||||
}
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
$settings = InstanceSettings::get();
|
||||
if (!is_null(env('AUTOUPDATE', null))) {
|
||||
if (env('AUTOUPDATE') == true) {
|
||||
$settings->update(['is_auto_update_enabled' => true]);
|
||||
} else {
|
||||
$settings->update(['is_auto_update_enabled' => false]);
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
$this->call('cleanup:queue');
|
||||
$this->call('cleanup:stucked-resources');
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
$settings = InstanceSettings::get();
|
||||
if (!is_null(env('AUTOUPDATE', null))) {
|
||||
if (env('AUTOUPDATE') == true) {
|
||||
$settings->update(['is_auto_update_enabled' => true]);
|
||||
} else {
|
||||
$settings->update(['is_auto_update_enabled' => false]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->call('cleanup:queue');
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
$this->call('cleanup:stucked-resources');
|
||||
}
|
||||
private function restore_coolify_db_backup()
|
||||
{
|
||||
@@ -128,8 +125,13 @@ class Init extends Command
|
||||
// Cleanup any failed deployments
|
||||
|
||||
try {
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
||||
foreach ($halted_deployments as $deployment) {
|
||||
if (isCloud()) {
|
||||
return;
|
||||
}
|
||||
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||
foreach ($queued_inprogress_deployments as $deployment) {
|
||||
ray($deployment->id, $deployment->status);
|
||||
echo "Cleaning up deployment: {$deployment->id}\n";
|
||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$deployment->save();
|
||||
}
|
||||
@@ -137,273 +139,4 @@ class Init extends Command
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
{
|
||||
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
echo "Deleting stuck application: {$application->name}\n";
|
||||
$application->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||
$postgresql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($redis as $redis) {
|
||||
echo "Deleting stuck redis: {$redis->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||
$mongodb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mysqls as $mysql) {
|
||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||
$mysql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||
$mariadb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($services as $service) {
|
||||
echo "Deleting stuck service: {$service->name}\n";
|
||||
$service->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceApps as $serviceApp) {
|
||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||
$serviceApp->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceDbs as $serviceDb) {
|
||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||
$serviceDb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
if (!data_get($application, 'environment')) {
|
||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in redis: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
try {
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class SyncBunny extends Command
|
||||
|
||||
$versions = "versions.json";
|
||||
|
||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
||||
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||
$headers = [
|
||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||
'Accept' => 'application/json',
|
||||
@@ -76,23 +76,26 @@ class SyncBunny extends Command
|
||||
}
|
||||
if ($only_template) {
|
||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||
}
|
||||
if ($only_version) {
|
||||
$this->info('About to sync versions.json to BunnyCDN.');
|
||||
}
|
||||
$confirmed = confirm('Are you sure you want to sync?');
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
if ($only_template) {
|
||||
$confirmed = confirm("Are you sure you want to sync?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||
]);
|
||||
$this->info('Service template uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
if ($only_version) {
|
||||
} else if ($only_version) {
|
||||
$this->info('About to sync versions.json to BunnyCDN.');
|
||||
$file = file_get_contents("$parent_dir/$versions");
|
||||
$json = json_decode($file, true);
|
||||
$actual_version = data_get($json, 'coolify.v4.version');
|
||||
|
||||
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
@@ -101,6 +104,7 @@ class SyncBunny extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Console;
|
||||
|
||||
use App\Jobs\CheckLogDrainContainerJob;
|
||||
use App\Jobs\CleanupInstanceStuffsJob;
|
||||
use App\Jobs\ComplexContainerStatusJob;
|
||||
use App\Jobs\DatabaseBackupJob;
|
||||
use App\Jobs\ScheduledTaskJob;
|
||||
use App\Jobs\InstanceAutoUpdateJob;
|
||||
@@ -36,6 +37,8 @@ class Kernel extends ConsoleKernel
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
|
||||
@@ -89,7 +92,6 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
ray('no scheduled backups');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
@@ -115,12 +117,11 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$scheduled_tasks = ScheduledTask::all();
|
||||
if ($scheduled_tasks->isEmpty()) {
|
||||
ray('no scheduled tasks');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_tasks as $scheduled_task) {
|
||||
$service = $scheduled_task->service()->get();
|
||||
$application = $scheduled_task->application()->get();
|
||||
$service = $scheduled_task->service;
|
||||
$application = $scheduled_task->application;
|
||||
|
||||
if (!$application && !$service) {
|
||||
ray('application/service attached to scheduled task does not exist');
|
||||
|
||||
@@ -10,4 +10,5 @@ enum ProcessStatus: string
|
||||
case ERROR = 'error';
|
||||
case KILLED = 'killed';
|
||||
case CANCELLED = 'cancelled';
|
||||
case CLOSED = 'closed';
|
||||
}
|
||||
|
||||
161
app/Http/Controllers/Api/Deploy.php
Normal file
161
app/Http/Controllers/Api/Deploy.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Database\StartMariadb;
|
||||
use App\Actions\Database\StartMongodb;
|
||||
use App\Actions\Database\StartMysql;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Deploy extends Controller
|
||||
{
|
||||
public function deploy(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
$uuids = $request->query->get('uuid');
|
||||
$tags = $request->query->get('tag');
|
||||
$force = $request->query->get('force') ?? false;
|
||||
|
||||
if ($uuids && $tags) {
|
||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
} else if ($uuids) {
|
||||
return $this->by_uuids($uuids, $teamId, $force);
|
||||
}
|
||||
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||
{
|
||||
$uuids = explode(',', $uuid);
|
||||
$uuids = collect(array_filter($uuids));
|
||||
|
||||
if (count($uuids) === 0) {
|
||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
if ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
}
|
||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
}
|
||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||
{
|
||||
$tags = explode(',', $tags);
|
||||
$tags = collect(array_filter($tags));
|
||||
|
||||
if (count($tags) === 0) {
|
||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
foreach ($tags as $tag) {
|
||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||
if (!$found_tag) {
|
||||
$message->push("Tag {$tag} not found.");
|
||||
continue;
|
||||
}
|
||||
$applications = $found_tag->applications()->get();
|
||||
$services = $found_tag->services()->get();
|
||||
if ($applications->count() === 0 && $services->count() === 0) {
|
||||
$message->push("No resources found for tag {$tag}.");
|
||||
continue;
|
||||
}
|
||||
foreach ($applications as $resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
foreach ($services as $resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
if ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
}
|
||||
|
||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
}
|
||||
public function deploy_resource($resource, bool $force = false): Collection
|
||||
{
|
||||
$message = collect([]);
|
||||
if (gettype($resource) !== 'object') {
|
||||
return $message->push("Resource ($resource) not found.");
|
||||
}
|
||||
$type = $resource?->getMorphClass();
|
||||
if ($type === 'App\Models\Application') {
|
||||
queue_application_deployment(
|
||||
application: $resource,
|
||||
deployment_uuid: new Cuid2(7),
|
||||
force_rebuild: $force,
|
||||
);
|
||||
$message->push("Application {$resource->name} deployment queued.");
|
||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartPostgresql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartRedis::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMongodb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMysql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMariadb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
} else if ($type === 'App\Models\Service') {
|
||||
StartService::run($resource);
|
||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
39
app/Http/Controllers/Api/Project.php
Normal file
39
app/Http/Controllers/Api/Project.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Project extends Controller
|
||||
{
|
||||
public function projects(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
return response()->json($projects);
|
||||
}
|
||||
public function project_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
return response()->json($project);
|
||||
}
|
||||
public function environment_details(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
return response()->json($environment);
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Api/Server.php
Normal file
54
app/Http/Controllers/Api/Server.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Server extends Controller
|
||||
{
|
||||
public function servers(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||
$server['is_reachable'] = $server->settings->is_reachable;
|
||||
$server['is_usable'] = $server->settings->is_usable;
|
||||
return $server;
|
||||
});
|
||||
ray($servers);
|
||||
return response()->json($servers);
|
||||
}
|
||||
public function server_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['error' => 'Server not found.'], 404);
|
||||
}
|
||||
$server->load(['settings']);
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
return $payload;
|
||||
});
|
||||
return response()->json($server);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
@@ -122,7 +122,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($source) {
|
||||
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
||||
}
|
||||
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
||||
$this->server = Server::find($this->application_deployment_queue->server_id);
|
||||
$this->timeout = $this->server->settings->dynamic_timeout;
|
||||
$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->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||
@@ -131,6 +133,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
ray('New container name: ', $this->container_name);
|
||||
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
@@ -158,15 +162,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->preview->fqdn = $preview_fqdn;
|
||||
$this->preview->save();
|
||||
}
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
// Generate custom host<->ip mapping
|
||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||
if (!is_null($allContainers)) {
|
||||
@@ -220,7 +223,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
}
|
||||
ray($this->build_server);
|
||||
try {
|
||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
|
||||
$this->just_restart();
|
||||
@@ -230,6 +232,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(false);
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockercompose') {
|
||||
@@ -241,22 +245,32 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} else if ($this->application->build_pack === 'static') {
|
||||
$this->deploy_static_buildpack();
|
||||
} else {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else {
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
// Otherwise built image needs to be pushed before from the build server.
|
||||
if (!$this->use_build_server) {
|
||||
$this->push_to_docker_registry();
|
||||
}
|
||||
// ray($this->use_build_server);
|
||||
// if (!$this->use_build_server) {
|
||||
// if ($this->application->additional_servers->count() > 0) {
|
||||
// $this->push_to_docker_registry(forceFail: true);
|
||||
// } else {
|
||||
// $this->push_to_docker_registry();
|
||||
// }
|
||||
// }
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->application->isConfigurationChanged(true);
|
||||
} catch (Exception $e) {
|
||||
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR);
|
||||
}
|
||||
ray($e);
|
||||
$this->fail($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
@@ -282,163 +296,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
private function write_deployment_configurations()
|
||||
{
|
||||
if (isset($this->docker_compose_base64)) {
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"mkdir -p $this->configuration_dir"
|
||||
],
|
||||
[
|
||||
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||
],
|
||||
[
|
||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||
]
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
}
|
||||
}
|
||||
private function push_to_docker_registry($forceFail = false)
|
||||
{
|
||||
ray((str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()));
|
||||
if (
|
||||
$this->application->docker_registry_image_name &&
|
||||
$this->application->build_pack !== 'dockerimage' &&
|
||||
!$this->application->destination->server->isSwarm() &&
|
||||
!$this->restart_only &&
|
||||
!(str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())
|
||||
) {
|
||||
try {
|
||||
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
|
||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
|
||||
],
|
||||
);
|
||||
if ($this->application->docker_registry_image_tag) {
|
||||
// Tag image with latest
|
||||
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||
],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
|
||||
} catch (Exception $e) {
|
||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
|
||||
if ($forceFail) {
|
||||
throw $e;
|
||||
}
|
||||
ray($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
private function generate_image_names()
|
||||
{
|
||||
if ($this->application->dockerfile) {
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
}
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||
}
|
||||
} else {
|
||||
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
private function just_restart()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->check_image_locally_or_remotely();
|
||||
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->create_workdir();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||
}
|
||||
private function check_image_locally_or_remotely()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||
$this->execute_remote_command([
|
||||
"docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true
|
||||
]);
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
}
|
||||
}
|
||||
private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
foreach ($this->application->environment_variables_preview as $env) {
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->environment_variables as $env) {
|
||||
$envs->push($env->key . '=' . $env->value);
|
||||
}
|
||||
}
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
private function deploy_simple_dockerfile()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -446,19 +310,30 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
$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->create_workdir();
|
||||
$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();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function deploy_dockerimage_buildpack()
|
||||
{
|
||||
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.");
|
||||
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
|
||||
$this->generate_image_names();
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_compose_file();
|
||||
@@ -476,14 +351,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||
}
|
||||
if ($this->pull_request_id === 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
}
|
||||
$this->server->executeRemoteCommand(
|
||||
commands: $this->application->prepareHelperImage($this->deployment_uuid),
|
||||
loggingModel: $this->application_deployment_queue
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->clone_repository();
|
||||
$this->generate_image_names();
|
||||
@@ -549,30 +421,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (data_get($this->application, 'dockerfile_location')) {
|
||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->clone_repository();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
// if ($this->application->additional_destinations) {
|
||||
// $this->push_to_docker_registry();
|
||||
// $this->deploy_to_additional_destinations();
|
||||
// } else {
|
||||
$this->rolling_update();
|
||||
// }
|
||||
}
|
||||
private function deploy_nixpacks_buildpack()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
@@ -583,6 +432,38 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->create_workdir();
|
||||
$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->clone_repository();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
private function deploy_nixpacks_buildpack()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
$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->create_workdir();
|
||||
$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();
|
||||
return;
|
||||
}
|
||||
@@ -595,8 +476,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_nixpacks_confs();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
// $this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
private function deploy_static_buildpack()
|
||||
@@ -604,18 +485,205 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} 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->create_workdir();
|
||||
$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->clone_repository();
|
||||
$this->cleanup_git();
|
||||
$this->build_image();
|
||||
$this->generate_compose_file();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function write_deployment_configurations()
|
||||
{
|
||||
if (isset($this->docker_compose_base64)) {
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"mkdir -p $this->configuration_dir"
|
||||
],
|
||||
[
|
||||
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||
],
|
||||
[
|
||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||
]
|
||||
);
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
}
|
||||
}
|
||||
private function push_to_docker_registry()
|
||||
{
|
||||
$forceFail = true;
|
||||
if (str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
ray('empty docker_registry_image_name');
|
||||
return;
|
||||
}
|
||||
if ($this->restart_only) {
|
||||
ray('restart_only');
|
||||
return;
|
||||
}
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
ray('dockerimage');
|
||||
return;
|
||||
}
|
||||
if ($this->use_build_server) {
|
||||
ray('use_build_server');
|
||||
$forceFail = true;
|
||||
}
|
||||
if ($this->server->isSwarm() && $this->build_pack !== 'dockerimage') {
|
||||
ray('isSwarm');
|
||||
$forceFail = true;
|
||||
}
|
||||
if ($this->application->additional_servers->count() > 0) {
|
||||
ray('additional_servers');
|
||||
$forceFail = true;
|
||||
}
|
||||
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
|
||||
ray('this is an additional_servers, no pushy pushy');
|
||||
return;
|
||||
}
|
||||
ray('push_to_docker_registry noww: ' . $this->production_image_name);
|
||||
try {
|
||||
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
|
||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
|
||||
],
|
||||
);
|
||||
if ($this->application->docker_registry_image_tag) {
|
||||
// Tag image with latest
|
||||
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||
],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
|
||||
} catch (Exception $e) {
|
||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
|
||||
if ($forceFail) {
|
||||
throw new RuntimeException($e->getMessage(), 69420);
|
||||
}
|
||||
ray($e);
|
||||
}
|
||||
}
|
||||
private function generate_image_names()
|
||||
{
|
||||
if ($this->application->dockerfile) {
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
}
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||
}
|
||||
} 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_name) {
|
||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
|
||||
} else {
|
||||
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
|
||||
}
|
||||
}
|
||||
}
|
||||
private function just_restart()
|
||||
{
|
||||
$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();
|
||||
$this->check_image_locally_or_remotely();
|
||||
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->create_workdir();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||
}
|
||||
private function check_image_locally_or_remotely()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||
$this->execute_remote_command([
|
||||
"docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true
|
||||
]);
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
}
|
||||
}
|
||||
private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
foreach ($this->application->environment_variables_preview as $env) {
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->environment_variables as $env) {
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
}
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private function framework_based_notification()
|
||||
{
|
||||
// Laravel old env variables
|
||||
@@ -633,9 +701,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function rolling_update()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
if ($this->build_pack !== 'dockerimage') {
|
||||
$this->push_to_docker_registry(forceFail: true);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update started.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -645,13 +710,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
} else {
|
||||
if ($this->use_build_server) {
|
||||
$this->push_to_docker_registry(forceFail: true);
|
||||
$this->write_deployment_configurations();
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||
}
|
||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
||||
}
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
} else {
|
||||
@@ -681,7 +750,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->full_healthcheck_url) {
|
||||
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
|
||||
}
|
||||
while ($counter < $this->application->health_check_retries) {
|
||||
while ($counter <= $this->application->health_check_retries) {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||
@@ -724,10 +793,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_nixpacks_confs();
|
||||
}
|
||||
$this->generate_compose_file();
|
||||
|
||||
// Needs separate preview variables
|
||||
$this->generate_build_env_variables();
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
if ($this->application->build_pack === 'dockerfile') {
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
}
|
||||
$this->build_image();
|
||||
@@ -792,7 +859,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function deploy_to_additional_destinations()
|
||||
{
|
||||
$destination_ids = collect(str($this->application->additional_destinations)->explode(','));
|
||||
if ($this->application->additional_networks->count() === 0) {
|
||||
return;
|
||||
}
|
||||
$destination_ids = $this->application->additional_networks->pluck('id');
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode.");
|
||||
return;
|
||||
}
|
||||
if ($destination_ids->contains($this->destination->id)) {
|
||||
ray('Same destination found in additional destinations. Skipping.');
|
||||
return;
|
||||
}
|
||||
foreach ($destination_ids as $destination_id) {
|
||||
$destination = StandaloneDocker::find($destination_id);
|
||||
$server = $destination->server;
|
||||
@@ -800,11 +878,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
||||
continue;
|
||||
}
|
||||
$this->server = $server;
|
||||
$this->application_deployment_queue->addLogEntry("Deploying to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_image_names();
|
||||
$this->rolling_update();
|
||||
// ray('Deploying to additional destination: ', $server->name);
|
||||
$deployment_uuid = new Cuid2();
|
||||
queue_application_deployment(
|
||||
deployment_uuid: $deployment_uuid,
|
||||
application: $this->application,
|
||||
server: $server,
|
||||
destination: $destination,
|
||||
no_questions_asked: true,
|
||||
);
|
||||
$this->application_deployment_queue->addLogEntry("Deploying to additional server: {$server->name}. Click here to see the deployment status: " . route('project.application.deployment.show', [
|
||||
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
||||
'application_uuid' => data_get($this->application, 'uuid'),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'environment_name' => data_get($this->application, 'environment.name'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
private function set_base_dir()
|
||||
@@ -863,7 +951,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function generate_git_import_commands()
|
||||
{
|
||||
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands($this->deployment_uuid, $this->pull_request_id, $this->git_type);
|
||||
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands(
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
pull_request_id: $this->pull_request_id,
|
||||
git_type: $this->git_type,
|
||||
commit: $this->commit
|
||||
);
|
||||
return $commands;
|
||||
}
|
||||
|
||||
@@ -929,11 +1022,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_nixpacks_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->value}");
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,11 +1041,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$this->env_args->put($env->key, $env->value);
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$this->env_args->put($env->key, $env->value);
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
||||
@@ -1113,9 +1214,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// ];
|
||||
// }
|
||||
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||
}
|
||||
} else {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
}
|
||||
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
@@ -1159,22 +1270,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function generate_environment_variables($ports)
|
||||
{
|
||||
$environment_variables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($this->pull_request_id === 0) {
|
||||
// ray($this->application->runtime_environment_variables)->green();
|
||||
foreach ($this->application->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
@@ -1402,6 +1510,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
});
|
||||
if ($this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||
$this->application_deployment_queue->update([
|
||||
@@ -1457,12 +1570,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$value = escapeshellarg($env->value);
|
||||
$value = escapeshellarg($env->real_value);
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$value = escapeshellarg($env->value);
|
||||
$value = escapeshellarg($env->real_value);
|
||||
$this->build_args->push("--build-arg {$env->key}={$value}");
|
||||
}
|
||||
}
|
||||
@@ -1478,11 +1591,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}");
|
||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
@@ -1494,31 +1607,39 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
queue_next_deployment($this->application);
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
queue_next_deployment($this->application);
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
return;
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
$this->deploy_to_additional_destinations();
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
|
||||
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
||||
if (str($exception->getMessage())->isNotEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
||||
}
|
||||
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||
);
|
||||
$code = $exception->getCode();
|
||||
if ($code !== 69420) {
|
||||
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use App\Traits\ExecuteRemoteCommand;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
||||
|
||||
public $timeout = 3600;
|
||||
public $tries = 1;
|
||||
|
||||
public static int $batch_counter = 0;
|
||||
public Server $mainServer;
|
||||
public $servers;
|
||||
public string $basedir;
|
||||
public string $workdir;
|
||||
|
||||
public string $deploymentUuid;
|
||||
public int $pullRequestId = 0;
|
||||
|
||||
// Git related
|
||||
public string $gitImportCommands;
|
||||
public ?string $gitType = null;
|
||||
public string $gitRepository;
|
||||
public string $gitBranch;
|
||||
public int $gitPort;
|
||||
public string $gitFullRepoUrl;
|
||||
|
||||
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
|
||||
{
|
||||
$this->mainServer = data_get($this->application, 'destination.server');
|
||||
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
|
||||
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
|
||||
$this->gitType = data_get($this->deployment, 'git_type');
|
||||
|
||||
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
|
||||
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
ray()->clearAll();
|
||||
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
|
||||
|
||||
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
|
||||
if ($this->application->dockerfile_target_build) {
|
||||
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
||||
}
|
||||
|
||||
// Get the git repository and port (custom port or default port)
|
||||
[
|
||||
'repository' => $this->gitRepository,
|
||||
'port' => $this->gitPort
|
||||
] = $this->application->customRepository();
|
||||
|
||||
// Get the git branch and git import commands
|
||||
[
|
||||
'commands' => $this->gitImportCommands,
|
||||
'branch' => $this->gitBranch,
|
||||
'fullRepoUrl' => $this->gitFullRepoUrl
|
||||
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
|
||||
|
||||
$this->servers = $this->application->servers();
|
||||
|
||||
if ($this->deployment->restart_only) {
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
throw new \Exception('Restart only is not supported for docker image based deployments');
|
||||
}
|
||||
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
$this->servers->each(function ($server) {
|
||||
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
|
||||
$this->restartOnly($server);
|
||||
});
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
} catch (Throwable $exception) {
|
||||
$this->fail($exception);
|
||||
} finally {
|
||||
$this->servers->each(function ($server) {
|
||||
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
"command" => "docker rm -f {$this->deploymentUuid}",
|
||||
"hidden" => true,
|
||||
"ignoreErrors" => true,
|
||||
]),
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
public function restartOnly(Server $server)
|
||||
{
|
||||
$server->executeRemoteCommand(
|
||||
commands: $this->application->prepareHelperImage($this->deploymentUuid),
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
|
||||
$privateKey = data_get($this->application, 'private_key.private_key', null);
|
||||
$gitLsRemoteCommand = collect([]);
|
||||
if ($privateKey) {
|
||||
$privateKey = base64_decode($privateKey);
|
||||
$gitLsRemoteCommand
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
|
||||
])
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
|
||||
])
|
||||
->push([
|
||||
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
|
||||
])
|
||||
->push([
|
||||
"name" => "git_commit_sha",
|
||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$gitLsRemoteCommand->push([
|
||||
"name" => "git_commit_sha",
|
||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
}
|
||||
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
|
||||
|
||||
$server->executeRemoteCommand(
|
||||
commands: $gitLsRemoteCommand,
|
||||
loggingModel: $this->deployment
|
||||
);
|
||||
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
|
||||
|
||||
[
|
||||
'productionImageName' => $productionImageName
|
||||
] = $this->application->generateImageNames($commit, $this->pullRequestId);
|
||||
|
||||
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
|
||||
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
|
||||
|
||||
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
|
||||
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
|
||||
|
||||
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
|
||||
|
||||
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
|
||||
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||
}
|
||||
public function failed(Throwable $exception): void
|
||||
{
|
||||
ray($exception);
|
||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||
}
|
||||
private function next(string $status)
|
||||
{
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->deployment->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
queue_next_deployment($this->application, isNew: true);
|
||||
}
|
||||
}
|
||||
@@ -17,38 +17,34 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $build_logs_url;
|
||||
public Application $application;
|
||||
public ApplicationPreview $preview;
|
||||
public string $body;
|
||||
|
||||
public function __construct(
|
||||
public string $application_id,
|
||||
public int $pull_request_id,
|
||||
public string $deployment_uuid,
|
||||
public string $status
|
||||
public Application $application,
|
||||
public ApplicationPreview $preview,
|
||||
public ProcessStatus $status,
|
||||
public ?string $deployment_uuid = null
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$this->application = Application::findOrFail($this->application_id);
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
|
||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
|
||||
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
|
||||
if ($this->status === ProcessStatus::CLOSED) {
|
||||
$this->delete_comment();
|
||||
return;
|
||||
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
|
||||
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
||||
}
|
||||
if ($this->status === ProcessStatus::FINISHED->value) {
|
||||
} else if ($this->status === ProcessStatus::FINISHED) {
|
||||
$this->body = "The preview deployment is ready. 🟢\n\n";
|
||||
if ($this->preview->fqdn) {
|
||||
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
||||
}
|
||||
}
|
||||
if ($this->status === ProcessStatus::ERROR->value) {
|
||||
} else if ($this->status === ProcessStatus::ERROR) {
|
||||
$this->body = "The preview deployment failed. 🔴\n\n";
|
||||
}
|
||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||
|
||||
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
||||
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
||||
|
||||
@@ -77,10 +73,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function create_comment()
|
||||
{
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
|
||||
'body' => $this->body,
|
||||
]);
|
||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||
$this->preview->save();
|
||||
}
|
||||
private function delete_comment()
|
||||
{
|
||||
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Shared\ComplexStatusCheck;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
@@ -42,6 +43,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$applications = $this->server->applications();
|
||||
foreach ($applications as $application) {
|
||||
if ($application->additional_servers->count() > 0) {
|
||||
$is_main_server = $application->destination->server->id === $this->server->id;
|
||||
if ($is_main_server) {
|
||||
ComplexStatusCheck::run($application);
|
||||
$applications = $applications->filter(function ($value, $key) use ($application) {
|
||||
return $value->id !== $application->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->server->isFunctional()) {
|
||||
return 'Server is not ready.';
|
||||
};
|
||||
@@ -83,7 +97,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
});
|
||||
}
|
||||
}
|
||||
$applications = $this->server->applications();
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
@@ -160,10 +173,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// Notify user that this container should not be there.
|
||||
}
|
||||
}
|
||||
if (data_get($container,'Name') === '/coolify-db') {
|
||||
if (data_get($container, 'Name') === '/coolify-db') {
|
||||
$foundDatabases[] = 0;
|
||||
}
|
||||
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
@@ -209,7 +221,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$exitedServices = $exitedServices->unique('id');
|
||||
foreach ($exitedServices as $exitedService) {
|
||||
if ($exitedService->status === 'exited') {
|
||||
if (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
@@ -231,7 +243,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if ($application->status === 'exited') {
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
@@ -256,7 +268,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if ($preview->status === 'exited') {
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
@@ -281,7 +293,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if ($database->status === 'exited') {
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Service\DeleteService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneMariadb;
|
||||
@@ -18,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -30,45 +32,29 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$server = $this->resource->destination->server;
|
||||
$this->resource->delete();
|
||||
if (!$server->isFunctional()) {
|
||||
if ($this->resource->type() === 'service') {
|
||||
ray('dispatching delete service');
|
||||
DeleteService::dispatch($this->resource);
|
||||
} else {
|
||||
$this->resource->forceDelete();
|
||||
}
|
||||
return 'Server is not functional';
|
||||
}
|
||||
$this->resource->forceDelete();
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
StopApplication::run($this->resource);
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mongodb':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mysql':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-mariadb':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
}
|
||||
if ($this->resource->type() === 'service') {
|
||||
DeleteService::dispatch($this->resource);
|
||||
} else {
|
||||
$this->resource->forceDelete();
|
||||
case 'service':
|
||||
StopService::run($this->resource);
|
||||
DeleteService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
Artisan::queue('cleanup:stucked-resources');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ class ScheduledTaskJob implements ShouldQueue
|
||||
$this->resource = $service;
|
||||
} else if ($application = $task->application()->first()) {
|
||||
$this->resource = $application;
|
||||
} else {
|
||||
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
||||
}
|
||||
$this->team = Team::find($task->team_id);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public ?int $disk_usage = null;
|
||||
public int|string|null $disk_usage = null;
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
@@ -41,6 +41,15 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
throw new \RuntimeException('Server is not ready.');
|
||||
};
|
||||
try {
|
||||
// $this->server->validateConnection();
|
||||
// $this->server->validateOS();
|
||||
// $docker_installed = $this->server->validateDockerEngine();
|
||||
// if (!$docker_installed) {
|
||||
// $this->server->installDocker();
|
||||
// $this->server->validateDockerEngine();
|
||||
// }
|
||||
|
||||
// $this->server->validateDockerEngineVersion();
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->cleanup(notify: false);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class ActivityMonitor extends Component
|
||||
public $isPollingActive = false;
|
||||
|
||||
protected $activity;
|
||||
protected $listeners = ['newMonitorActivity'];
|
||||
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
|
||||
|
||||
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||
{
|
||||
|
||||
44
app/Livewire/Admin/Index.php
Normal file
44
app/Livewire/Admin/Index.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public $users = [];
|
||||
public function mount()
|
||||
{
|
||||
if (!isCloud()) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (auth()->user()->id !== 0 && session('adminToken') === null) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->users = User::whereHas('teams', function ($query) {
|
||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||
})->get();
|
||||
}
|
||||
public function switchUser(int $user_id)
|
||||
{
|
||||
$user = User::find($user_id);
|
||||
auth()->login($user);
|
||||
|
||||
if ($user_id === 0) {
|
||||
session()->forget('adminToken');
|
||||
} else {
|
||||
$token_payload = [
|
||||
'valid' => true,
|
||||
];
|
||||
$token = Crypt::encrypt($token_payload);
|
||||
session(['adminToken' => $token]);
|
||||
}
|
||||
return refreshSession();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.admin.index');
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Boarding;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
@@ -12,6 +13,7 @@ use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
protected $listeners = ['serverInstalled' => 'validateServer'];
|
||||
public string $currentState = 'welcome';
|
||||
|
||||
public ?string $selectedServerType = null;
|
||||
@@ -93,7 +95,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
return $this->validateServer('localhost');
|
||||
} elseif ($this->selectedServerType === 'remote') {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if (isDev()) {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get();
|
||||
} else {
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
}
|
||||
if ($this->privateKeys->count() > 0) {
|
||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||
}
|
||||
@@ -116,15 +122,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->validateServer();
|
||||
$this->installServer();
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||
if (!$proxyTypeSet) {
|
||||
$this->currentState = 'select-proxy';
|
||||
return;
|
||||
}
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
// return;
|
||||
// }
|
||||
$this->getProjects();
|
||||
}
|
||||
public function selectExistingPrivateKey()
|
||||
@@ -188,14 +195,19 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||
$this->createdServer->settings->save();
|
||||
$this->createdServer->addInitialNetwork();
|
||||
$this->validateServer();
|
||||
$this->currentState = 'validate-server';
|
||||
}
|
||||
public function installServer()
|
||||
{
|
||||
$this->dispatch('validateServer', true);
|
||||
}
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
instant_remote_process(['uptime'], $this->createdServer, true);
|
||||
// EC2 does not have `uptime` command, lol
|
||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => true,
|
||||
@@ -210,7 +222,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$this->currentState = 'install-docker';
|
||||
$this->currentState = 'validate-server';
|
||||
throw new \Exception('Docker not found or old version is installed.');
|
||||
}
|
||||
$this->createdServer->settings()->update([
|
||||
@@ -218,27 +230,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
]);
|
||||
$this->getProxyType();
|
||||
} catch (\Throwable $e) {
|
||||
// $this->dockerInstallationStarted = false;
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
try {
|
||||
$this->dockerInstallationStarted = true;
|
||||
$activity = InstallDocker::run($this->createdServer);
|
||||
$this->dispatch('installDocker');
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
$this->dockerInstallationStarted = false;
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
}
|
||||
public function dockerInstalledOrSkipped()
|
||||
{
|
||||
$this->validateServer();
|
||||
}
|
||||
public function selectProxy(string|null $proxyType = null)
|
||||
public function selectProxy(?string $proxyType = null)
|
||||
{
|
||||
if (!$proxyType) {
|
||||
return $this->getProjects();
|
||||
|
||||
@@ -2,18 +2,43 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
{
|
||||
public $projects = [];
|
||||
public $servers = [];
|
||||
public Collection $servers;
|
||||
public $deployments_per_server;
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||
$this->get_deployments();
|
||||
}
|
||||
public function cleanup_queue()
|
||||
{
|
||||
$this->dispatch('success', 'Cleanup started.');
|
||||
Artisan::queue('app:init', [
|
||||
'--cleanup-deployments' => 'true'
|
||||
]);
|
||||
}
|
||||
public function get_deployments()
|
||||
{
|
||||
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $this->servers->pluck("id"))->get([
|
||||
"id",
|
||||
"application_id",
|
||||
"application_name",
|
||||
"deployment_url",
|
||||
"pull_request_id",
|
||||
"server_name",
|
||||
"server_id",
|
||||
"status"
|
||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||
}
|
||||
// public function getIptables()
|
||||
// {
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -28,9 +30,8 @@ class Help extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(3, 60);
|
||||
$this->rateLimit(3, 30);
|
||||
$this->validate();
|
||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
|
||||
$debug = "Route: {$this->path}";
|
||||
$mail = new MailMessage();
|
||||
$mail->view(
|
||||
@@ -40,9 +41,21 @@ class Help extends Component
|
||||
'debug' => $debug
|
||||
]
|
||||
);
|
||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||
$this->dispatch('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
|
||||
$mail->subject("[HELP]: {$this->subject}");
|
||||
$settings = InstanceSettings::get();
|
||||
$type = set_transanctional_email_settings($settings);
|
||||
if (!$type) {
|
||||
$url = "https://app.coolify.io/api/feedback";
|
||||
if (isDev()) {
|
||||
$url = "http://localhost:80/api/feedback";
|
||||
}
|
||||
Http::post($url, [
|
||||
'content' => "User: `" . auth()->user()?->email . "` with subject: `" . $this->subject . "` has the following problem: `" . $this->description . "`"
|
||||
]);
|
||||
} else {
|
||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||
}
|
||||
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
63
app/Livewire/NewActivityMonitor.php
Normal file
63
app/Livewire/NewActivityMonitor.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Component;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class NewActivityMonitor extends Component
|
||||
{
|
||||
public ?string $header = null;
|
||||
public $activityId;
|
||||
public $eventToDispatch = 'activityFinished';
|
||||
public $isPollingActive = false;
|
||||
|
||||
protected $activity;
|
||||
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
|
||||
|
||||
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||
{
|
||||
$this->activityId = $activityId;
|
||||
$this->eventToDispatch = $eventToDispatch;
|
||||
|
||||
$this->hydrateActivity();
|
||||
|
||||
$this->isPollingActive = true;
|
||||
}
|
||||
|
||||
public function hydrateActivity()
|
||||
{
|
||||
$this->activity = Activity::find($this->activityId);
|
||||
}
|
||||
|
||||
public function polling()
|
||||
{
|
||||
$this->hydrateActivity();
|
||||
// $this->setStatus(ProcessStatus::IN_PROGRESS);
|
||||
$exit_code = data_get($this->activity, 'properties.exitCode');
|
||||
if ($exit_code !== null) {
|
||||
// if ($exit_code === 0) {
|
||||
// // $this->setStatus(ProcessStatus::FINISHED);
|
||||
// } else {
|
||||
// // $this->setStatus(ProcessStatus::ERROR);
|
||||
// }
|
||||
$this->isPollingActive = false;
|
||||
if ($this->eventToDispatch !== null) {
|
||||
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
|
||||
$causer_id = data_get($this->activity, 'causer_id');
|
||||
$user = User::find($causer_id);
|
||||
if ($user) {
|
||||
foreach ($user->teams as $team) {
|
||||
$teamId = $team->id;
|
||||
$this->eventToDispatch::dispatch($teamId);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->dispatch($this->eventToDispatch);
|
||||
ray('Dispatched event: ' . $this->eventToDispatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,25 @@ use Livewire\Component;
|
||||
class Advanced extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public bool $is_force_https_enabled;
|
||||
protected $rules = [
|
||||
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
||||
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
||||
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
|
||||
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
|
||||
'application.settings.is_force_https_enabled' => 'boolean|required',
|
||||
'is_force_https_enabled' => 'boolean|required',
|
||||
'application.settings.is_log_drain_enabled' => 'boolean|required',
|
||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
||||
'application.settings.gpu_driver' => 'string|required',
|
||||
'application.settings.gpu_count' => 'string|required',
|
||||
'application.settings.gpu_device_ids' => 'string|required',
|
||||
'application.settings.gpu_options' => 'string|required',
|
||||
];
|
||||
public function mount() {
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->application->isLogDrainEnabled()) {
|
||||
@@ -31,7 +36,8 @@ class Advanced extends Component
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($this->application->settings->is_force_https_enabled) {
|
||||
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
|
||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
||||
$this->dispatch('resetDefaultLabels', false);
|
||||
}
|
||||
$this->application->settings->save();
|
||||
|
||||
@@ -15,6 +15,7 @@ class Index extends Component
|
||||
public int $skip = 0;
|
||||
public int $default_take = 40;
|
||||
public bool $show_next = false;
|
||||
public bool $show_prev = false;
|
||||
public ?string $pull_request_id = null;
|
||||
protected $queryString = ['pull_request_id'];
|
||||
public function mount()
|
||||
@@ -60,15 +61,30 @@ class Index extends Component
|
||||
{
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function previous_page(?int $take = null)
|
||||
{
|
||||
|
||||
public function load_deployments(int|null $take = null)
|
||||
if ($take) {
|
||||
$this->skip = $this->skip - $take;
|
||||
}
|
||||
$this->skip = $this->skip - $this->default_take;
|
||||
if ($this->skip < 0) {
|
||||
$this->show_prev = false;
|
||||
$this->skip = 0;
|
||||
}
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function next_page(?int $take = null)
|
||||
{
|
||||
if ($take) {
|
||||
$this->skip = $this->skip + $take;
|
||||
}
|
||||
$take = $this->default_take;
|
||||
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
||||
$this->show_prev = true;
|
||||
$this->load_deployments();
|
||||
}
|
||||
public function load_deployments()
|
||||
{
|
||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take);
|
||||
$this->deployments = $deployments;
|
||||
$this->deployments_count = $count;
|
||||
$this->show_pull_request_only();
|
||||
|
||||
@@ -7,8 +7,6 @@ use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class DeploymentNavbar extends Component
|
||||
@@ -37,17 +35,24 @@ class DeploymentNavbar extends Component
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->dispatch('refreshQueue');
|
||||
}
|
||||
|
||||
public function force_start()
|
||||
{
|
||||
try {
|
||||
force_start_deployment($this->application_deployment_queue);
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
$kill_command = "kill -9 {$this->application_deployment_queue->current_process_id}";
|
||||
if ($this->application_deployment_queue->current_process_id) {
|
||||
$process = Process::run("ps -p {$this->application_deployment_queue->current_process_id} -o command --no-headers");
|
||||
if (Str::of($process->output())->contains([$this->server->ip, 'EOF-COOLIFY-SSH'])) {
|
||||
Process::run($kill_command);
|
||||
}
|
||||
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
||||
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
||||
$server = Server::find($server_id);
|
||||
if ($this->application_deployment_queue->logs) {
|
||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
$new_log_entry = [
|
||||
'command' => $kill_command,
|
||||
'output' => "Deployment cancelled by user.",
|
||||
@@ -61,14 +66,15 @@ class DeploymentNavbar extends Component
|
||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||
]);
|
||||
}
|
||||
instant_remote_process([$kill_command], $server);
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->application_deployment_queue->update([
|
||||
'current_process_id' => null,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
queue_next_deployment($this->application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,12 @@ class General extends Component
|
||||
'application.docker_compose_pr' => 'nullable',
|
||||
'application.docker_compose_raw' => 'nullable',
|
||||
'application.docker_compose_pr_raw' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.dockerfile_target_build' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.docker_compose_custom_start_command' => 'nullable',
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.custom_docker_run_options' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
];
|
||||
@@ -97,9 +98,10 @@ class General extends Component
|
||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||
];
|
||||
@@ -124,7 +126,6 @@ class General extends Component
|
||||
$this->application->save();
|
||||
}
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
$this->checkLabelUpdates();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@@ -162,6 +163,7 @@ class General extends Component
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
@@ -182,15 +184,6 @@ class General extends Component
|
||||
$this->submit();
|
||||
$this->dispatch('build_pack_updated');
|
||||
}
|
||||
public function checkLabelUpdates()
|
||||
{
|
||||
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
|
||||
$this->labelsChanged = true;
|
||||
} else {
|
||||
$this->labelsChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getWildcardDomain()
|
||||
{
|
||||
$server = data_get($this->application, 'destination.server');
|
||||
@@ -210,6 +203,13 @@ class General extends Component
|
||||
|
||||
public function updatedApplicationFqdn()
|
||||
{
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||
$this->application->save();
|
||||
$this->resetDefaultLabels(false);
|
||||
// $this->dispatch('success', 'Labels reset to default!');
|
||||
}
|
||||
@@ -236,19 +236,20 @@ class General extends Component
|
||||
]);
|
||||
}
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
$domains = $domains->unique();
|
||||
foreach ($domains as $domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||
$domains = str($this->application->fqdn)->trim()->explode(',');
|
||||
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='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||
}
|
||||
}
|
||||
}
|
||||
check_fqdn_usage($this->application);
|
||||
$this->application->fqdn = $domains->implode(',');
|
||||
}
|
||||
|
||||
if (data_get($this->application, 'custom_docker_run_options')) {
|
||||
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
|
||||
}
|
||||
if (data_get($this->application, 'dockerfile')) {
|
||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||
if ($port && !$this->application->ports_exposes) {
|
||||
@@ -271,7 +272,6 @@ class General extends Component
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->checkLabelUpdates();
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ComplexContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\Application;
|
||||
@@ -32,13 +33,14 @@ class Heading extends Component
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
// $this->application->refresh();
|
||||
// $this->application->previews->each(function ($preview) {
|
||||
// $preview->refresh();
|
||||
// });
|
||||
} else {
|
||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||
}
|
||||
|
||||
if ($showNotification) $this->dispatch('success', "Application status updated.");
|
||||
}
|
||||
|
||||
@@ -47,43 +49,27 @@ class Heading extends Component
|
||||
$this->deploy(force_rebuild: true);
|
||||
}
|
||||
|
||||
public function deployNew()
|
||||
{
|
||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
||||
$this->dispatch('error', 'Please load a Compose file first.');
|
||||
return;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: false,
|
||||
is_new_deployment: true,
|
||||
);
|
||||
return redirect()->route('project.application.deployment.show', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
}
|
||||
public function deploy(bool $force_rebuild = false)
|
||||
{
|
||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
||||
$this->dispatch('error', 'Please load a Compose file first.');
|
||||
$this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.');
|
||||
return;
|
||||
}
|
||||
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
|
||||
$this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
||||
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
||||
return;
|
||||
}
|
||||
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
|
||||
$this->dispatch('error', 'To use a build server you must set a Docker image name first.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
|
||||
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
|
||||
return;
|
||||
}
|
||||
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||
return;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
);
|
||||
@@ -106,29 +92,23 @@ class Heading extends Component
|
||||
StopApplication::run($this->application);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
}
|
||||
public function restartNew()
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
is_new_deployment: true,
|
||||
);
|
||||
return redirect()->route('project.application.deployment.show', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
]);
|
||||
if ($this->application->additional_servers->count() > 0) {
|
||||
$this->application->additional_servers->each(function ($server) {
|
||||
$server->pivot->status = "exited:unhealthy";
|
||||
$server->pivot->save();
|
||||
});
|
||||
}
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
public function restart()
|
||||
{
|
||||
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||
return;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
);
|
||||
|
||||
@@ -47,7 +47,7 @@ class Previews extends Component
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
force_rebuild: false,
|
||||
pull_request_id: $pull_request_id,
|
||||
|
||||
@@ -24,7 +24,7 @@ class Rollback extends Component
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
application: $this->application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
commit: $commit,
|
||||
force_rebuild: false,
|
||||
|
||||
@@ -22,12 +22,12 @@ class CloneMe extends Component
|
||||
public ?int $selectedDestination = null;
|
||||
public ?Server $server = null;
|
||||
public $resources = [];
|
||||
public string $newProjectName = '';
|
||||
public string $newName = '';
|
||||
|
||||
protected $messages = [
|
||||
'selectedServer' => 'Please select a server.',
|
||||
'selectedDestination' => 'Please select a server & destination.',
|
||||
'newProjectName' => 'Please enter a name for the new project.',
|
||||
'newName' => 'Please enter a name for the new project or environment.',
|
||||
];
|
||||
public function mount($project_uuid)
|
||||
{
|
||||
@@ -36,7 +36,7 @@ class CloneMe extends Component
|
||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||
$this->project_id = $this->project->id;
|
||||
$this->servers = currentTeam()->servers;
|
||||
$this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
||||
$this->newName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
||||
}
|
||||
|
||||
public function render()
|
||||
@@ -46,34 +46,50 @@ class CloneMe extends Component
|
||||
|
||||
public function selectServer($server_id, $destination_id)
|
||||
{
|
||||
if ($server_id == $this->selectedServer && $destination_id == $this->selectedDestination) {
|
||||
$this->selectedServer = null;
|
||||
$this->selectedDestination = null;
|
||||
$this->server = null;
|
||||
return;
|
||||
}
|
||||
$this->selectedServer = $server_id;
|
||||
$this->selectedDestination = $destination_id;
|
||||
$this->server = $this->servers->where('id', $server_id)->first();
|
||||
}
|
||||
|
||||
public function clone()
|
||||
public function clone(string $type)
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'selectedDestination' => 'required',
|
||||
'newProjectName' => 'required',
|
||||
'newName' => 'required',
|
||||
]);
|
||||
$foundProject = Project::where('name', $this->newProjectName)->first();
|
||||
if ($foundProject) {
|
||||
throw new \Exception('Project with the same name already exists.');
|
||||
}
|
||||
$newProject = Project::create([
|
||||
'name' => $this->newProjectName,
|
||||
'team_id' => currentTeam()->id,
|
||||
'description' => $this->project->description . ' (clone)',
|
||||
]);
|
||||
if ($this->environment->name !== 'production') {
|
||||
$newProject->environments()->create([
|
||||
'name' => $this->environment->name,
|
||||
if ($type === 'project') {
|
||||
$foundProject = Project::where('name', $this->newName)->first();
|
||||
if ($foundProject) {
|
||||
throw new \Exception('Project with the same name already exists.');
|
||||
}
|
||||
$project = Project::create([
|
||||
'name' => $this->newName,
|
||||
'team_id' => currentTeam()->id,
|
||||
'description' => $this->project->description . ' (clone)',
|
||||
]);
|
||||
if ($this->environment->name !== 'production') {
|
||||
$project->environments()->create([
|
||||
'name' => $this->environment->name,
|
||||
]);
|
||||
}
|
||||
$environment = $project->environments->where('name', $this->environment->name)->first();
|
||||
} else {
|
||||
$foundEnv = $this->project->environments()->where('name', $this->newName)->first();
|
||||
if ($foundEnv) {
|
||||
throw new \Exception('Environment with the same name already exists.');
|
||||
}
|
||||
$project = $this->project;
|
||||
$environment = $this->project->environments()->create([
|
||||
'name' => $this->newName,
|
||||
]);
|
||||
}
|
||||
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
|
||||
// Clone Applications
|
||||
$applications = $this->environment->applications;
|
||||
$databases = $this->environment->databases();
|
||||
$services = $this->environment->services;
|
||||
@@ -83,7 +99,7 @@ class CloneMe extends Component
|
||||
'uuid' => $uuid,
|
||||
'fqdn' => generateFqdn($this->server, $uuid),
|
||||
'status' => 'exited',
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'environment_id' => $environment->id,
|
||||
// This is not correct, but we need to set it to something
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
@@ -110,7 +126,7 @@ class CloneMe extends Component
|
||||
'uuid' => $uuid,
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
$newDatabase->save();
|
||||
@@ -136,7 +152,7 @@ class CloneMe extends Component
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$newService = $service->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'environment_id' => $newEnvironment->id,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $this->selectedDestination,
|
||||
]);
|
||||
$newService->save();
|
||||
@@ -153,8 +169,8 @@ class CloneMe extends Component
|
||||
$newService->parse();
|
||||
}
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $newProject->uuid,
|
||||
'environment_name' => $newEnvironment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -58,19 +58,19 @@ class Heading extends Component
|
||||
{
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$activity = StartPostgresql::run($this->database);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-redis') {
|
||||
$activity = StartRedis::run($this->database);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-mongodb') {
|
||||
$activity = StartMongodb::run($this->database);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-mysql') {
|
||||
$activity = StartMysql::run($this->database);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||
$activity = StartMariadb::run($this->database);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ class Import extends Component
|
||||
|
||||
if (!empty($this->importCommands)) {
|
||||
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->validated = false;
|
||||
|
||||
@@ -9,6 +9,7 @@ class DeleteEnvironment extends Component
|
||||
{
|
||||
public array $parameters;
|
||||
public int $environment_id;
|
||||
public bool $disabled = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ class DeleteProject extends Component
|
||||
{
|
||||
public array $parameters;
|
||||
public int $project_id;
|
||||
public bool $disabled = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
|
||||
@@ -12,7 +12,24 @@ class Edit extends Component
|
||||
'project.name' => 'required|min:3|max:255',
|
||||
'project.description' => 'nullable|string|max:255',
|
||||
];
|
||||
public function mount() {
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->project->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'project',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->project->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$projectUuid = request()->route('project_uuid');
|
||||
$teamId = currentTeam()->id;
|
||||
$project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
|
||||
|
||||
@@ -12,14 +12,30 @@ class EnvironmentEdit extends Component
|
||||
public Application $application;
|
||||
public $environment;
|
||||
public array $parameters;
|
||||
|
||||
protected $rules = [
|
||||
'environment.name' => 'required|min:3|max:255',
|
||||
'environment.description' => 'nullable|min:3|max:255',
|
||||
];
|
||||
public function mount() {
|
||||
$this->parameters = get_route_parameters();
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->environment->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'environment',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->environment->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
|
||||
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ class GithubPrivateRepository extends Component
|
||||
public bool $is_static = false;
|
||||
public string|null $publish_directory = null;
|
||||
protected int $page = 1;
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
|
||||
public function mount()
|
||||
@@ -49,6 +51,20 @@ class GithubPrivateRepository extends Component
|
||||
$this->repositories = $this->branches = collect();
|
||||
$this->github_apps = GithubApp::private();
|
||||
}
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
}
|
||||
}
|
||||
public function loadRepositories($github_app_id)
|
||||
{
|
||||
$this->repositories = collect();
|
||||
@@ -95,7 +111,7 @@ class GithubPrivateRepository extends Component
|
||||
$this->loadBranchByPage();
|
||||
}
|
||||
}
|
||||
$this->selected_branch_name = data_get($this->branches,'0.name');
|
||||
$this->selected_branch_name = data_get($this->branches, '0.name', 'main');
|
||||
}
|
||||
|
||||
protected function loadBranchByPage()
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -18,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
public $current_step = 'private_keys';
|
||||
public $parameters;
|
||||
public $query;
|
||||
public $private_keys;
|
||||
public $private_keys =[];
|
||||
public int $private_key_id;
|
||||
|
||||
public int $port = 3000;
|
||||
@@ -29,12 +30,22 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
|
||||
public string $repository_url;
|
||||
public string $branch;
|
||||
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||
private ?string $git_host = null;
|
||||
private string $git_repository;
|
||||
|
||||
protected $rules = [
|
||||
'repository_url' => 'required',
|
||||
'branch' => 'required|string',
|
||||
'port' => 'required|numeric',
|
||||
'is_static' => 'required|boolean',
|
||||
'publish_directory' => 'nullable|string',
|
||||
'build_pack' => 'required|string',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'repository_url' => 'Repository',
|
||||
@@ -42,11 +53,9 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'port' => 'Port',
|
||||
'is_static' => 'Is static',
|
||||
'publish_directory' => 'Publish directory',
|
||||
'build_pack' => 'Build pack',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||
private ?string $git_host = null;
|
||||
private string $git_repository;
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -62,6 +71,20 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->is_static) {
|
||||
|
||||
@@ -30,7 +30,7 @@ class PublicGitRepository extends Component
|
||||
public GithubApp|GitlabApp|string $git_source = 'other';
|
||||
public string $git_host;
|
||||
public string $git_repository;
|
||||
public $build_pack;
|
||||
public $build_pack = 'nixpacks';
|
||||
public bool $show_is_static = true;
|
||||
|
||||
protected $rules = [
|
||||
@@ -61,9 +61,11 @@ class PublicGitRepository extends Component
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
$this->show_is_static = true;
|
||||
$this->port = 3000;
|
||||
} else if ($this->build_pack === 'static') {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
$this->port = 80;
|
||||
} else {
|
||||
$this->show_is_static = false;
|
||||
$this->is_static = false;
|
||||
|
||||
@@ -29,7 +29,8 @@ class Index extends Component
|
||||
}
|
||||
$this->project = $project;
|
||||
$this->environment = $environment;
|
||||
$this->applications = $environment->applications->sortBy('name');
|
||||
|
||||
$this->applications = $this->environment->applications->load(['tags']);
|
||||
$this->applications = $this->applications->map(function ($application) {
|
||||
if (data_get($application, 'environment.project.uuid')) {
|
||||
$application->hrefLink = route('project.application.configuration', [
|
||||
@@ -40,8 +41,8 @@ class Index extends Component
|
||||
}
|
||||
return $application;
|
||||
});
|
||||
$this->postgresqls = $environment->postgresqls->sortBy('name');
|
||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||
$this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name');
|
||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||
if (data_get($postgresql, 'environment.project.uuid')) {
|
||||
$postgresql->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
||||
@@ -51,7 +52,7 @@ class Index extends Component
|
||||
}
|
||||
return $postgresql;
|
||||
});
|
||||
$this->redis = $environment->redis->sortBy('name');
|
||||
$this->redis = $this->environment->redis->load(['tags'])->sortBy('name');
|
||||
$this->redis = $this->redis->map(function ($redis) {
|
||||
if (data_get($redis, 'environment.project.uuid')) {
|
||||
$redis->hrefLink = route('project.database.configuration', [
|
||||
@@ -62,7 +63,7 @@ class Index extends Component
|
||||
}
|
||||
return $redis;
|
||||
});
|
||||
$this->mongodbs = $environment->mongodbs->sortBy('name');
|
||||
$this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name');
|
||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
||||
$mongodb->hrefLink = route('project.database.configuration', [
|
||||
@@ -73,7 +74,7 @@ class Index extends Component
|
||||
}
|
||||
return $mongodb;
|
||||
});
|
||||
$this->mysqls = $environment->mysqls->sortBy('name');
|
||||
$this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name');
|
||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
||||
if (data_get($mysql, 'environment.project.uuid')) {
|
||||
$mysql->hrefLink = route('project.database.configuration', [
|
||||
@@ -84,7 +85,7 @@ class Index extends Component
|
||||
}
|
||||
return $mysql;
|
||||
});
|
||||
$this->mariadbs = $environment->mariadbs->sortBy('name');
|
||||
$this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name');
|
||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
||||
$mariadb->hrefLink = route('project.database.configuration', [
|
||||
@@ -95,7 +96,7 @@ class Index extends Component
|
||||
}
|
||||
return $mariadb;
|
||||
});
|
||||
$this->services = $environment->services->sortBy('name');
|
||||
$this->services = $this->environment->services->load(['tags'])->sortBy('name');
|
||||
$this->services = $this->services->map(function ($service) {
|
||||
if (data_get($service, 'environment.project.uuid')) {
|
||||
$service->hrefLink = route('project.service.configuration', [
|
||||
@@ -103,7 +104,7 @@ class Index extends Component
|
||||
'environment_name' => data_get($service, 'environment.name'),
|
||||
'service_uuid' => data_get($service, 'uuid')
|
||||
]);
|
||||
$service->status = serviceStatus($service);
|
||||
$service->status = $service->status();
|
||||
}
|
||||
return $service;
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
||||
|
||||
class Configuration extends Component
|
||||
{
|
||||
public Service $service;
|
||||
public ?Service $service = null;
|
||||
public $applications;
|
||||
public $databases;
|
||||
public array $parameters;
|
||||
@@ -30,7 +30,10 @@ class Configuration extends Component
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->first();
|
||||
if (!$this->service) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->databases = $this->service->databases->sort();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class FileStorage extends Component
|
||||
$this->fileStorage->content = null;
|
||||
}
|
||||
$this->fileStorage->save();
|
||||
$this->fileStorage->saveStorageOnServer($this->service);
|
||||
$this->fileStorage->saveStorageOnServer();
|
||||
$this->dispatch('success', 'File updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->fileStorage->setRawAttributes($original);
|
||||
|
||||
@@ -27,12 +27,12 @@ class Index extends Component
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
|
||||
$service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first();
|
||||
if ($service) {
|
||||
$this->serviceApplication = $service;
|
||||
$this->serviceApplication->getFilesFromServer();
|
||||
} else {
|
||||
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
||||
$this->serviceDatabase = $this->service->databases()->whereUuid($this->parameters['stack_service_uuid'])->first();
|
||||
$this->serviceDatabase->getFilesFromServer();
|
||||
}
|
||||
$this->s3s = currentTeam()->s3s;
|
||||
|
||||
@@ -57,7 +57,7 @@ class Navbar extends Component
|
||||
}
|
||||
$this->service->parse();
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
public function stop(bool $forceCleanup = false)
|
||||
{
|
||||
@@ -82,6 +82,6 @@ class Navbar extends Component
|
||||
StopService::run($this->service);
|
||||
$this->service->parse();
|
||||
$activity = StartService::run($this->service);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Project\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use Livewire\Component;
|
||||
|
||||
class Application extends Component
|
||||
class ServiceApplicationView extends Component
|
||||
{
|
||||
public ServiceApplication $application;
|
||||
public $parameters;
|
||||
@@ -17,10 +17,11 @@ class Application extends Component
|
||||
'application.exclude_from_status' => 'required|boolean',
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
'application.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'application.is_gzip_enabled' => 'nullable|boolean',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.application');
|
||||
return view('livewire.project.service.service-application-view');
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@@ -53,6 +54,7 @@ class Application extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
check_fqdn_usage($this->application);
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
@@ -45,8 +45,10 @@ class StackForm extends Component
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
public function instantSave() {
|
||||
public function instantSave()
|
||||
{
|
||||
$this->service->save();
|
||||
$this->dispatch('success', 'Service settings saved successfully.');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
@@ -17,14 +17,15 @@ class Danger extends Component
|
||||
{
|
||||
$this->modalId = new Cuid2(7);
|
||||
$parameters = get_route_parameters();
|
||||
$this->projectUuid = $parameters['project_uuid'];
|
||||
$this->environmentName = $parameters['environment_name'];
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentName = data_get($parameters, 'environment_name');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
DeleteResourceJob::dispatchSync($this->resource);
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource);
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName
|
||||
|
||||
@@ -2,11 +2,86 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Application\StopApplicationOneServer;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Destination extends Component
|
||||
{
|
||||
public $resource;
|
||||
public $servers = [];
|
||||
public $additional_servers = [];
|
||||
public $networks = [];
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'loadData',
|
||||
];
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->loadData();
|
||||
}
|
||||
public function loadData()
|
||||
{
|
||||
$all_networks = collect([]);
|
||||
$all_networks = $all_networks->push($this->resource->destination);
|
||||
$all_networks = $all_networks->merge($this->resource->additional_networks);
|
||||
|
||||
$this->networks = Server::isUsable()->get()->map(function ($server) {
|
||||
return $server->standaloneDockers;
|
||||
})->flatten();
|
||||
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
|
||||
return $all_networks->pluck('id')->contains($network->id);
|
||||
});
|
||||
$this->networks = $this->networks->reject(function ($network) {
|
||||
return $this->resource->destination->server->id == $network->server->id;
|
||||
});
|
||||
}
|
||||
public function redeploy(int $network_id, int $server_id)
|
||||
{
|
||||
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||
return;
|
||||
}
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
$server = Server::find($server_id);
|
||||
$destination = StandaloneDocker::find($network_id);
|
||||
queue_application_deployment(
|
||||
deployment_uuid: $deployment_uuid,
|
||||
application: $this->resource,
|
||||
server: $server,
|
||||
destination: $destination,
|
||||
no_questions_asked: true,
|
||||
);
|
||||
return redirect()->route('project.application.deployment.show', [
|
||||
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
||||
'application_uuid' => data_get($this->resource, 'uuid'),
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'environment_name' => data_get($this->resource, 'environment.name'),
|
||||
]);
|
||||
}
|
||||
public function addServer(int $network_id, int $server_id)
|
||||
{
|
||||
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
|
||||
$this->resource->load(['additional_networks']);
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
$this->loadData();
|
||||
}
|
||||
public function removeServer(int $network_id, int $server_id)
|
||||
{
|
||||
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) {
|
||||
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
|
||||
return;
|
||||
}
|
||||
$server = Server::find($server_id);
|
||||
StopApplicationOneServer::run($this->resource, $server);
|
||||
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
|
||||
$this->resource->load(['additional_networks']);
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
$this->loadData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,13 @@ class Add extends Component
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
ray($this->key, $this->value, $this->is_build_time);
|
||||
if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
|
||||
$type = str($this->value)->after("{{")->before(".")->value;
|
||||
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->dispatch('saveKey', [
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
|
||||
@@ -71,12 +71,26 @@ class All extends Component
|
||||
continue;
|
||||
}
|
||||
$found->value = $variable;
|
||||
if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
|
||||
$type = str($found->value)->after("{{")->before(".")->value;
|
||||
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$found->save();
|
||||
continue;
|
||||
} else {
|
||||
$environment = new EnvironmentVariable();
|
||||
$environment->key = $key;
|
||||
$environment->value = $variable;
|
||||
if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
|
||||
$type = str($environment->value)->after("{{")->before(".")->value;
|
||||
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$environment->is_build_time = false;
|
||||
$environment->is_preview = $isPreview ? true : false;
|
||||
switch ($this->resource->type()) {
|
||||
@@ -157,7 +171,7 @@ class All extends Component
|
||||
}
|
||||
$environment->save();
|
||||
$this->refreshEnvs();
|
||||
$this->dispatch('success', 'Environment variable added successfully.');
|
||||
$this->dispatch('success', 'Environment variable added.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public $parameters;
|
||||
public ModelsEnvironmentVariable $env;
|
||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||
public ?string $modalId = null;
|
||||
public bool $isDisabled = false;
|
||||
public bool $isLocked = false;
|
||||
public bool $isSharedVariable = false;
|
||||
public string $type;
|
||||
|
||||
protected $rules = [
|
||||
@@ -20,16 +22,20 @@ class Show extends Component
|
||||
'env.value' => 'nullable',
|
||||
'env.is_build_time' => 'required|boolean',
|
||||
'env.is_shown_once' => 'required|boolean',
|
||||
'env.real_value' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'key' => 'Key',
|
||||
'value' => 'Value',
|
||||
'is_build_time' => 'Build Time',
|
||||
'is_shown_once' => 'Shown Once',
|
||||
'env.key' => 'Key',
|
||||
'env.value' => 'Value',
|
||||
'env.is_build_time' => 'Build Time',
|
||||
'env.is_shown_once' => 'Shown Once',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||
$this->isSharedVariable = true;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->checkEnvs();
|
||||
@@ -44,9 +50,17 @@ class Show extends Component
|
||||
$this->isLocked = true;
|
||||
}
|
||||
}
|
||||
public function serialize()
|
||||
{
|
||||
data_forget($this->env, 'real_value');
|
||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||
data_forget($this->env, 'is_build_time');
|
||||
}
|
||||
}
|
||||
public function lock()
|
||||
{
|
||||
$this->env->is_shown_once = true;
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->checkEnvs();
|
||||
$this->dispatch('refreshEnvs');
|
||||
@@ -57,10 +71,30 @@ class Show extends Component
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
try {
|
||||
if ($this->isSharedVariable) {
|
||||
$this->validate([
|
||||
'env.key' => 'required|string',
|
||||
'env.value' => 'nullable',
|
||||
'env.is_shown_once' => 'required|boolean',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
|
||||
$type = str($this->env->value)->after("{{")->before(".")->value;
|
||||
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
@@ -115,7 +115,7 @@ class ExecuteContainerCommand extends Component
|
||||
$exec = "docker exec {$this->container} {$cmd}";
|
||||
}
|
||||
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
178
app/Livewire/Project/Shared/ResourceOperations.php
Normal file
178
app/Livewire/Project/Shared/ResourceOperations.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class ResourceOperations extends Component
|
||||
{
|
||||
public $resource;
|
||||
public $projectUuid;
|
||||
public $environmentName;
|
||||
public $projects;
|
||||
public $servers;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$parameters = get_route_parameters();
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentName = data_get($parameters, 'environment_name');
|
||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||
$this->servers = currentTeam()->servers;
|
||||
}
|
||||
public function cloneTo($destination_id)
|
||||
{
|
||||
$new_destination = StandaloneDocker::find($destination_id);
|
||||
if (!$new_destination) {
|
||||
$new_destination = SwarmDocker::find($destination_id);
|
||||
}
|
||||
if (!$new_destination) {
|
||||
return $this->addError('destination_id', 'Destination not found.');
|
||||
}
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$server = $new_destination->server;
|
||||
if ($this->resource->getMorphClass() === 'App\Models\Application') {
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'fqdn' => generateFqdn($server, $uuid),
|
||||
'status' => 'exited',
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||
$new_resource->custom_labels = base64_encode($customLabels);
|
||||
$new_resource->save();
|
||||
}
|
||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||
foreach ($environmentVaribles as $environmentVarible) {
|
||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||
'application_id' => $new_resource->id,
|
||||
]);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
$persistentVolumes = $this->resource->persistentStorages()->get();
|
||||
foreach ($persistentVolumes as $volume) {
|
||||
$newPersistentVolume = $volume->replicate()->fill([
|
||||
'name' => $new_resource->uuid . '-' . str($volume->name)->afterLast('-'),
|
||||
'resource_id' => $new_resource->id,
|
||||
]);
|
||||
$newPersistentVolume->save();
|
||||
}
|
||||
$route = route('project.application.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'application_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if (
|
||||
$this->resource->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis'
|
||||
) {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'status' => 'exited',
|
||||
'started_at' => null,
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||
foreach ($environmentVaribles as $environmentVarible) {
|
||||
$payload = [];
|
||||
if ($this->resource->type() === 'standalone-postgresql') {
|
||||
$payload['standalone_postgresql_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-redis') {
|
||||
$payload['standalone_redis_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mongodb') {
|
||||
$payload['standalone_mongodb_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mysql') {
|
||||
$payload['standalone_mysql_id'] = $new_resource->id;
|
||||
} else if ($this->resource->type() === 'standalone-mariadb') {
|
||||
$payload['standalone_mariadb_id'] = $new_resource->id;
|
||||
}
|
||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill($payload);
|
||||
$newEnvironmentVariable->save();
|
||||
}
|
||||
$route = route('project.database.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'database_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if ($this->resource->type() === 'service') {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
'uuid' => $uuid,
|
||||
'name' => $this->resource->name . '-clone-' . $uuid,
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
foreach ($new_resource->applications() as $application) {
|
||||
$application->update([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
}
|
||||
foreach ($new_resource->databases() as $database) {
|
||||
$database->update([
|
||||
'status' => 'exited',
|
||||
]);
|
||||
}
|
||||
$new_resource->parse();
|
||||
$route = route('project.service.configuration', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName,
|
||||
'service_uuid' => $new_resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
}
|
||||
return;
|
||||
}
|
||||
public function moveTo($environment_id)
|
||||
{
|
||||
try {
|
||||
$new_environment = Environment::findOrFail($environment_id);
|
||||
$this->resource->update([
|
||||
'environment_id' => $environment_id
|
||||
]);
|
||||
if ($this->resource->type() === 'application') {
|
||||
$route = route('project.application.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'application_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if (str($this->resource->type())->startsWith('standalone-')) {
|
||||
$route = route('project.database.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'database_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
} else if ($this->resource->type() === 'service') {
|
||||
$route = route('project.service.configuration', [
|
||||
'project_uuid' => $new_environment->project->uuid,
|
||||
'environment_name' => $new_environment->name,
|
||||
'service_uuid' => $this->resource->uuid,
|
||||
]) . "#resource-operations";
|
||||
return redirect()->to($route);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.resource-operations');
|
||||
}
|
||||
}
|
||||
90
app/Livewire/Project/Shared/Tags.php
Normal file
90
app/Livewire/Project/Shared/Tags.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Tag;
|
||||
use Livewire\Component;
|
||||
|
||||
class Tags extends Component
|
||||
{
|
||||
public $resource = null;
|
||||
public ?string $new_tag = null;
|
||||
public $tags = [];
|
||||
protected $listeners = [
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
protected $rules = [
|
||||
'resource.tags.*.name' => 'required|string|min:2',
|
||||
'new_tag' => 'required|string|min:2'
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'new_tag' => 'tag'
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||
}
|
||||
public function addTag(string $id, string $name)
|
||||
{
|
||||
try {
|
||||
if ($this->resource->tags()->where('id', $id)->exists()) {
|
||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added.");
|
||||
return;
|
||||
}
|
||||
$this->resource->tags()->syncWithoutDetaching($id);
|
||||
$this->refresh();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function deleteTag(string $id)
|
||||
{
|
||||
try {
|
||||
$this->resource->tags()->detach($id);
|
||||
|
||||
$found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
|
||||
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0){
|
||||
$found_more_tags->delete();
|
||||
}
|
||||
$this->refresh();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function refresh()
|
||||
{
|
||||
$this->resource->load(['tags']);
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||
$this->new_tag = null;
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'new_tag' => 'required|string|min:2'
|
||||
]);
|
||||
$tags = str($this->new_tag)->trim()->explode(' ');
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$tag</span> already added.");
|
||||
continue;
|
||||
}
|
||||
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
||||
if (!$found) {
|
||||
$found = Tag::create([
|
||||
'name' => $tag,
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
}
|
||||
$this->resource->tags()->syncWithoutDetaching($found->id);
|
||||
}
|
||||
$this->refresh();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.tags');
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,11 @@ class Webhooks extends Component
|
||||
public ?string $deploywebhook = null;
|
||||
public ?string $githubManualWebhook = null;
|
||||
public ?string $gitlabManualWebhook = null;
|
||||
public ?string $bitbucketManualWebhook = null;
|
||||
protected $rules = [
|
||||
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
|
||||
];
|
||||
public function saveSecret()
|
||||
{
|
||||
@@ -29,6 +31,7 @@ class Webhooks extends Component
|
||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ class RunCommand extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -14,7 +13,9 @@ class Form extends Component
|
||||
public ?string $wildcard_domain = null;
|
||||
public int $cleanup_after_percentage;
|
||||
public bool $dockerInstallationStarted = false;
|
||||
protected $listeners = ['serverRefresh'];
|
||||
public bool $revalidate = false;
|
||||
|
||||
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
||||
|
||||
protected $rules = [
|
||||
'server.name' => 'required',
|
||||
@@ -27,6 +28,8 @@ class Form extends Component
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_build_server' => 'required|boolean',
|
||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -40,6 +43,9 @@ class Form extends Component
|
||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
'server.settings.is_build_server' => 'Build Server',
|
||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
||||
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -47,9 +53,10 @@ class Form extends Component
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
}
|
||||
public function serverRefresh($install = true)
|
||||
public function serverInstalled()
|
||||
{
|
||||
$this->validateServer($install);
|
||||
$this->server->refresh();
|
||||
$this->server->settings->refresh();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@@ -62,12 +69,9 @@ class Form extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function installDocker()
|
||||
public function revalidate()
|
||||
{
|
||||
$this->dispatch('installDocker');
|
||||
$this->dockerInstallationStarted = true;
|
||||
$activity = InstallDocker::run($this->server);
|
||||
$this->dispatch('newMonitorActivity', $activity->id);
|
||||
$this->revalidate = true;
|
||||
}
|
||||
public function checkLocalhostConnection()
|
||||
{
|
||||
@@ -78,48 +82,13 @@ class Form extends Component
|
||||
$this->server->settings->is_usable = true;
|
||||
$this->server->settings->save();
|
||||
} else {
|
||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
||||
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
public function validateServer($install = true)
|
||||
{
|
||||
try {
|
||||
$uptime = $this->server->validateConnection();
|
||||
if (!$uptime) {
|
||||
$install && $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
||||
return;
|
||||
}
|
||||
$supported_os_type = $this->server->validateOS();
|
||||
if (!$supported_os_type) {
|
||||
$install && $this->dispatch('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.');
|
||||
return;
|
||||
}
|
||||
$dockerInstalled = $this->server->validateDockerEngine();
|
||||
if ($dockerInstalled) {
|
||||
$install && $this->dispatch('success', 'Docker Engine is installed.<br> Checking version.');
|
||||
} else {
|
||||
$install && $this->installDocker();
|
||||
return;
|
||||
}
|
||||
$dockerVersion = $this->server->validateDockerEngineVersion();
|
||||
if ($dockerVersion) {
|
||||
$install && $this->dispatch('success', 'Docker Engine version is 22+.');
|
||||
} else {
|
||||
$install && $this->installDocker();
|
||||
return;
|
||||
}
|
||||
if ($this->server->isSwarm()) {
|
||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||
if ($swarmInstalled) {
|
||||
$install && $this->dispatch('success', 'Docker Swarm is initiated.');
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
$this->dispatch('validateServer', $install);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
@@ -71,7 +71,7 @@ class Deploy extends Component
|
||||
{
|
||||
try {
|
||||
$activity = StartProxy::run($this->server);
|
||||
$this->dispatch('newMonitorActivity', $activity->id, ProxyStatusChanged::class);
|
||||
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,8 @@ class Status extends Component
|
||||
public Server $server;
|
||||
public bool $polling = false;
|
||||
public int $numberOfPolls = 0;
|
||||
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function mount() {
|
||||
}
|
||||
public function startProxyPolling()
|
||||
{
|
||||
$this->checkProxy();
|
||||
|
||||
64
app/Livewire/Server/Resources.php
Normal file
64
app/Livewire/Server/Resources.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Resources extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $unmanagedContainers;
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'refreshStatus',
|
||||
];
|
||||
}
|
||||
|
||||
public function startUnmanaged($id) {
|
||||
$this->server->startUnmanaged($id);
|
||||
$this->dispatch('success', 'Container started.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function restartUnmanaged($id) {
|
||||
$this->server->restartUnmanaged($id);
|
||||
$this->dispatch('success', 'Container restarted.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function stopUnmanaged($id) {
|
||||
$this->server->stopUnmanaged($id);
|
||||
$this->dispatch('success', 'Container stopped.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function refreshStatus() {
|
||||
$this->server->refresh();
|
||||
$this->loadUnmanagedContainers();
|
||||
$this->dispatch('success', 'Resource statuses refreshed.');
|
||||
}
|
||||
public function loadUnmanagedContainers() {
|
||||
$this->unmanagedContainers = $this->server->loadUnmanagedContainers();
|
||||
}
|
||||
public function mount() {
|
||||
$this->unmanagedContainers = collect();
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.resources');
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ class Show extends Component
|
||||
use AuthorizesRequests;
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
protected $listeners = ['serverInstalled' => '$refresh'];
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
@@ -19,14 +20,13 @@ class Show extends Component
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->dispatch('serverRefresh',false);
|
||||
$this->dispatch('serverRefresh', false);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ class ShowPrivateKey extends Component
|
||||
if ($uptime) {
|
||||
$this->dispatch('success', 'Server is reachable.');
|
||||
} else {
|
||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/configuration#openssh-server">documentation</a> for further help.');
|
||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh#openssh">documentation</a> for further help.');
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
121
app/Livewire/Server/ValidateAndInstall.php
Normal file
121
app/Livewire/Server/ValidateAndInstall.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class ValidateAndInstall extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public int $number_of_tries = 0;
|
||||
public int $max_tries = 1;
|
||||
public bool $install = true;
|
||||
public $uptime = null;
|
||||
public $supported_os_type = null;
|
||||
public $docker_installed = null;
|
||||
public $docker_compose_installed = null;
|
||||
public $docker_version = null;
|
||||
public $proxy_started = false;
|
||||
public $error = null;
|
||||
public bool $ask = false;
|
||||
|
||||
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
|
||||
|
||||
public function init(bool $install = true)
|
||||
{
|
||||
$this->install = $install;
|
||||
$this->uptime = null;
|
||||
$this->supported_os_type = null;
|
||||
$this->docker_installed = null;
|
||||
$this->docker_version = null;
|
||||
$this->docker_compose_installed = null;
|
||||
$this->proxy_started = null;
|
||||
$this->error = null;
|
||||
$this->number_of_tries = 0;
|
||||
if (!$this->ask) {
|
||||
$this->dispatch('validateServerNow');
|
||||
}
|
||||
}
|
||||
public function startValidatingAfterAsking() {
|
||||
$this->ask = false;
|
||||
$this->init();
|
||||
}
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
$this->validateConnection();
|
||||
$this->validateOS();
|
||||
$this->validateDockerEngine();
|
||||
|
||||
if ($this->server->isSwarm()) {
|
||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||
if ($swarmInstalled) {
|
||||
$this->dispatch('success', 'Docker Swarm is initiated.');
|
||||
}
|
||||
} else {
|
||||
$proxy = StartProxy::run($this->server);
|
||||
if ($proxy) {
|
||||
$this->proxy_started = true;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
$this->uptime = $this->server->validateConnection();
|
||||
if (!$this->uptime) {
|
||||
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.';
|
||||
return;
|
||||
}
|
||||
}
|
||||
public function validateOS()
|
||||
{
|
||||
$this->supported_os_type = $this->server->validateOS();
|
||||
if (!$this->supported_os_type) {
|
||||
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
return;
|
||||
}
|
||||
}
|
||||
public function validateDockerEngine()
|
||||
{
|
||||
$this->docker_installed = $this->server->validateDockerEngine();
|
||||
$this->docker_compose_installed = $this->server->validateDockerCompose();
|
||||
if (!$this->docker_installed || !$this->docker_compose_installed) {
|
||||
if ($this->install) {
|
||||
if ($this->number_of_tries == $this->max_tries) {
|
||||
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
return;
|
||||
} else {
|
||||
$activity = $this->server->installDocker();
|
||||
$this->number_of_tries++;
|
||||
$this->dispatch('newActivityMonitor', $activity->id, 'validateDockerEngine');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->validateDockerVersion();
|
||||
}
|
||||
}
|
||||
public function validateDockerVersion()
|
||||
{
|
||||
$this->docker_version = $this->server->validateDockerEngineVersion();
|
||||
if ($this->docker_version) {
|
||||
$this->dispatch('serverInstalled');
|
||||
$this->dispatch('success', 'Server validated successfully.');
|
||||
} else {
|
||||
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||
return;
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.validate-and-install');
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class Change extends Component
|
||||
public ?bool $preview_deployment_permissions = true;
|
||||
|
||||
public $parameters;
|
||||
public GithubApp $github_app;
|
||||
public ?GithubApp $github_app;
|
||||
public string $name;
|
||||
public bool $is_system_wide;
|
||||
|
||||
|
||||
18
app/Livewire/Tags/Index.php
Normal file
18
app/Livewire/Tags/Index.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Tags;
|
||||
|
||||
use App\Models\Tag;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public $tags = [];
|
||||
public function mount() {
|
||||
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.tags.index');
|
||||
}
|
||||
}
|
||||
72
app/Livewire/Tags/Show.php
Normal file
72
app/Livewire/Tags/Show.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Tags;
|
||||
|
||||
use App\Http\Controllers\Api\Deploy;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Tag;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public $tags;
|
||||
public Tag $tag;
|
||||
public $applications;
|
||||
public $services;
|
||||
public $webhook = null;
|
||||
public $deployments_per_tag_per_server = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
|
||||
$tag = $this->tags->where('name', request()->tag_name)->first();
|
||||
if (!$tag) {
|
||||
return redirect()->route('tags.index');
|
||||
}
|
||||
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||
$this->applications = $tag->applications()->get();
|
||||
$this->services = $tag->services()->get();
|
||||
$this->tag = $tag;
|
||||
$this->get_deployments();
|
||||
}
|
||||
public function get_deployments()
|
||||
{
|
||||
try {
|
||||
$resource_ids = $this->applications->pluck('id');
|
||||
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
|
||||
"id",
|
||||
"application_id",
|
||||
"application_name",
|
||||
"deployment_url",
|
||||
"pull_request_id",
|
||||
"server_name",
|
||||
"server_id",
|
||||
"status"
|
||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function redeploy_all()
|
||||
{
|
||||
try {
|
||||
$message = collect([]);
|
||||
$this->applications->each(function ($resource) use ($message) {
|
||||
$deploy = new Deploy();
|
||||
$message->push($deploy->deploy_resource($resource));
|
||||
});
|
||||
$this->services->each(function ($resource) use ($message) {
|
||||
$deploy = new Deploy();
|
||||
$message->push($deploy->deploy_resource($resource));
|
||||
});
|
||||
$this->dispatch('success', 'Mass deployment started.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.tags.show');
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,11 @@ class Invitations extends Component
|
||||
|
||||
public function deleteInvitation(int $invitation_id)
|
||||
{
|
||||
TeamInvitation::find($invitation_id)->delete();
|
||||
$initiation_found = TeamInvitation::find($invitation_id);
|
||||
if (!$initiation_found) {
|
||||
return $this->dispatch('error', 'Invitation not found.');
|
||||
}
|
||||
$initiation_found->delete();
|
||||
$this->refreshInvitations();
|
||||
$this->dispatch('success', 'Invitation revoked.');
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ class Create extends Component
|
||||
$this->storage->save();
|
||||
return redirect()->route('team.storage.show', $this->storage->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||
// return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ class Form extends Component
|
||||
{
|
||||
try {
|
||||
$this->storage->testConnection(shouldSave: true);
|
||||
return $this->dispatch('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
return $this->dispatch('success', 'Connection is working.', 'Tested with "ListObjectsV2" action.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
36
app/Livewire/TeamSharedVariablesIndex.php
Normal file
36
app/Livewire/TeamSharedVariablesIndex.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Team;
|
||||
use Livewire\Component;
|
||||
|
||||
class TeamSharedVariablesIndex extends Component
|
||||
{
|
||||
public Team $team;
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
try {
|
||||
$this->team->environment_variables()->create([
|
||||
'key' => $data['key'],
|
||||
'value' => $data['value'],
|
||||
'type' => 'team',
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
$this->team->refresh();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->team = currentTeam();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.team-shared-variables-index');
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,12 @@ use Illuminate\Support\Collection;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($application) {
|
||||
@@ -39,6 +37,7 @@ class Application extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
$application->update(['fqdn' => null]);
|
||||
$application->settings()->delete();
|
||||
$storages = $application->persistentStorages()->get();
|
||||
$server = data_get($application, 'destination.server');
|
||||
@@ -50,67 +49,30 @@ class Application extends BaseModel
|
||||
$application->persistentStorages()->delete();
|
||||
$application->environment_variables()->delete();
|
||||
$application->environment_variables_preview()->delete();
|
||||
foreach ($application->scheduled_tasks as $task) {
|
||||
$task->delete();
|
||||
}
|
||||
$application->tags()->detach();
|
||||
});
|
||||
}
|
||||
// Build packs / deployment types
|
||||
|
||||
|
||||
public function servers(): Collection
|
||||
public function additional_servers()
|
||||
{
|
||||
$mainServer = data_get($this, 'destination.server');
|
||||
$additionalDestinations = data_get($this, 'additional_destinations', null);
|
||||
$additionalServers = collect([]);
|
||||
if ($this->isMultipleServerDeployment()) {
|
||||
ray('asd');
|
||||
if (str($additionalDestinations)->isNotEmpty()) {
|
||||
$additionalDestinations = str($additionalDestinations)->explode(',');
|
||||
foreach ($additionalDestinations as $destinationId) {
|
||||
$destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first();
|
||||
$server = data_get($destination, 'server');
|
||||
$additionalServers->push($server);
|
||||
}
|
||||
}
|
||||
}
|
||||
return collect([$mainServer])->merge($additionalServers);
|
||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||
->withPivot('standalone_docker_id', 'status');
|
||||
}
|
||||
|
||||
public function generateImageNames(string $commit, int $pullRequestId)
|
||||
public function additional_networks()
|
||||
{
|
||||
if ($this->dockerfile) {
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:latest");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:latest");
|
||||
}
|
||||
} else if ($this->build_pack === 'dockerimage') {
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}");
|
||||
} else if ($pullRequestId === 0) {
|
||||
$dockerImageTag = str($commit)->substr(0, 128);
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}");
|
||||
}
|
||||
} else if ($pullRequestId !== 0) {
|
||||
if ($this->docker_registry_image_name) {
|
||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build");
|
||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}");
|
||||
} else {
|
||||
$buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build");
|
||||
$productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}");
|
||||
}
|
||||
}
|
||||
return [
|
||||
'buildImageName' => $buildImageName,
|
||||
'productionImageName' => $productionImageName,
|
||||
];
|
||||
return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')
|
||||
->withPivot('server_id', 'status');
|
||||
}
|
||||
public function is_github_based(): bool
|
||||
{
|
||||
if (data_get($this, 'source')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// End of build packs / deployment types
|
||||
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
@@ -242,9 +204,6 @@ class Application extends BaseModel
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
// Normal Deployments
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -254,6 +213,83 @@ class Application extends BaseModel
|
||||
|
||||
);
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
}
|
||||
public function status(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if ($this->additional_servers->count() === 0) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
} else {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
}
|
||||
},
|
||||
get: function ($value) {
|
||||
if ($this->additional_servers->count() === 0) {
|
||||
//running (healthy)
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
} else {
|
||||
$complex_status = null;
|
||||
$complex_health = null;
|
||||
$complex_status = $main_server_status = str($value)->before(':')->value();
|
||||
$complex_health = $main_server_health = str($value)->after(':')->value() ?? 'unhealthy';
|
||||
$additional_servers_status = $this->additional_servers->pluck('pivot.status');
|
||||
foreach ($additional_servers_status as $status) {
|
||||
$server_status = str($status)->before(':')->value();
|
||||
$server_health = str($status)->after(':')->value() ?? 'unhealthy';
|
||||
if ($server_status !== 'running') {
|
||||
if ($main_server_status !== $server_status) {
|
||||
$complex_status = 'degraded';
|
||||
}
|
||||
}
|
||||
if ($server_health !== 'healthy') {
|
||||
if ($main_server_health !== $server_health) {
|
||||
$complex_health = 'unhealthy';
|
||||
}
|
||||
}
|
||||
}
|
||||
return "$complex_status:$complex_health";
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
@@ -263,6 +299,18 @@ class Application extends BaseModel
|
||||
: explode(',', $this->ports_exposes)
|
||||
);
|
||||
}
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function serviceType()
|
||||
{
|
||||
$found = str(collect(SPECIFIC_SERVICES)->filter(function ($service) {
|
||||
@@ -427,11 +475,11 @@ class Application extends BaseModel
|
||||
{
|
||||
return data_get($this, 'settings.is_log_drain_enabled', false);
|
||||
}
|
||||
public function isConfigurationChanged($save = false)
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables->all());
|
||||
$newConfigHash .= json_encode($this->environment_variables());
|
||||
} else {
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->all());
|
||||
}
|
||||
@@ -454,31 +502,6 @@ class Application extends BaseModel
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isMultipleServerDeployment()
|
||||
{
|
||||
return false;
|
||||
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function healthCheckUrl()
|
||||
{
|
||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
return null;
|
||||
}
|
||||
if (!$this->health_check_port) {
|
||||
$health_check_port = $this->ports_exposes_array[0];
|
||||
} else {
|
||||
$health_check_port = $this->health_check_port;
|
||||
}
|
||||
if ($this->health_check_path) {
|
||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
||||
} else {
|
||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
||||
}
|
||||
return $full_healthcheck_url;
|
||||
}
|
||||
function customRepository()
|
||||
{
|
||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
||||
@@ -500,310 +523,25 @@ class Application extends BaseModel
|
||||
{
|
||||
return "/artifacts/{$uuid}";
|
||||
}
|
||||
function generateHealthCheckCommands()
|
||||
{
|
||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||
return 'exit 0';
|
||||
}
|
||||
if (!$this->health_check_port) {
|
||||
$health_check_port = $this->ports_exposes_array[0];
|
||||
} else {
|
||||
$health_check_port = $this->health_check_port;
|
||||
}
|
||||
if ($this->health_check_path) {
|
||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null"
|
||||
];
|
||||
} else {
|
||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
||||
$generated_healthchecks_commands = [
|
||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"
|
||||
];
|
||||
}
|
||||
return implode(' ', $generated_healthchecks_commands);
|
||||
}
|
||||
function generateLocalPersistentVolumes(int $pullRequestId)
|
||||
{
|
||||
$persistentStorages = [];
|
||||
$volumeNames = [];
|
||||
foreach ($this->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
if ($pullRequestId !== 0) {
|
||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
$persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $persistentStorage->name;
|
||||
|
||||
if ($pullRequestId !== 0) {
|
||||
$name = $name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
|
||||
$volumeNames[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'persistentStorages' => $persistentStorages,
|
||||
'volumeNames' => $volumeNames,
|
||||
];
|
||||
}
|
||||
public function generateEnvironmentVariables($ports)
|
||||
{
|
||||
$environmentVariables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($this->pull_request_id === 0) {
|
||||
// ray($this->runtime_environment_variables)->green();
|
||||
foreach ($this->runtime_environment_variables as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($this->nixpacks_environment_variables as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->runtime_environment_variables_preview)->green();
|
||||
foreach ($this->runtime_environment_variables_preview as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($this->nixpacks_environment_variables_preview as $env) {
|
||||
$environmentVariables->push("$env->key=$env->value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||
$environmentVariables->push("PORT={$ports[0]}");
|
||||
}
|
||||
return $environmentVariables->all();
|
||||
}
|
||||
function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
||||
{
|
||||
$pullRequestId = $deployment->pull_request_id;
|
||||
$ports = $this->settings->is_static ? [80] : $this->ports_exposes_array;
|
||||
$container_name = generateApplicationContainerName($this, $this->pull_request_id);
|
||||
$commit = str($deployment->getOutput('git_commit_sha'))->before("\t");
|
||||
|
||||
[
|
||||
'productionImageName' => $productionImageName
|
||||
] = $this->generateImageNames($commit, $pullRequestId);
|
||||
|
||||
[
|
||||
'persistentStorages' => $persistentStorages,
|
||||
'volumeNames' => $volumeNames
|
||||
] = $this->generateLocalPersistentVolumes($pullRequestId);
|
||||
|
||||
$environmentVariables = $this->generateEnvironmentVariables($ports);
|
||||
|
||||
if (data_get($this, 'custom_labels')) {
|
||||
$labels = collect(str($this->custom_labels)->explode(','));
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !Str::startsWith($value, 'coolify.');
|
||||
});
|
||||
$this->custom_labels = $labels->implode(',');
|
||||
$this->save();
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $productionImageName,
|
||||
'container_name' => $container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environmentVariables,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generateHealthCheckCommands()
|
||||
],
|
||||
'interval' => $this->health_check_interval . 's',
|
||||
'timeout' => $this->health_check_timeout . 's',
|
||||
'retries' => $this->health_check_retries,
|
||||
'start_period' => $this->health_check_start_period . 's'
|
||||
],
|
||||
'mem_limit' => $this->limits_memory,
|
||||
'memswap_limit' => $this->limits_memory_swap,
|
||||
'mem_swappiness' => $this->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->limits_memory_reservation,
|
||||
'cpus' => (float) $this->limits_cpus,
|
||||
'cpu_shares' => $this->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->destination->network,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($this->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.expose');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.restart');
|
||||
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_limit');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpus');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpuset');
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares');
|
||||
|
||||
$docker_compose['services'][$container_name]['deploy'] = [
|
||||
'placement' => [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
],
|
||||
'mode' => 'replicated',
|
||||
'replicas' => 1,
|
||||
'update_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
'rollback_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
'labels' => $labels,
|
||||
'resources' => [
|
||||
'limits' => [
|
||||
'cpus' => $this->limits_cpus,
|
||||
'memory' => $this->limits_memory,
|
||||
],
|
||||
'reservations' => [
|
||||
'cpus' => $this->limits_cpus,
|
||||
'memory' => $this->limits_memory,
|
||||
]
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$docker_compose['services'][$container_name]['labels'] = $labels;
|
||||
}
|
||||
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($this->settings->is_gpu_enabled) {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [
|
||||
[
|
||||
'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'),
|
||||
'capabilities' => ['gpu'],
|
||||
'options' => data_get($this, 'settings.gpu_options', [])
|
||||
]
|
||||
];
|
||||
if (data_get($this, 'settings.gpu_count')) {
|
||||
$count = data_get($this, 'settings.gpu_count');
|
||||
if ($count === 'all') {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
||||
} else {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
||||
}
|
||||
} else if (data_get($this, 'settings.gpu_device_ids')) {
|
||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids');
|
||||
}
|
||||
}
|
||||
if ($this->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $container_name . '.healthcheck');
|
||||
}
|
||||
if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array;
|
||||
}
|
||||
if (count($persistentStorages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistentStorages;
|
||||
}
|
||||
if (count($volumeNames) > 0) {
|
||||
$docker_compose['volumes'] = $volumeNames;
|
||||
}
|
||||
|
||||
$docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name];
|
||||
|
||||
data_forget($docker_compose, 'services.' . $container_name);
|
||||
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"),
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
||||
{
|
||||
$pullRequestId = $deployment->pull_request_id;
|
||||
$containerName = generateApplicationContainerName($this, $pullRequestId);
|
||||
// if (count($this->ports_mappings_array) > 0) {
|
||||
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
|
||||
// if ($pullRequestId === 0) {
|
||||
// $containers = $containers->filter(function ($container) use ($containerName) {
|
||||
// return data_get($container, 'Names') !== $containerName;
|
||||
// });
|
||||
// }
|
||||
$containers->each(function ($container) use ($server, $deployment) {
|
||||
$removingContainerName = data_get($container, 'Names');
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => "docker rm -f $removingContainerName",
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
});
|
||||
// }
|
||||
$server->executeRemoteCommand(
|
||||
commands: collect([])->push([
|
||||
'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"),
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
$deployment->addLogEntry("New container started.");
|
||||
}
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||
{
|
||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||
|
||||
if ($this->git_commit_sha !== 'HEAD') {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
}
|
||||
if ($this->settings->is_git_submodules_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git submodule update --init --recursive";
|
||||
if ($public) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && sed -i \"s#git@\(.*\):#https://\\1/#g\" {$baseDir}/.gitmodules || true";
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive";
|
||||
}
|
||||
if ($this->settings->is_git_lfs_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git lfs pull";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull";
|
||||
}
|
||||
return $git_clone_command;
|
||||
}
|
||||
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null)
|
||||
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
||||
{
|
||||
$branch = $this->git_branch;
|
||||
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
||||
@@ -816,7 +554,6 @@ class Application extends BaseModel
|
||||
if ($pull_request_id !== 0) {
|
||||
$pr_branch_name = "pr-{$pull_request_id}-coolify";
|
||||
}
|
||||
|
||||
if ($this->deploymentType() === 'source') {
|
||||
$source_html_url = data_get($this, 'source.html_url');
|
||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||
@@ -828,7 +565,7 @@ class Application extends BaseModel
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
@@ -895,8 +632,7 @@ class Application extends BaseModel
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
}
|
||||
if ($git_type === 'github') {
|
||||
} else if ($git_type === 'github') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
@@ -904,6 +640,13 @@ class Application extends BaseModel
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -921,7 +664,35 @@ class Application extends BaseModel
|
||||
if ($this->deploymentType() === 'other') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
|
||||
if ($pull_request_id !== 0) {
|
||||
if ($git_type === 'gitlab') {
|
||||
$branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'github') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||
} else if ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||
}
|
||||
}
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
} else {
|
||||
@@ -934,34 +705,6 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
}
|
||||
public function prepareHelperImage(string $deploymentUuid)
|
||||
{
|
||||
$basedir = $this->generateBaseDir($deploymentUuid);
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$server = data_get($this, 'destination.server');
|
||||
$network = data_get($this, 'destination.network');
|
||||
|
||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
||||
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||
"hidden" => true,
|
||||
]);
|
||||
}
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
function parseCompose(int $pull_request_id = 0)
|
||||
{
|
||||
if ($this->docker_compose_raw) {
|
||||
@@ -1002,7 +745,7 @@ class Application extends BaseModel
|
||||
if (!$composeFileContent) {
|
||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||
$this->save();
|
||||
throw new \Exception("Could not load base compose file from $workdir$composeFile");
|
||||
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
|
||||
} else {
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
@@ -1072,4 +815,12 @@ class Application extends BaseModel
|
||||
$this->save();
|
||||
return $customLabels;
|
||||
}
|
||||
public function fqdns(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->fqdn)
|
||||
? []
|
||||
: explode(',', $this->fqdn),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,19 @@ class Environment extends Model
|
||||
return $this->applications()->count() == 0 &&
|
||||
$this->redis()->count() == 0 &&
|
||||
$this->postgresqls()->count() == 0 &&
|
||||
$this->mysqls()->count() == 0 &&
|
||||
$this->mariadbs()->count() == 0 &&
|
||||
$this->mongodbs()->count() == 0 &&
|
||||
$this->services()->count() == 0;
|
||||
}
|
||||
|
||||
public function environment_variables() {
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasMany(Application::class);
|
||||
}
|
||||
|
||||
public function postgresqls()
|
||||
{
|
||||
return $this->hasMany(StandalonePostgresql::class);
|
||||
|
||||
@@ -15,6 +15,7 @@ class EnvironmentVariable extends Model
|
||||
'value' => 'encrypted',
|
||||
'is_build_time' => 'boolean',
|
||||
];
|
||||
protected $appends = ['real_value', 'is_shared'];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
@@ -48,23 +49,80 @@ class EnvironmentVariable extends Model
|
||||
set: fn (?string $value = null) => $this->set_environment_variables($value),
|
||||
);
|
||||
}
|
||||
|
||||
public function realValue(): Attribute
|
||||
{
|
||||
$resource = null;
|
||||
if ($this->application_id) {
|
||||
$resource = Application::find($this->application_id);
|
||||
} else if ($this->service_id) {
|
||||
$resource = Service::find($this->service_id);
|
||||
} else if ($this->database_id) {
|
||||
$resource = StandalonePostgresql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMysql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneRedis::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMongodb::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMariadb::find($this->database_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Attribute::make(
|
||||
get: function () use ($resource) {
|
||||
return $this->get_real_environment_variables($this->value, $resource);
|
||||
}
|
||||
);
|
||||
}
|
||||
protected function isShared(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function () {
|
||||
$type = str($this->value)->after("{{")->before(".")->value;
|
||||
if (str($this->value)->startsWith('{{' . $type) && str($this->value)->endsWith('}}')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
||||
{
|
||||
if (!$environment_variable || !$resource) {
|
||||
return null;
|
||||
}
|
||||
$environment_variable = trim($environment_variable);
|
||||
$type = str($environment_variable)->after("{{")->before(".")->value;
|
||||
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
|
||||
$variable = Str::after($environment_variable, "{$type}.");
|
||||
$variable = Str::before($variable, '}}');
|
||||
$variable = Str::of($variable)->trim()->value;
|
||||
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
return $variable;
|
||||
}
|
||||
if ($type === 'environment') {
|
||||
$id = $resource->environment->id;
|
||||
} else if ($type === 'project') {
|
||||
$id = $resource->environment->project->id;
|
||||
} else {
|
||||
$id = $resource->team()->id;
|
||||
}
|
||||
$environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
|
||||
if ($environment_variable_found) {
|
||||
return $environment_variable_found->value;
|
||||
}
|
||||
}
|
||||
return $environment_variable;
|
||||
}
|
||||
private function get_environment_variables(?string $environment_variable = null): string|null
|
||||
{
|
||||
// $team_id = currentTeam()->id;
|
||||
if (!$environment_variable) {
|
||||
return null;
|
||||
}
|
||||
$environment_variable = trim(decrypt($environment_variable));
|
||||
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
|
||||
$variable = Str::after($environment_variable, 'global.');
|
||||
$variable = Str::before($variable, '}}');
|
||||
$variable = Str::of($variable)->trim()->value;
|
||||
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
|
||||
ray('global env variable');
|
||||
return $environment_variable;
|
||||
}
|
||||
return $environment_variable;
|
||||
return trim(decrypt($environment_variable));
|
||||
}
|
||||
|
||||
private function set_environment_variables(?string $environment_variable = null): string|null
|
||||
@@ -73,6 +131,10 @@ class EnvironmentVariable extends Model
|
||||
return null;
|
||||
}
|
||||
$environment_variable = trim($environment_variable);
|
||||
$type = str($environment_variable)->after("{{")->before(".")->value;
|
||||
if (str($environment_variable)->startsWith("{{" . $type) && str($environment_variable)->endsWith('}}')) {
|
||||
return encrypt((string) str($environment_variable)->replace(' ', ''));
|
||||
}
|
||||
return encrypt($environment_variable);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,20 +10,37 @@ class LocalFileVolume extends BaseModel
|
||||
use HasFactory;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function (LocalFileVolume $fileVolume) {
|
||||
$fileVolume->load(['service']);
|
||||
$fileVolume->saveStorageOnServer();
|
||||
});
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service)
|
||||
public function saveStorageOnServer()
|
||||
{
|
||||
$workdir = $service->service->workdir();
|
||||
$server = $service->service->server;
|
||||
$workdir = $this->resource->service->workdir();
|
||||
$server = $this->resource->service->server;
|
||||
$commands = collect([
|
||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||
"cd $workdir"
|
||||
]);
|
||||
$is_directory = $this->is_directory;
|
||||
if ($is_directory) {
|
||||
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
|
||||
}
|
||||
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
|
||||
$parent_dir = str($this->fs_path)->beforeLast('/');
|
||||
if ($parent_dir != '') {
|
||||
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
$fileVolume = $this;
|
||||
$path = Str::of(data_get($fileVolume, 'fs_path'));
|
||||
$path = str(data_get($fileVolume, 'fs_path'));
|
||||
$content = data_get($fileVolume, 'content');
|
||||
if ($path->startsWith('.')) {
|
||||
$path = $path->after('.');
|
||||
@@ -32,17 +49,18 @@ class LocalFileVolume extends BaseModel
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
||||
throw new \Exception("The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
||||
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
||||
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
|
||||
throw new \Exception("The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.");
|
||||
}
|
||||
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
}
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
}
|
||||
ray($commands->toArray());
|
||||
return instant_remote_process($commands, $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ class Project extends BaseModel
|
||||
$project->settings()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function environment_variables() {
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function environments()
|
||||
{
|
||||
return $this->hasMany(Environment::class);
|
||||
@@ -62,10 +64,13 @@ class Project extends BaseModel
|
||||
}
|
||||
public function mysqls()
|
||||
{
|
||||
return $this->hasMany(StandaloneMysql::class, Environment::class);
|
||||
return $this->hasManyThrough(StandaloneMysql::class, Environment::class);
|
||||
}
|
||||
public function mariadbs()
|
||||
{
|
||||
return $this->hasMany(StandaloneMariadb::class, Environment::class);
|
||||
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
|
||||
}
|
||||
public function resource_count() {
|
||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectSetting extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'project_id'
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -227,6 +225,32 @@ class Server extends BaseModel
|
||||
$services = $this->services();
|
||||
return $applications->concat($databases)->concat($services->get());
|
||||
}
|
||||
public function stopUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker stop -t 0 $id"], $this);
|
||||
}
|
||||
public function restartUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker restart $id"], $this);
|
||||
}
|
||||
public function startUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker start $id"], $this);
|
||||
}
|
||||
public function loadUnmanagedContainers()
|
||||
{
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.managed")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return collect($containers);
|
||||
}
|
||||
public function hasDefinedResources()
|
||||
{
|
||||
$applications = $this->applications()->count() > 0;
|
||||
@@ -247,13 +271,23 @@ class Server extends BaseModel
|
||||
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
|
||||
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
})->filter(function ($item) {
|
||||
return data_get($item, 'name') !== 'coolify-db';
|
||||
})->flatten();
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
$applications = $this->destinations()->map(function ($standaloneDocker) {
|
||||
return $standaloneDocker->applications;
|
||||
})->flatten();
|
||||
$additionalApplicationIds = DB::table('additional_destinations')->where('server_id', $this->id)->get('application_id');
|
||||
$additionalApplicationIds = collect($additionalApplicationIds)->map(function ($item) {
|
||||
return $item->application_id;
|
||||
});
|
||||
Application::whereIn('id', $additionalApplicationIds)->get()->each(function ($application) use ($applications) {
|
||||
$applications->push($application);
|
||||
});
|
||||
return $applications;
|
||||
}
|
||||
public function dockerComposeBasedApplications()
|
||||
{
|
||||
@@ -303,7 +337,8 @@ class Server extends BaseModel
|
||||
{
|
||||
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get();
|
||||
$swarm_docker = $this->hasMany(SwarmDocker::class)->get();
|
||||
return $standalone_docker->concat($swarm_docker);
|
||||
$asd = $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')->withPivot('server_id')->get();
|
||||
return $standalone_docker->concat($swarm_docker)->concat($asd);
|
||||
}
|
||||
|
||||
public function standaloneDockers()
|
||||
@@ -335,20 +370,6 @@ class Server extends BaseModel
|
||||
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
||||
return false;
|
||||
}
|
||||
// foreach ($this->applications() as $application) {
|
||||
// if (data_get($application, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// ray($this->services()->get());
|
||||
|
||||
// if ($this->id === 0) {
|
||||
// $settings = InstanceSettings::get();
|
||||
// if (data_get($settings, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
public function isFunctional()
|
||||
@@ -398,6 +419,8 @@ class Server extends BaseModel
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
$server = Server::find($this->id);
|
||||
if (!$server) {
|
||||
return false;
|
||||
@@ -405,7 +428,9 @@ class Server extends BaseModel
|
||||
if ($server->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
$uptime = instant_remote_process(['uptime'], $server, false);
|
||||
// EC2 does not have `uptime` command, lol
|
||||
|
||||
$uptime = instant_remote_process(['ls /'], $server, false);
|
||||
if (!$uptime) {
|
||||
$server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
@@ -427,6 +452,11 @@ class Server extends BaseModel
|
||||
|
||||
return true;
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
$activity = InstallDocker::run($this);
|
||||
return $activity;
|
||||
}
|
||||
public function validateDockerEngine($throwError = false)
|
||||
{
|
||||
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
||||
@@ -443,6 +473,21 @@ class Server extends BaseModel
|
||||
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
|
||||
return true;
|
||||
}
|
||||
public function validateDockerCompose($throwError = false)
|
||||
{
|
||||
$dockerCompose = instant_remote_process(["docker compose version"], $this, false);
|
||||
if (is_null($dockerCompose)) {
|
||||
$this->settings->is_usable = false;
|
||||
$this->settings->save();
|
||||
if ($throwError) {
|
||||
throw new \Exception('Server is not usable. Docker Compose is not installed.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$this->settings->is_usable = true;
|
||||
$this->settings->save();
|
||||
return true;
|
||||
}
|
||||
public function validateDockerSwarm()
|
||||
{
|
||||
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
||||
@@ -481,153 +526,4 @@ class Server extends BaseModel
|
||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||
}
|
||||
}
|
||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
||||
{
|
||||
static::$batch_counter++;
|
||||
foreach ($commands as $command) {
|
||||
$realCommand = data_get($command, 'command');
|
||||
if (is_null($realCommand)) {
|
||||
throw new \RuntimeException('Command is not set');
|
||||
}
|
||||
$hidden = data_get($command, 'hidden', false);
|
||||
$ignoreErrors = data_get($command, 'ignoreErrors', false);
|
||||
$customOutputType = data_get($command, 'customOutputType');
|
||||
$name = data_get($command, 'name');
|
||||
$remoteCommand = generateSshCommand($this, $realCommand);
|
||||
|
||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remoteCommand, function (string $type, string $output) use ($realCommand, $hidden, $customOutputType, $loggingModel, $name) {
|
||||
$output = str($output)->trim();
|
||||
if ($output->startsWith('╔')) {
|
||||
$output = "\n" . $output;
|
||||
}
|
||||
$newLogEntry = [
|
||||
'command' => remove_iip($realCommand),
|
||||
'output' => remove_iip($output),
|
||||
'type' => $customOutputType ?? $type === 'err' ? 'stderr' : 'stdout',
|
||||
'timestamp' => Carbon::now('UTC'),
|
||||
'hidden' => $hidden,
|
||||
'batch' => static::$batch_counter,
|
||||
];
|
||||
if ($loggingModel) {
|
||||
if (!$loggingModel->logs) {
|
||||
$newLogEntry['order'] = 1;
|
||||
} else {
|
||||
$previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
||||
}
|
||||
if ($name) {
|
||||
$newLogEntry['name'] = $name;
|
||||
}
|
||||
|
||||
$previousLogs[] = $newLogEntry;
|
||||
$loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
||||
$loggingModel->save();
|
||||
}
|
||||
});
|
||||
if ($loggingModel) {
|
||||
$loggingModel->update([
|
||||
'current_process_id' => $process->id(),
|
||||
]);
|
||||
}
|
||||
$processResult = $process->wait();
|
||||
if ($processResult->exitCode() !== 0) {
|
||||
if (!$ignoreErrors) {
|
||||
if ($loggingModel) {
|
||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
||||
$loggingModel->status = $status;
|
||||
$loggingModel->save();
|
||||
}
|
||||
throw new \RuntimeException($processResult->errorOutput());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public function stopApplicationRelatedRunningContainers(string $applicationId, string $containerName)
|
||||
{
|
||||
$containers = getCurrentApplicationContainerStatus($this, $applicationId, 0);
|
||||
$containers = $containers->filter(function ($container) use ($containerName) {
|
||||
return data_get($container, 'Names') !== $containerName;
|
||||
});
|
||||
$containers->each(function ($container) {
|
||||
$removableContainer = data_get($container, 'Names');
|
||||
$this->server->executeRemoteCommand(
|
||||
commands: collect([
|
||||
'command' => "docker rm -f $removableContainer >/dev/null 2>&1",
|
||||
'hidden' => true,
|
||||
'ignoreErrors' => true
|
||||
]),
|
||||
loggingModel: $this->deploymentQueueEntry
|
||||
);
|
||||
});
|
||||
}
|
||||
public function getHostIPMappings($network)
|
||||
{
|
||||
$addHosts = null;
|
||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this);
|
||||
if (!is_null($allContainers)) {
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
$ips = collect([]);
|
||||
if (count($allContainers) > 0) {
|
||||
$allContainers = $allContainers[0];
|
||||
foreach ($allContainers as $container) {
|
||||
$containerName = data_get($container, 'Name');
|
||||
if ($containerName === 'coolify-proxy') {
|
||||
continue;
|
||||
}
|
||||
$containerIp = data_get($container, 'IPv4Address');
|
||||
if ($containerName && $containerIp) {
|
||||
$containerIp = str($containerIp)->before('/');
|
||||
$ips->put($containerName, $containerIp->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
$addHosts = $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
}
|
||||
return $addHosts;
|
||||
}
|
||||
public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment)
|
||||
{
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"name" => "local_image_found",
|
||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
||||
"hidden" => true,
|
||||
]
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
if (str($deployment->getOutput('local_image_found'))->isEmpty()) {
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"command" => "docker pull {$imageName} 2>/dev/null",
|
||||
"ignoreErrors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
[
|
||||
"name" => "local_image_found",
|
||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
||||
"hidden" => true,
|
||||
]
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
}
|
||||
public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment)
|
||||
{
|
||||
$this->executeRemoteCommand(
|
||||
commands: collect([
|
||||
[
|
||||
"command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"),
|
||||
"ignoreErrors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
]),
|
||||
loggingModel: $deployment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Service\DeleteService;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@@ -17,6 +16,60 @@ class Service extends BaseModel
|
||||
{
|
||||
return 'service';
|
||||
}
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function status() {
|
||||
$foundRunning = false;
|
||||
$isDegraded = false;
|
||||
$foundRestaring = false;
|
||||
$applications = $this->applications;
|
||||
$databases = $this->databases;
|
||||
foreach ($applications as $application) {
|
||||
if ($application->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($application->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($application->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
foreach ($databases as $database) {
|
||||
if ($database->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($database->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($database->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
if ($foundRestaring) {
|
||||
return 'degraded';
|
||||
}
|
||||
if ($foundRunning && !$isDegraded) {
|
||||
return 'running';
|
||||
} else if ($foundRunning && $isDegraded) {
|
||||
return 'degraded';
|
||||
} else if (!$foundRunning && !$isDegraded) {
|
||||
return 'exited';
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
public function extraFields()
|
||||
{
|
||||
$fields = collect([]);
|
||||
@@ -423,7 +476,7 @@ class Service extends BaseModel
|
||||
$envs = $this->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
||||
}
|
||||
if ($envs->count() === 0) {
|
||||
$commands[] = "touch .env";
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServiceApplication extends BaseModel
|
||||
{
|
||||
@@ -15,6 +14,7 @@ class ServiceApplication extends BaseModel
|
||||
protected static function booted()
|
||||
{
|
||||
static::deleting(function ($service) {
|
||||
$service->update(['fqdn' => null]);
|
||||
$service->persistentStorages()->delete();
|
||||
$service->fileStorages()->delete();
|
||||
});
|
||||
@@ -23,6 +23,10 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return data_get($this, 'is_gzip_enabled', true);
|
||||
}
|
||||
public function type()
|
||||
{
|
||||
return 'service';
|
||||
@@ -55,7 +59,6 @@ class ServiceApplication extends BaseModel
|
||||
get: fn () => is_null($this->fqdn)
|
||||
? []
|
||||
: explode(',', $this->fqdn),
|
||||
|
||||
);
|
||||
}
|
||||
public function getFilesFromServer(bool $isInit = false)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user