mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-01 20:59:24 +00:00
Compare commits
417 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0590456b62 | ||
|
|
e2726d9dfb | ||
|
|
2be92a1d29 | ||
|
|
d75ed0b208 | ||
|
|
8fa1fcf96e | ||
|
|
f089185c39 | ||
|
|
fa28e952de | ||
|
|
c2e431d631 | ||
|
|
6fb88ed479 | ||
|
|
58c6d066f2 | ||
|
|
671b22e964 | ||
|
|
777bff6553 | ||
|
|
d3b3c2f7fd | ||
|
|
0b000919cf | ||
|
|
92f90d4e52 | ||
|
|
59702c6dbc | ||
|
|
13e8d3c17c | ||
|
|
5d384b1149 | ||
|
|
7b31955409 | ||
|
|
efb4049966 | ||
|
|
6da359bc60 | ||
|
|
f5f3c77d9e | ||
|
|
dc5c324f9c | ||
|
|
d283be4917 | ||
|
|
f0278bc33d | ||
|
|
052565f4e8 | ||
|
|
64146a46fc | ||
|
|
5a82395bb7 | ||
|
|
d3085e1ade | ||
|
|
af41ed26ba | ||
|
|
961ba49d89 | ||
|
|
a2150f2f7d | ||
|
|
46b549ab8d | ||
|
|
e4c3d61b76 | ||
|
|
869f0878c2 | ||
|
|
dfb6d4da3d | ||
|
|
5d4a379e8d | ||
|
|
73a265107a | ||
|
|
b77171d2f2 | ||
|
|
8b817dad87 | ||
|
|
c4436aadfa | ||
|
|
c9a7af0ffa | ||
|
|
c648e0dff9 | ||
|
|
e897eb2999 | ||
|
|
35e62a3003 | ||
|
|
b66c511160 | ||
|
|
a866bf437d | ||
|
|
a4f107e191 | ||
|
|
f22d7741a3 | ||
|
|
f4d64e121c | ||
|
|
818d5e1159 | ||
|
|
7eacdf23f9 | ||
|
|
0459baa55e | ||
|
|
a426c00a03 | ||
|
|
fd36e143e0 | ||
|
|
2243a3304b | ||
|
|
bb23141138 | ||
|
|
5f1e1c0ac4 | ||
|
|
ef8be5f133 | ||
|
|
f205c0ab9e | ||
|
|
c4cf116e6e | ||
|
|
f7b1aaca92 | ||
|
|
0ce41d2c1c | ||
|
|
d9edb1c72f | ||
|
|
93322dc3cf | ||
|
|
22f04e4708 | ||
|
|
d9a079c289 | ||
|
|
f4690bf15a | ||
|
|
a81faa68b8 | ||
|
|
4b388b463d | ||
|
|
3c98b558f6 | ||
|
|
e3c7c615c6 | ||
|
|
74e8a4a703 | ||
|
|
c5de1a25c3 | ||
|
|
d4cb7e25dc | ||
|
|
50ede5cab9 | ||
|
|
b64d4881cb | ||
|
|
4dfec6771c | ||
|
|
92ebc3f0c6 | ||
|
|
3e0821e471 | ||
|
|
38976dac12 | ||
|
|
ea5101c814 | ||
|
|
8133a8b770 | ||
|
|
3269ca3eb8 | ||
|
|
012d660886 | ||
|
|
ddb4b4d215 | ||
|
|
2725a93bfd | ||
|
|
27e82f0bde | ||
|
|
5755965b4f | ||
|
|
e4b92bb660 | ||
|
|
bc48b42ff1 | ||
|
|
94b2d67a6e | ||
|
|
7d6a895449 | ||
|
|
39e3ea9f07 | ||
|
|
465b254813 | ||
|
|
e2bc3f4841 | ||
|
|
b38a651a08 | ||
|
|
76c39a987c | ||
|
|
1d8c496906 | ||
|
|
820693ac22 | ||
|
|
d7b45a6dd2 | ||
|
|
cb2fc68dde | ||
|
|
e6e48c5812 | ||
|
|
fd855847ff | ||
|
|
35dbced3c5 | ||
|
|
e8b2d8bf03 | ||
|
|
9887d5eedd | ||
|
|
1feb8488a3 | ||
|
|
342ef4d367 | ||
|
|
85d080a042 | ||
|
|
69c48a511e | ||
|
|
10b9c4bcfa | ||
|
|
38d914076e | ||
|
|
102dd6bfb1 | ||
|
|
04379b76f2 | ||
|
|
d6fb54f3c3 | ||
|
|
1d419c6ab8 | ||
|
|
8a4e958663 | ||
|
|
b04f7686fd | ||
|
|
5aabdefaa7 | ||
|
|
a2e439686d | ||
|
|
219b21767b | ||
|
|
281c6e39a5 | ||
|
|
95548b9d9b | ||
|
|
2ed1529b95 | ||
|
|
85f037702a | ||
|
|
2caa91d772 | ||
|
|
d7350fad76 | ||
|
|
dcfb716711 | ||
|
|
0e0c81c32b | ||
|
|
802cab4a70 | ||
|
|
2c0b40ac8c | ||
|
|
001e8493ae | ||
|
|
e9256152e8 | ||
|
|
25410cb31a | ||
|
|
3dedf5548f | ||
|
|
2d2bb23708 | ||
|
|
6afc0b6303 | ||
|
|
f558f01ffd | ||
|
|
47fa955e71 | ||
|
|
96c4f5b8da | ||
|
|
6a4aa492c0 | ||
|
|
debd2a3433 | ||
|
|
192bc0f13b | ||
|
|
8756141f1d | ||
|
|
b97abc600e | ||
|
|
4b29636b42 | ||
|
|
321c51f8ed | ||
|
|
69d8f706cf | ||
|
|
98f67c5c6c | ||
|
|
1cf8d0b886 | ||
|
|
613830e6a6 | ||
|
|
2ea146333e | ||
|
|
f76d45b826 | ||
|
|
6cc86a3c82 | ||
|
|
f1e5b61970 | ||
|
|
65380646f7 | ||
|
|
189a8347ed | ||
|
|
e96e8f6fec | ||
|
|
38299ab507 | ||
|
|
f134171855 | ||
|
|
320204d854 | ||
|
|
b68199a482 | ||
|
|
6f4436fd5e | ||
|
|
0d8cc19698 | ||
|
|
a3a1ff69e1 | ||
|
|
5df7e23aa4 | ||
|
|
35d9691b3f | ||
|
|
465f649641 | ||
|
|
d909e7d802 | ||
|
|
06db6b8502 | ||
|
|
12261b9082 | ||
|
|
583ec432e8 | ||
|
|
8ffbccf7db | ||
|
|
097ca209bc | ||
|
|
439fe43a04 | ||
|
|
7fd9a799b5 | ||
|
|
7459ab22d1 | ||
|
|
133a68f3eb | ||
|
|
3224110583 | ||
|
|
810488b115 | ||
|
|
b2276147ad | ||
|
|
6c1293c63e | ||
|
|
526d675272 | ||
|
|
14b2442d40 | ||
|
|
d6d194d414 | ||
|
|
0e99f97855 | ||
|
|
14dc933219 | ||
|
|
9497f123b4 | ||
|
|
6feb439d0a | ||
|
|
e4ca5ee5f5 | ||
|
|
f21c12f39b | ||
|
|
6c1e50a914 | ||
|
|
2dbba366b7 | ||
|
|
da064def7a | ||
|
|
3af3fa5773 | ||
|
|
005bd55fb2 | ||
|
|
82a5b4c55d | ||
|
|
b8e95b2099 | ||
|
|
8ea50dc029 | ||
|
|
ec191af874 | ||
|
|
d98c742aff | ||
|
|
2529496594 | ||
|
|
1b6114036a | ||
|
|
b33fb6c39a | ||
|
|
0a6826af58 | ||
|
|
1c7034ff78 | ||
|
|
7e11698c55 | ||
|
|
1c4eb31d59 | ||
|
|
b4b6a4294a | ||
|
|
4c031a7c05 | ||
|
|
1b4a8aa58f | ||
|
|
be6d74a6a3 | ||
|
|
997a262b6c | ||
|
|
5aae65f62f | ||
|
|
3be06ced92 | ||
|
|
c0e88df3e8 | ||
|
|
85e1cbad53 | ||
|
|
c37398af72 | ||
|
|
19cfe4e514 | ||
|
|
23a1b1925f | ||
|
|
1fb8d1e14c | ||
|
|
804c70b575 | ||
|
|
548c4a4c64 | ||
|
|
2978042162 | ||
|
|
4225ec7060 | ||
|
|
893339fc8e | ||
|
|
356e7b57d2 | ||
|
|
4ee1f1a507 | ||
|
|
7d64df60cd | ||
|
|
eb3a4ca157 | ||
|
|
a7b5157fa6 | ||
|
|
793e6d19eb | ||
|
|
674fa4d09c | ||
|
|
0089e86dd1 | ||
|
|
e1d802b507 | ||
|
|
9927b71af9 | ||
|
|
b1c0f105ab | ||
|
|
35cae1d4dc | ||
|
|
3dab3365e2 | ||
|
|
1bdc7c87ba | ||
|
|
cab8ad0ca0 | ||
|
|
43409f3ff0 | ||
|
|
a5dd4cab52 | ||
|
|
a815240f4e | ||
|
|
2a44e7c5bd | ||
|
|
28c7e439b1 | ||
|
|
4396c786b4 | ||
|
|
b67bb8595f | ||
|
|
bec47487dd | ||
|
|
b110d0c12b | ||
|
|
ae425475b4 | ||
|
|
dc6aee44b3 | ||
|
|
4ffea311e8 | ||
|
|
77a6a6e46a | ||
|
|
2278ba31e7 | ||
|
|
aaeec3d340 | ||
|
|
2cbe530b7e | ||
|
|
6ada6d145c | ||
|
|
0f55e83591 | ||
|
|
4017ea7b65 | ||
|
|
a85066c644 | ||
|
|
b08d38f339 | ||
|
|
d4f4632461 | ||
|
|
666aa041f4 | ||
|
|
1c565fd502 | ||
|
|
7de2b8cbd7 | ||
|
|
852e906736 | ||
|
|
5778466947 | ||
|
|
7006239b0d | ||
|
|
49d011574d | ||
|
|
046a358ae0 | ||
|
|
d23f5af957 | ||
|
|
20a3f4b200 | ||
|
|
73acda833e | ||
|
|
fa895db76e | ||
|
|
88f33be5b6 | ||
|
|
21612cccf7 | ||
|
|
39a7332343 | ||
|
|
21825876fb | ||
|
|
aaee887d3e | ||
|
|
cb44373eff | ||
|
|
4e6ea4f584 | ||
|
|
62a93d3e51 | ||
|
|
f60c281e80 | ||
|
|
43c40cdb09 | ||
|
|
c851262d81 | ||
|
|
91783ccc3e | ||
|
|
6ba3d5f86e | ||
|
|
a9a20755a9 | ||
|
|
d2693c1ac8 | ||
|
|
aaa6f434a9 | ||
|
|
314a3ac83f | ||
|
|
36e177479e | ||
|
|
cbeebed6c9 | ||
|
|
4b905dbfad | ||
|
|
6072e7efc7 | ||
|
|
19097c6692 | ||
|
|
d37f63c63c | ||
|
|
574bafd950 | ||
|
|
2b805f869a | ||
|
|
36c4be1d17 | ||
|
|
f2d82e16d6 | ||
|
|
c6658e1ac7 | ||
|
|
22a7d85e58 | ||
|
|
b3421b47b6 | ||
|
|
b5247f77ec | ||
|
|
e63e806572 | ||
|
|
62b84add36 | ||
|
|
858ae1266f | ||
|
|
3ae990aa40 | ||
|
|
deb4b16ae1 | ||
|
|
b37dc4c73e | ||
|
|
6b08100819 | ||
|
|
2c45e7146b | ||
|
|
7c4a722d72 | ||
|
|
f4bccefaba | ||
|
|
491bb93e95 | ||
|
|
f35700c9ee | ||
|
|
bd26aca3d9 | ||
|
|
71d24773b6 | ||
|
|
781bd29f40 | ||
|
|
fd4dd1edfa | ||
|
|
d2db26cb5e | ||
|
|
01977839f7 | ||
|
|
0116892b6b | ||
|
|
4d88873524 | ||
|
|
8e46c0186d | ||
|
|
1b0e589aab | ||
|
|
02ba149e26 | ||
|
|
2981aa876c | ||
|
|
82057e1f50 | ||
|
|
4ce36631e0 | ||
|
|
995324d6b3 | ||
|
|
c61ad9cd95 | ||
|
|
26f4bcc77e | ||
|
|
c2b2d06e47 | ||
|
|
cbcc7f6d88 | ||
|
|
d05e23264b | ||
|
|
db9faed184 | ||
|
|
e7feac848a | ||
|
|
33b965d9db | ||
|
|
6c33bd9c72 | ||
|
|
c72fd2fc9d | ||
|
|
906a3dc9b4 | ||
|
|
2d3a6a4528 | ||
|
|
01abc26316 | ||
|
|
2dfe43fc3c | ||
|
|
f71861300a | ||
|
|
52d7841334 | ||
|
|
9c821e2480 | ||
|
|
f8f0aa171c | ||
|
|
38d9999814 | ||
|
|
920305432b | ||
|
|
42fb8ab379 | ||
|
|
88ab385100 | ||
|
|
479a3540ec | ||
|
|
47f5a0de81 | ||
|
|
311c118834 | ||
|
|
0c40c0d795 | ||
|
|
25f0a8f0b7 | ||
|
|
f58a1a9ecf | ||
|
|
efa2ae5177 | ||
|
|
5e55c799ec | ||
|
|
46e61cb409 | ||
|
|
b24a489c77 | ||
|
|
65a618d019 | ||
|
|
4459c9f73d | ||
|
|
3c13f1ff61 | ||
|
|
c39d6dd407 | ||
|
|
1249b1ece9 | ||
|
|
da6f2da3d0 | ||
|
|
dbc235d84a | ||
|
|
b86924bc0e | ||
|
|
0fb8cf4241 | ||
|
|
30b7e831c0 | ||
|
|
f1b4ebcde2 | ||
|
|
e3c4ebb121 | ||
|
|
2dd17cfac5 | ||
|
|
93d04ef426 | ||
|
|
70bfd4dd8a | ||
|
|
ca917d9d21 | ||
|
|
be633f0560 | ||
|
|
613e980267 | ||
|
|
4fb37054df | ||
|
|
07508df8fd | ||
|
|
2a52fb5872 | ||
|
|
f45b3cab55 | ||
|
|
eb76d63117 | ||
|
|
0964c7a338 | ||
|
|
7af151d44e | ||
|
|
7028391e57 | ||
|
|
cbae0845e7 | ||
|
|
0e512962c6 | ||
|
|
ac694b855b | ||
|
|
490d45e788 | ||
|
|
b0863eb5ea | ||
|
|
ee199ed038 | ||
|
|
41268fa20b | ||
|
|
7474896368 | ||
|
|
54c4296a25 | ||
|
|
116f5afe3c | ||
|
|
c015c8f45d | ||
|
|
fe26b3d759 | ||
|
|
afee2d8ca8 | ||
|
|
408c24c700 | ||
|
|
53032469e7 | ||
|
|
768e27d68c | ||
|
|
691ae04ca9 | ||
|
|
6585db1f9c | ||
|
|
42aa2d0088 | ||
|
|
2f87deb10b | ||
|
|
65253ca54e | ||
|
|
7cc4a21383 | ||
|
|
af464c2af7 | ||
|
|
e7e85456ea | ||
|
|
440baf6009 |
13
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
13
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: 'Create a new bug report.'
|
description: "Create a new bug report."
|
||||||
title: '[Bug]: '
|
title: "[Bug]: "
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -35,3 +35,12 @@ body:
|
|||||||
description: Coolify's version (see top of your screen).
|
description: Coolify's version (see top of your screen).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Cloud?
|
||||||
|
description: "Are you using the cloud version of Coolify?"
|
||||||
|
options:
|
||||||
|
- label: 'Yes'
|
||||||
|
required: false
|
||||||
|
- label: 'No'
|
||||||
|
required: false
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -4,5 +4,5 @@ contact_links:
|
|||||||
url: https://coollabs.io/discord
|
url: https://coollabs.io/discord
|
||||||
about: Reach out to us on Discord.
|
about: Reach out to us on Discord.
|
||||||
- name: 🙋♂️ Feature Requests
|
- name: 🙋♂️ Feature Requests
|
||||||
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
|
url: https://github.com/coollabsio/coolify/discussions/categories/new-features
|
||||||
about: All feature requests will be discussed here.
|
about: All feature requests will be discussed here.
|
||||||
|
|||||||
81
.github/workflows/pr-build.yml
vendored
Normal file
81
.github/workflows/pr-build.yml
vendored
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
name: PR Build (v4)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
branches-ignore: ["main", "v3"]
|
||||||
|
paths-ignore:
|
||||||
|
- .github/workflows/coolify-helper.yml
|
||||||
|
- docker/coolify-helper/Dockerfile
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build image and push to registry
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/prod/Dockerfile
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
needs: [amd64, aarch64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.number }}
|
||||||
|
- uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties/new)
|
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=open)
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
|
||||||
|
|
||||||
# About the Project
|
# About the Project
|
||||||
|
|
||||||
@@ -49,6 +47,8 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
||||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
||||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||||
|
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
||||||
|
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
|
|||||||
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class LoadComposeFile
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$application->loadComposeFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ class StopApplication
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application)
|
public function handle(Application $application, bool $previewDeployments = false)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
if ($application->destination->server->isSwarm()) {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||||
@@ -26,18 +26,27 @@ class StopApplication
|
|||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
if ($previewDeployments) {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
||||||
|
} else {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
|
}
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
if ($containerName) {
|
if ($containerName) {
|
||||||
instant_remote_process(
|
instant_remote_process(command: ["docker stop --time=30 $containerName"], server: $server, throwError: false);
|
||||||
["docker rm -f {$containerName}"],
|
instant_remote_process(command: ["docker rm $containerName"], server: $server, throwError: false);
|
||||||
$server
|
instant_remote_process(command: ["docker rm -f {$containerName}"], server: $server, throwError: false);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
// remove network
|
||||||
|
$uuid = $application->uuid;
|
||||||
|
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||||
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
StopDatabase::run($database);
|
||||||
|
|
||||||
|
return StartDatabase::run($database);
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\StandaloneClickhouse;
|
||||||
|
use App\Models\StandaloneDragonfly;
|
||||||
|
use App\Models\StandaloneKeydb;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StartDatabase
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||||
|
{
|
||||||
|
$server = $database->destination->server;
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
switch ($database->getMorphClass()) {
|
||||||
|
case 'App\Models\StandalonePostgresql':
|
||||||
|
$activity = StartPostgresql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneRedis':
|
||||||
|
$activity = StartRedis::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMongodb':
|
||||||
|
$activity = StartMongodb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMysql':
|
||||||
|
$activity = StartMysql::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneMariadb':
|
||||||
|
$activity = StartMariadb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneKeydb':
|
||||||
|
$activity = StartKeydb::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneDragonfly':
|
||||||
|
$activity = StartDragonfly::run($database);
|
||||||
|
break;
|
||||||
|
case 'App\Models\StandaloneClickhouse':
|
||||||
|
$activity = StartClickhouse::run($database);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($database->is_public && $database->public_port) {
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,14 +22,13 @@ class StopDatabase
|
|||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
instant_remote_process(
|
|
||||||
["docker rm -f {$database->uuid}"],
|
instant_remote_process(command: ["docker stop --time=30 $database->uuid"], server: $server, throwError: false);
|
||||||
$server
|
instant_remote_process(command: ["docker rm $database->uuid"], server: $server, throwError: false);
|
||||||
);
|
instant_remote_process(command: ["docker rm -f $database->uuid"], server: $server, throwError: false);
|
||||||
|
|
||||||
if ($database->is_public) {
|
if ($database->is_public) {
|
||||||
StopDatabaseProxy::run($database);
|
StopDatabaseProxy::run($database);
|
||||||
}
|
}
|
||||||
// TODO: make notification for services
|
|
||||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ class StopDatabaseProxy
|
|||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
|
||||||
$database->save();
|
$database->save();
|
||||||
DatabaseStatusChanged::dispatch();
|
DatabaseStatusChanged::dispatch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use App\Models\ServiceDatabase;
|
|||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use App\Notifications\Container\ContainerStopped;
|
use App\Notifications\Container\ContainerStopped;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class GetContainersStatus
|
class GetContainersStatus
|
||||||
@@ -20,13 +21,16 @@ class GetContainersStatus
|
|||||||
|
|
||||||
public $applications;
|
public $applications;
|
||||||
|
|
||||||
|
public ?Collection $containers;
|
||||||
|
|
||||||
|
public ?Collection $containerReplicates;
|
||||||
|
|
||||||
public $server;
|
public $server;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server, ?Collection $containers = null, ?Collection $containerReplicates = null)
|
||||||
{
|
{
|
||||||
// if (isDev()) {
|
$this->containers = $containers;
|
||||||
// $server = Server::find(0);
|
$this->containerReplicates = $containerReplicates;
|
||||||
// }
|
|
||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not ready.';
|
||||||
@@ -66,322 +70,312 @@ class GetContainersStatus
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sentinel()
|
// private function sentinel()
|
||||||
{
|
// {
|
||||||
try {
|
// try {
|
||||||
$containers = $this->server->getContainers();
|
// $this->containers = $this->server->getContainersWithSentinel();
|
||||||
if ($containers->count() === 0) {
|
// if ($this->containers->count() === 0) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
$databases = $this->server->databases();
|
// $databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
// $services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
// $previews = $this->server->previews();
|
||||||
$foundApplications = [];
|
// $foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
// $foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
// $foundDatabases = [];
|
||||||
$foundServices = [];
|
// $foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
// foreach ($this->containers as $container) {
|
||||||
$labels = Arr::undot(data_get($container, 'labels'));
|
// $labels = Arr::undot(data_get($container, 'labels'));
|
||||||
$containerStatus = data_get($container, 'state');
|
// $containerStatus = data_get($container, 'state');
|
||||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
// $containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
// $containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
// $applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($applicationId) {
|
// if ($applicationId) {
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
if ($pullRequestId) {
|
// if ($pullRequestId) {
|
||||||
if (str($applicationId)->contains('-')) {
|
// if (str($applicationId)->contains('-')) {
|
||||||
$applicationId = str($applicationId)->before('-');
|
// $applicationId = str($applicationId)->before('-');
|
||||||
}
|
// }
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
// if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
// $foundApplicationPreviews[] = $preview->id;
|
||||||
$statusFromDb = $preview->status;
|
// $statusFromDb = $preview->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$preview->update(['status' => $containerStatus]);
|
// $preview->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
// $application = $this->applications->where('id', $applicationId)->first();
|
||||||
if ($application) {
|
// if ($application) {
|
||||||
$foundApplications[] = $application->id;
|
// $foundApplications[] = $application->id;
|
||||||
$statusFromDb = $application->status;
|
// $statusFromDb = $application->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$application->update(['status' => $containerStatus]);
|
// $application->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
// $uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
$type = data_get($labels, 'coolify.type');
|
// $type = data_get($labels, 'coolify.type');
|
||||||
if ($uuid) {
|
// if ($uuid) {
|
||||||
if ($type === 'service') {
|
// if ($type === 'service') {
|
||||||
$database_id = data_get($labels, 'coolify.service.subId');
|
// $database_id = data_get($labels, 'coolify.service.subId');
|
||||||
if ($database_id) {
|
// if ($database_id) {
|
||||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
if ($service_db) {
|
// if ($service_db) {
|
||||||
$uuid = $service_db->service->uuid;
|
// $uuid = $service_db->service->uuid;
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
// $isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
// return data_get($value, 'name') === "$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundTcpProxy) {
|
// if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($service_db);
|
// StartDatabaseProxy::run($service_db);
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
// $database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
// if ($database) {
|
||||||
$isPublic = data_get($database, 'is_public');
|
// $isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
// $foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
// $statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
// $database->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === "$uuid-proxy";
|
// return data_get($value, 'name') === "$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundTcpProxy) {
|
// if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
// StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// Notify user that this container should not be there.
|
// // 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;
|
// $foundDatabases[] = 0;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
if ($serviceLabelId) {
|
// if ($serviceLabelId) {
|
||||||
$subType = data_get($labels, 'coolify.service.subType');
|
// $subType = data_get($labels, 'coolify.service.subType');
|
||||||
$subId = data_get($labels, 'coolify.service.subId');
|
// $subId = data_get($labels, 'coolify.service.subId');
|
||||||
$service = $services->where('id', $serviceLabelId)->first();
|
// $service = $services->where('id', $serviceLabelId)->first();
|
||||||
if (! $service) {
|
// if (! $service) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
if ($subType === 'application') {
|
// if ($subType === 'application') {
|
||||||
$service = $service->applications()->where('id', $subId)->first();
|
// $service = $service->applications()->where('id', $subId)->first();
|
||||||
} else {
|
// } else {
|
||||||
$service = $service->databases()->where('id', $subId)->first();
|
// $service = $service->databases()->where('id', $subId)->first();
|
||||||
}
|
// }
|
||||||
if ($service) {
|
// if ($service) {
|
||||||
$foundServices[] = "$service->id-$service->name";
|
// $foundServices[] = "$service->id-$service->name";
|
||||||
$statusFromDb = $service->status;
|
// $statusFromDb = $service->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
// // ray('Updating status: ' . $containerStatus);
|
||||||
$service->update(['status' => $containerStatus]);
|
// $service->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = collect([]);
|
// $exitedServices = collect([]);
|
||||||
foreach ($services as $service) {
|
// foreach ($services as $service) {
|
||||||
$apps = $service->applications()->get();
|
// $apps = $service->applications()->get();
|
||||||
$dbs = $service->databases()->get();
|
// $dbs = $service->databases()->get();
|
||||||
foreach ($apps as $app) {
|
// foreach ($apps as $app) {
|
||||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
// if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($app);
|
// $exitedServices->push($app);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
foreach ($dbs as $db) {
|
// foreach ($dbs as $db) {
|
||||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
// if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($db);
|
// $exitedServices->push($db);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = $exitedServices->unique('id');
|
// $exitedServices = $exitedServices->unique('id');
|
||||||
foreach ($exitedServices as $exitedService) {
|
// foreach ($exitedServices as $exitedService) {
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
// if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$name = data_get($exitedService, 'name');
|
// $name = data_get($exitedService, 'name');
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
// $fqdn = data_get($exitedService, 'fqdn');
|
||||||
if ($name) {
|
// if ($name) {
|
||||||
if ($fqdn) {
|
// if ($fqdn) {
|
||||||
$containerName = "$name, available at $fqdn";
|
// $containerName = "$name, available at $fqdn";
|
||||||
} else {
|
// } else {
|
||||||
$containerName = $name;
|
// $containerName = $name;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if ($fqdn) {
|
// if ($fqdn) {
|
||||||
$containerName = $fqdn;
|
// $containerName = $fqdn;
|
||||||
} else {
|
// } else {
|
||||||
$containerName = null;
|
// $containerName = null;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
// $projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
$serviceUuid = data_get($service, 'uuid');
|
// $serviceUuid = data_get($service, 'uuid');
|
||||||
$environmentName = data_get($service, 'environment.name');
|
// $environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
// if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
// $exitedService->update(['status' => 'exited']);
|
||||||
}
|
// }
|
||||||
|
|
||||||
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
// $notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
// foreach ($notRunningApplications as $applicationId) {
|
||||||
$application = $this->applications->where('id', $applicationId)->first();
|
// $application = $this->applications->where('id', $applicationId)->first();
|
||||||
if (str($application->status)->startsWith('exited')) {
|
// if (str($application->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$application->update(['status' => 'exited']);
|
// $application->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($application, 'name');
|
// $name = data_get($application, 'name');
|
||||||
$fqdn = data_get($application, 'fqdn');
|
// $fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
// $projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
$applicationUuid = data_get($application, 'uuid');
|
// $applicationUuid = data_get($application, 'uuid');
|
||||||
$environment = data_get($application, 'environment.name');
|
// $environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environment) {
|
// if ($projectUuid && $applicationUuid && $environment) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
// foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
$preview = $previews->where('id', $previewId)->first();
|
// $preview = $previews->where('id', $previewId)->first();
|
||||||
if (str($preview->status)->startsWith('exited')) {
|
// if (str($preview->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$preview->update(['status' => 'exited']);
|
// $preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($preview, 'name');
|
// $name = data_get($preview, 'name');
|
||||||
$fqdn = data_get($preview, 'fqdn');
|
// $fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
$environmentName = data_get($preview, 'application.environment.name');
|
// $environmentName = data_get($preview, 'application.environment.name');
|
||||||
$applicationUuid = data_get($preview, 'application.uuid');
|
// $applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
// if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
// foreach ($notRunningDatabases as $database) {
|
||||||
$database = $databases->where('id', $database)->first();
|
// $database = $databases->where('id', $database)->first();
|
||||||
if (str($database->status)->startsWith('exited')) {
|
// if (str($database->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$database->update(['status' => 'exited']);
|
// $database->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($database, 'name');
|
// $name = data_get($database, 'name');
|
||||||
$fqdn = data_get($database, 'fqdn');
|
// $fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name;
|
// $containerName = $name;
|
||||||
|
|
||||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
// $projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
$environmentName = data_get($database, 'environment.name');
|
// $environmentName = data_get($database, 'environment.name');
|
||||||
$databaseUuid = data_get($database, 'uuid');
|
// $databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
// if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
// $url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// // $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if proxy is running
|
// // Check if proxy is running
|
||||||
$this->server->proxyType();
|
// $this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
// $foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
// TODO: fix this with sentinel
|
// // TODO: fix this with sentinel
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'name') === 'coolify-proxy';
|
// return data_get($value, 'name') === 'coolify-proxy';
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (! $foundProxyContainer) {
|
// if (! $foundProxyContainer) {
|
||||||
try {
|
// try {
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
// $shouldStart = CheckProxy::run($this->server);
|
||||||
if ($shouldStart) {
|
// if ($shouldStart) {
|
||||||
StartProxy::run($this->server, false);
|
// StartProxy::run($this->server, false);
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
}
|
// }
|
||||||
} catch (\Throwable $e) {
|
// } catch (\Throwable $e) {
|
||||||
ray($e);
|
// ray($e);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
// $this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||||
$this->server->save();
|
// $this->server->save();
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
// }
|
||||||
} catch (\Exception $e) {
|
// } catch (\Exception $e) {
|
||||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
// // send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
// ray($e->getMessage());
|
||||||
|
|
||||||
return handleError($e);
|
// return handleError($e);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private function old_way()
|
private function old_way()
|
||||||
{
|
{
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->containers === null) {
|
||||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
['containers' => $this->containers,'containerReplicates' => $this->containerReplicates] = $this->server->getContainers();
|
||||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
|
||||||
} else {
|
|
||||||
// Precheck for containers
|
|
||||||
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
|
|
||||||
if (! $containers) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
|
||||||
$containerReplicates = null;
|
|
||||||
}
|
}
|
||||||
if (is_null($containers)) {
|
|
||||||
|
if (is_null($this->containers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
if ($this->containerReplicates) {
|
||||||
if ($containerReplicates) {
|
foreach ($this->containerReplicates as $containerReplica) {
|
||||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
|
||||||
foreach ($containerReplicates as $containerReplica) {
|
|
||||||
$name = data_get($containerReplica, 'Name');
|
$name = data_get($containerReplica, 'Name');
|
||||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
$this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
if (data_get($container, 'Spec.Name') === $name) {
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
$replicas = data_get($containerReplica, 'Replicas');
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
$running = str($replicas)->explode('/')[0];
|
$running = str($replicas)->explode('/')[0];
|
||||||
@@ -407,7 +401,7 @@ class GetContainersStatus
|
|||||||
$foundDatabases = [];
|
$foundDatabases = [];
|
||||||
$foundServices = [];
|
$foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
foreach ($this->containers as $container) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
$labels = data_get($container, 'Spec.Labels');
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
$uuid = data_get($labels, 'coolify.name');
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
@@ -461,7 +455,7 @@ class GetContainersStatus
|
|||||||
if ($uuid) {
|
if ($uuid) {
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
@@ -486,7 +480,7 @@ class GetContainersStatus
|
|||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
}
|
}
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
} else {
|
||||||
@@ -659,7 +653,7 @@ class GetContainersStatus
|
|||||||
|
|
||||||
// Check if proxy is running
|
// Check if proxy is running
|
||||||
$this->server->proxyType();
|
$this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class CheckConfiguration
|
|||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
];
|
];
|
||||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
|
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ class StartProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class CleanupDocker
|
|||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server, bool $force = true)
|
||||||
{
|
{
|
||||||
|
// cleanup docker images, containers, and builder caches
|
||||||
if ($force) {
|
if ($force) {
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
instant_remote_process(['docker image prune -af'], $server, false);
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
|
|||||||
@@ -24,12 +24,7 @@ class InstallLogDrain
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($type === 'none') {
|
if ($type === 'none') {
|
||||||
$command = [
|
return 'No log drain is enabled.';
|
||||||
"echo 'Stopping old Fluent Bit'",
|
|
||||||
'docker rm -f coolify-log-drain || true',
|
|
||||||
];
|
|
||||||
|
|
||||||
return instant_remote_process($command, $server);
|
|
||||||
} elseif ($type === 'newrelic') {
|
} elseif ($type === 'newrelic') {
|
||||||
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
if (! $server->settings->is_logdrain_newrelic_enabled) {
|
||||||
throw new \Exception('New Relic log drain is not enabled.');
|
throw new \Exception('New Relic log drain is not enabled.');
|
||||||
|
|||||||
20
app/Actions/Server/StopLogDrain.php
Normal file
20
app/Actions/Server/StopLogDrain.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopLogDrain
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return instant_remote_process(['docker rm -f coolify-log-drain || true'], $server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ namespace App\Actions\Server;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
@@ -20,12 +22,16 @@ class UpdateCoolify
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
ray('Running InstanceAutoUpdateJob');
|
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
||||||
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
|
if ($response->successful()) {
|
||||||
|
$versions = $response->json();
|
||||||
|
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
if (! $manual_update) {
|
if (! $manual_update) {
|
||||||
@@ -40,6 +46,8 @@ class UpdateCoolify
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
|
$settings->new_version_available = false;
|
||||||
|
$settings->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
@@ -48,7 +56,6 @@ class UpdateCoolify
|
|||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ray('Running in dev mode');
|
|
||||||
remote_process([
|
remote_process([
|
||||||
'sleep 10',
|
'sleep 10',
|
||||||
], $this->server);
|
], $this->server);
|
||||||
|
|||||||
67
app/Actions/Server/ValidateServer.php
Normal file
67
app/Actions/Server/ValidateServer.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ValidateServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public ?string $uptime = null;
|
||||||
|
|
||||||
|
public ?string $error = null;
|
||||||
|
|
||||||
|
public ?string $supported_os_type = null;
|
||||||
|
|
||||||
|
public ?string $docker_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_version = null;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
|
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->supported_os_type = $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="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->docker_installed = $server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $server->validateDockerCompose();
|
||||||
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->docker_version = $server->validateDockerEngineVersion();
|
||||||
|
|
||||||
|
if ($this->docker_version) {
|
||||||
|
return 'OK';
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Service;
|
||||||
|
|
||||||
|
use App\Models\Service;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class RestartService
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Service $service)
|
||||||
|
{
|
||||||
|
StopService::run($service);
|
||||||
|
|
||||||
|
return StartService::run($service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,20 +19,21 @@ class StopService
|
|||||||
ray('Stopping service: '.$service->name);
|
ray('Stopping service: '.$service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
|
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
|
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
$application->update(['status' => 'exited']);
|
$application->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
$dbs = $service->databases()->get();
|
||||||
foreach ($dbs as $db) {
|
foreach ($dbs as $db) {
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
|
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
|
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
|
||||||
$db->update(['status' => 'exited']);
|
$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 disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||||
// TODO: make notification for databases
|
|
||||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
echo $e->getMessage();
|
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return $e->getMessage();
|
return $e->getMessage();
|
||||||
|
|||||||
@@ -18,7 +18,12 @@ class CleanupDatabase extends Command
|
|||||||
} else {
|
} else {
|
||||||
echo "Running database cleanup in dry-run mode...\n";
|
echo "Running database cleanup in dry-run mode...\n";
|
||||||
}
|
}
|
||||||
$keep_days = 60;
|
if (isCloud()) {
|
||||||
|
// Later on we can increase this to 180 days or dynamically set
|
||||||
|
$keep_days = 60;
|
||||||
|
} else {
|
||||||
|
$keep_days = 60;
|
||||||
|
}
|
||||||
echo "Keep days: $keep_days\n";
|
echo "Keep days: $keep_days\n";
|
||||||
// Cleanup failed jobs table
|
// Cleanup failed jobs table
|
||||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(1));
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class CleanupUnreachableServers extends Command
|
|||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4',
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
|
|||||||
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCleanupSubscriptions extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cloud:cleanup-subs';
|
||||||
|
|
||||||
|
protected $description = 'Cleanup subcriptions teams';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (! isCloud()) {
|
||||||
|
$this->error('This command can only be run on cloud');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ray()->clearAll();
|
||||||
|
$this->info('Cleaning up subcriptions teams');
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
|
||||||
|
$teams = Team::all()->filter(function ($team) {
|
||||||
|
return $team->id !== 0;
|
||||||
|
})->sortBy('id');
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team) {
|
||||||
|
$this->info("Checking team {$team->id}");
|
||||||
|
}
|
||||||
|
if (! data_get($team, 'subscription')) {
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||||
|
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||||
|
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||||
|
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||||
|
$status = data_get($subscription, 'status');
|
||||||
|
if ($status === 'active' || $status === 'past_due') {
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => true,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->info('Subscription status: '.$status);
|
||||||
|
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||||
|
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||||
|
if (! $confirm) {
|
||||||
|
$this->info("Skipping team {$team->id} {$team->name}");
|
||||||
|
} else {
|
||||||
|
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||||
|
$team->subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
'stripe_trial_already_ended' => false,
|
||||||
|
'stripe_subscription_id' => null,
|
||||||
|
]);
|
||||||
|
$this->disableServers($team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function disableServers(Team $team)
|
||||||
|
{
|
||||||
|
foreach ($team->servers as $server) {
|
||||||
|
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
|
||||||
|
$this->info("Disabling server {$server->id} {$server->name}");
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_usable' => false,
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,13 +9,41 @@ use Illuminate\Support\Facades\Process;
|
|||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'dev:init';
|
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||||
|
|
||||||
protected $description = 'Init the app in dev mode';
|
protected $description = 'Helper commands for development.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('init')) {
|
||||||
|
$this->init();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->option('generate-openapi')) {
|
||||||
|
$this->generateOpenApi();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateOpenApi()
|
||||||
|
{
|
||||||
|
// Generate OpenAPI documentation
|
||||||
|
echo "Generating OpenAPI documentation.\n";
|
||||||
|
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||||
|
$error = $process->errorOutput();
|
||||||
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
echo $error;
|
||||||
|
echo $process->output();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(env('APP_KEY'))) {
|
||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
set_transanctional_email_settings();
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('Test Email');
|
$this->mail->subject('Test Email');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'updates':
|
case 'updates':
|
||||||
@@ -107,7 +107,7 @@ class Emails extends Command
|
|||||||
$confirmed = confirm('Are you sure?');
|
$confirmed = confirm('Are you sure?');
|
||||||
if ($confirmed) {
|
if ($confirmed) {
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->subject('One-click Services, Docker Compose support');
|
$this->mail->subject('One-click Services, Docker Compose support');
|
||||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||||
'token' => encrypt($email),
|
'token' => encrypt($email),
|
||||||
@@ -118,7 +118,7 @@ class Emails extends Command
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'emails-test':
|
case 'emails-test':
|
||||||
$this->mail = (new Test())->toMail();
|
$this->mail = (new Test)->toMail();
|
||||||
$this->sendEmail();
|
$this->sendEmail();
|
||||||
break;
|
break;
|
||||||
case 'database-backup-statuses-daily':
|
case 'database-backup-statuses-daily':
|
||||||
@@ -224,7 +224,7 @@ class Emails extends Command
|
|||||||
// $this->sendEmail();
|
// $this->sendEmail();
|
||||||
// break;
|
// break;
|
||||||
case 'waitlist-invitation-link':
|
case 'waitlist-invitation-link':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.waitlist-invitation', [
|
$this->mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => 'https://coolify.io',
|
'loginLink' => 'https://coolify.io',
|
||||||
]);
|
]);
|
||||||
@@ -241,7 +241,7 @@ class Emails extends Command
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 'realusers-before-trial':
|
case 'realusers-before-trial':
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.before-trial-conversion');
|
$this->mail->view('emails.before-trial-conversion');
|
||||||
$this->mail->subject('Trial period has been added for all subscription plans.');
|
$this->mail->subject('Trial period has been added for all subscription plans.');
|
||||||
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
|
||||||
@@ -287,7 +287,7 @@ class Emails extends Command
|
|||||||
foreach ($admins as $admin) {
|
foreach ($admins as $admin) {
|
||||||
$this->info($admin);
|
$this->info($admin);
|
||||||
}
|
}
|
||||||
$this->mail = new MailMessage();
|
$this->mail = new MailMessage;
|
||||||
$this->mail->view('emails.server-lost-connection', [
|
$this->mail->view('emails.server-lost-connection', [
|
||||||
'name' => $server->name,
|
'name' => $server->name,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\StopSentinel;
|
||||||
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
@@ -11,21 +13,36 @@ use App\Models\ScheduledDatabaseBackup;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments} {--cleanup-proxy-networks}';
|
||||||
|
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
|
public $servers = null;
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->servers = Server::all();
|
||||||
$this->alive();
|
$this->alive();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
if ($server->settings->is_metrics_enabled === true) {
|
||||||
|
$server->settings->update(['is_metrics_enabled' => false]);
|
||||||
|
}
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
StopSentinel::dispatch($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
|
$cleanup_proxy_networks = $this->option('cleanup-proxy-networks');
|
||||||
$this->replace_slash_in_environment_name();
|
$this->replace_slash_in_environment_name();
|
||||||
if ($cleanup_deployments) {
|
if ($cleanup_deployments) {
|
||||||
echo "Running cleanup deployments.\n";
|
echo "Running cleanup deployments.\n";
|
||||||
@@ -33,17 +50,26 @@ class Init extends Command
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($cleanup_proxy_networks) {
|
||||||
|
echo "Running cleanup proxy networks.\n";
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($full_cleanup) {
|
if ($full_cleanup) {
|
||||||
// Required for falsely deleted coolify db
|
// Required for falsely deleted coolify db
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
$this->update_traefik_labels();
|
||||||
|
$this->cleanup_unused_network_from_coolify_proxy();
|
||||||
|
$this->cleanup_unnecessary_dynamic_proxy_configuration();
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
$this->cleanup_stucked_helper_containers();
|
$this->cleanup_stucked_helper_containers();
|
||||||
$this->call('cleanup:queue');
|
$this->call('cleanup:queue');
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
try {
|
try {
|
||||||
$server = Server::find(0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$server->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
@@ -57,6 +83,13 @@ class Init extends Command
|
|||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isCloud()) {
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -64,6 +97,79 @@ class Init extends Command
|
|||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function update_traefik_labels()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Server::where('proxy->type', 'TRAEFIK_V2')->update(['proxy->type' => 'TRAEFIK']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in updating traefik labels: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||||
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
try {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($server->id === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$file = $server->proxyPath().'/dynamic/coolify.yaml';
|
||||||
|
|
||||||
|
return instant_remote_process([
|
||||||
|
"rm -f $file",
|
||||||
|
], $server, false);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unnecessary dynamic proxy configuration: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
|
{
|
||||||
|
foreach ($this->servers as $server) {
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (! $server->isProxyShouldRun()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
['networks' => $networks, 'allNetworks' => $allNetworks] = collectDockerNetworksByServer($server);
|
||||||
|
$removeNetworks = $allNetworks->diff($networks);
|
||||||
|
$commands = collect();
|
||||||
|
foreach ($removeNetworks as $network) {
|
||||||
|
$out = instant_remote_process(["docker network inspect -f json $network | jq '.[].Containers | if . == {} then null else . end'"], $server, false);
|
||||||
|
if (empty($out)) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
} else {
|
||||||
|
$data = collect(json_decode($out, true));
|
||||||
|
if ($data->count() === 1) {
|
||||||
|
// If only coolify-proxy itself is connected to that network (it should not be possible, but who knows)
|
||||||
|
$isCoolifyProxyItself = data_get($data->first(), 'Name') === 'coolify-proxy';
|
||||||
|
if ($isCoolifyProxyItself) {
|
||||||
|
$commands->push("docker network disconnect $network coolify-proxy >/dev/null 2>&1 || true");
|
||||||
|
$commands->push("docker network rm $network >/dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($commands->isNotEmpty()) {
|
||||||
|
echo "Cleaning up unused networks from coolify proxy\n";
|
||||||
|
remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning up unused networks from coolify proxy: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -91,8 +197,7 @@ class Init extends Command
|
|||||||
|
|
||||||
private function cleanup_stucked_helper_containers()
|
private function cleanup_stucked_helper_containers()
|
||||||
{
|
{
|
||||||
$servers = Server::all();
|
foreach ($this->servers as $server) {
|
||||||
foreach ($servers as $server) {
|
|
||||||
if ($server->isFunctional()) {
|
if ($server->isFunctional()) {
|
||||||
CleanupHelperContainersJob::dispatch($server);
|
CleanupHelperContainersJob::dispatch($server);
|
||||||
}
|
}
|
||||||
@@ -137,7 +242,6 @@ class Init extends Command
|
|||||||
private function cleanup_in_progress_application_deployments()
|
private function cleanup_in_progress_application_deployments()
|
||||||
{
|
{
|
||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class WaitlistInvite extends Command
|
|||||||
{
|
{
|
||||||
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
|
||||||
$loginLink = route('auth.link', ['token' => $token]);
|
$loginLink = route('auth.link', ['token' => $token]);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.waitlist-invitation', [
|
$mail->view('emails.waitlist-invitation', [
|
||||||
'loginLink' => $loginLink,
|
'loginLink' => $loginLink,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullCoolifyImageJob;
|
use App\Jobs\PullCoolifyImageJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -26,25 +28,26 @@ class Kernel extends ConsoleKernel
|
|||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
$this->all_servers = Server::all();
|
$this->all_servers = Server::all();
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
$this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->check_scheduled_backups($schedule);
|
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||||
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
|
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
|
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
$this->schedule_updates($schedule);
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
@@ -59,12 +62,26 @@ class Kernel extends ConsoleKernel
|
|||||||
|
|
||||||
private function pull_images($schedule)
|
private function pull_images($schedule)
|
||||||
{
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
$schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
$schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function schedule_updates($schedule)
|
||||||
|
{
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
|
$updateCheckFrequency = $settings->update_check_frequency;
|
||||||
|
$schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer();
|
||||||
|
|
||||||
|
if ($settings->is_auto_update_enabled) {
|
||||||
|
$autoUpdateFrequency = $settings->auto_update_frequency;
|
||||||
|
$schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,19 +91,12 @@ class Kernel extends ConsoleKernel
|
|||||||
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||||
$own = Team::find(0)->servers;
|
$own = Team::find(0)->servers;
|
||||||
$servers = $servers->merge($own);
|
$servers = $servers->merge($own);
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
} else {
|
} else {
|
||||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
|
||||||
}
|
|
||||||
foreach ($containerServers as $server) {
|
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
|
||||||
if ($server->isLogDrainEnabled()) {
|
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
app/Enums/BuildPackTypes.php
Normal file
11
app/Enums/BuildPackTypes.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum BuildPackTypes: string
|
||||||
|
{
|
||||||
|
case NIXPACKS = 'nixpacks';
|
||||||
|
case STATIC = 'static';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
}
|
||||||
15
app/Enums/NewDatabaseTypes.php
Normal file
15
app/Enums/NewDatabaseTypes.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewDatabaseTypes: string
|
||||||
|
{
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
||||||
22
app/Enums/NewResourceTypes.php
Normal file
22
app/Enums/NewResourceTypes.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum NewResourceTypes: string
|
||||||
|
{
|
||||||
|
case PUBLIC = 'public';
|
||||||
|
case PRIVATE_GH_APP = 'private-gh-app';
|
||||||
|
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
|
||||||
|
case DOCKERFILE = 'dockerfile';
|
||||||
|
case DOCKERCOMPOSE = 'dockercompose';
|
||||||
|
case DOCKER_IMAGE = 'docker-image';
|
||||||
|
case SERVICE = 'service';
|
||||||
|
case POSTGRESQL = 'postgresql';
|
||||||
|
case MYSQL = 'mysql';
|
||||||
|
case MONGODB = 'mongodb';
|
||||||
|
case REDIS = 'redis';
|
||||||
|
case MARIADB = 'mariadb';
|
||||||
|
case KEYDB = 'keydb';
|
||||||
|
case DRAGONFLY = 'dragonfly';
|
||||||
|
case CLICKHOUSE = 'clickhouse';
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ namespace App\Enums;
|
|||||||
enum ProxyTypes: string
|
enum ProxyTypes: string
|
||||||
{
|
{
|
||||||
case NONE = 'NONE';
|
case NONE = 'NONE';
|
||||||
case TRAEFIK_V2 = 'TRAEFIK_V2';
|
case TRAEFIK = 'TRAEFIK';
|
||||||
case NGINX = 'NGINX';
|
case NGINX = 'NGINX';
|
||||||
case CADDY = 'CADDY';
|
case CADDY = 'CADDY';
|
||||||
}
|
}
|
||||||
|
|||||||
10
app/Enums/RedirectTypes.php
Normal file
10
app/Enums/RedirectTypes.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum RedirectTypes: string
|
||||||
|
{
|
||||||
|
case BOTH = 'both';
|
||||||
|
case WWW = 'www';
|
||||||
|
case NON_WWW = 'non-www';
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@@ -20,15 +20,19 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
return [
|
if ($this->userId) {
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
return [
|
||||||
];
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
app/Events/FileStorageChanged.php
Normal file
32
app/Events/FileStorageChanged.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class FileStorageChanged implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
ray($teamId);
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public $userId;
|
public ?string $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
@@ -20,15 +20,19 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
$userId = auth()->user()->id ?? null;
|
$userId = auth()->user()->id ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
throw new \Exception('User id is null');
|
return false;
|
||||||
}
|
}
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
return [
|
if (! is_null($this->userId)) {
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
return [
|
||||||
];
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Handler extends ExceptionHandler
|
|||||||
return response()->json(['message' => $exception->getMessage()], 401);
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
return redirect()->guest($exception->redirectTo($request) ?? route('login'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
|
|||||||
if ($e instanceof RuntimeException) {
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
if ($this->settings->do_not_track) {
|
if ($this->settings->do_not_track) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\Project;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Applications extends Controller
|
|
||||||
{
|
|
||||||
public function applications(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
|
||||||
$applications = collect();
|
|
||||||
$applications->push($projects->pluck('applications')->flatten());
|
|
||||||
$applications = $applications->flatten();
|
|
||||||
|
|
||||||
return response()->json($applications);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function application_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::where('uuid', $uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($application);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function update_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->collect()->count() == 0) {
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'No data provided.',
|
|
||||||
], 400);
|
|
||||||
}
|
|
||||||
$application = Application::where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
ray($request->collect());
|
|
||||||
|
|
||||||
// if ($request->has('domains')) {
|
|
||||||
// $existingDomains = explode(',', $application->fqdn);
|
|
||||||
// $newDomains = $request->domains;
|
|
||||||
// $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) {
|
|
||||||
// return ! in_array($domain, $existingDomains);
|
|
||||||
// });
|
|
||||||
// $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains));
|
|
||||||
// $application->fqdn = implode(',', $mergedDomains);
|
|
||||||
// $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
|
||||||
// $application->save();
|
|
||||||
// }
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => 'Application updated successfully.',
|
|
||||||
'application' => serialize_api_response($application),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_deploy(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
$instant_deploy = $request->query->get('instant_deploy') ?? false;
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::where('uuid', $uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $application,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
force_rebuild: $force,
|
|
||||||
is_api: true,
|
|
||||||
no_questions_asked: $instant_deploy
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json(
|
|
||||||
[
|
|
||||||
'message' => 'Deployment request queued.',
|
|
||||||
'deployment_uuid' => $deployment_uuid->toString(),
|
|
||||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
|
||||||
],
|
|
||||||
200
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_stop(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
$sync = $request->query->get('sync') ?? false;
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::where('uuid', $uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
if ($sync) {
|
|
||||||
StopApplication::run($application);
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Stopped the application.'], 200);
|
|
||||||
} else {
|
|
||||||
StopApplication::dispatch($application);
|
|
||||||
|
|
||||||
return response()->json(['message' => 'Stopping request queued.'], 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function action_restart(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$application = Application::where('uuid', $uuid)->first();
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json(['error' => 'Application not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $application,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
restart_only: true,
|
|
||||||
is_api: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
return response()->json(
|
|
||||||
[
|
|
||||||
'message' => 'Restart request queued.',
|
|
||||||
'deployment_uuid' => $deployment_uuid->toString(),
|
|
||||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
|
||||||
],
|
|
||||||
200
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2570
app/Http/Controllers/Api/ApplicationsController.php
Normal file
2570
app/Http/Controllers/Api/ApplicationsController.php
Normal file
File diff suppressed because it is too large
Load Diff
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,234 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Actions\Database\StartClickhouse;
|
|
||||||
use App\Actions\Database\StartDragonfly;
|
|
||||||
use App\Actions\Database\StartKeydb;
|
|
||||||
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\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Models\Tag;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Deploy extends Controller
|
|
||||||
{
|
|
||||||
public function deployments(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$servers = Server::whereTeamId($teamId)->get();
|
|
||||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
|
|
||||||
'id',
|
|
||||||
'application_id',
|
|
||||||
'application_name',
|
|
||||||
'deployment_url',
|
|
||||||
'pull_request_id',
|
|
||||||
'server_name',
|
|
||||||
'server_id',
|
|
||||||
'status',
|
|
||||||
])->sortBy('id')->toArray();
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($deployments_per_server), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deployment_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->route('uuid');
|
|
||||||
if (! $uuid) {
|
|
||||||
return response()->json(['error' => 'UUID is required.'], 400);
|
|
||||||
}
|
|
||||||
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs');
|
|
||||||
if (! $deployment) {
|
|
||||||
return response()->json(['error' => 'Deployment not found.'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(serialize_api_response($deployment), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
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-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
if ($tags) {
|
|
||||||
return $this->by_tags($tags, $teamId, $force);
|
|
||||||
} elseif ($uuids) {
|
|
||||||
return $this->by_uuids($uuids, $teamId, $force);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/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-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = collect();
|
|
||||||
foreach ($uuids as $uuid) {
|
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
|
||||||
if ($resource) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
} else {
|
|
||||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('deployments', $deployments->toArray());
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/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-reference/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$message = collect([]);
|
|
||||||
$deployments = collect();
|
|
||||||
$payload = 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) {
|
|
||||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
|
||||||
if ($deployment_uuid) {
|
|
||||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
|
||||||
}
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
foreach ($services as $resource) {
|
|
||||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
|
||||||
$message = $message->merge($return_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ray($message);
|
|
||||||
if ($message->count() > 0) {
|
|
||||||
$payload->put('message', $message->toArray());
|
|
||||||
if ($deployments->count() > 0) {
|
|
||||||
$payload->put('details', $deployments->toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($payload->toArray(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deploy_resource($resource, bool $force = false): array
|
|
||||||
{
|
|
||||||
$message = null;
|
|
||||||
$deployment_uuid = null;
|
|
||||||
if (gettype($resource) !== 'object') {
|
|
||||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
$type = $resource?->getMorphClass();
|
|
||||||
if ($type === 'App\Models\Application') {
|
|
||||||
$deployment_uuid = new Cuid2(7);
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $resource,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
|
||||||
force_rebuild: $force,
|
|
||||||
);
|
|
||||||
$message = "Application {$resource->name} deployment queued.";
|
|
||||||
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
|
||||||
StartPostgresql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneRedis') {
|
|
||||||
StartRedis::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
|
||||||
StartKeydb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
|
||||||
StartDragonfly::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
|
||||||
StartClickhouse::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
|
||||||
StartMongodb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMysql') {
|
|
||||||
StartMysql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
|
||||||
StartMariadb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message = "Database {$resource->name} started.";
|
|
||||||
} elseif ($type === 'App\Models\Service') {
|
|
||||||
StartService::run($resource);
|
|
||||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
317
app/Http/Controllers/Api/DeployController.php
Normal file
317
app/Http/Controllers/Api/DeployController.php
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabase;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class DeployController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($deployment)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment->makeHidden([
|
||||||
|
'logs',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($deployment);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List currently running deployments',
|
||||||
|
path: '/deployments',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all currently running deployments.',
|
||||||
|
content: [
|
||||||
|
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployments(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = Server::whereTeamId($teamId)->get();
|
||||||
|
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
|
||||||
|
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
|
||||||
|
return $this->removeSensitiveData($deployment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($deployments_per_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
path: '/deployments/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment by UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/ApplicationDeploymentQueue',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deployment_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
||||||
|
if (! $deployment) {
|
||||||
|
return response()->json(['message' => 'Deployment not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($deployment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Deploy',
|
||||||
|
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||||
|
path: '/deploy',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Deployments'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||||
|
],
|
||||||
|
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get deployment(s) Uuid\'s',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'deployments' => new OA\Property(
|
||||||
|
property: 'deployments',
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string'],
|
||||||
|
'resource_uuid' => ['type' => 'string'],
|
||||||
|
'deployment_uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} elseif ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You must provide uuid or tag.'], 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(['message' => 'No UUIDs provided.'], 400);
|
||||||
|
}
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = collect();
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
} else {
|
||||||
|
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('deployments', $deployments->toArray());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found.'], 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(['message' => 'No TAGs provided.'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
$deployments = collect();
|
||||||
|
$payload = 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) {
|
||||||
|
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||||
|
if ($deployment_uuid) {
|
||||||
|
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||||
|
}
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
$payload->put('message', $message->toArray());
|
||||||
|
if ($deployments->count() > 0) {
|
||||||
|
$payload->put('details', $deployments->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($payload->toArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deploy_resource($resource, bool $force = false): array
|
||||||
|
{
|
||||||
|
$message = null;
|
||||||
|
$deployment_uuid = null;
|
||||||
|
if (gettype($resource) !== 'object') {
|
||||||
|
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
switch ($resource?->getMorphClass()) {
|
||||||
|
case 'App\Models\Application':
|
||||||
|
$deployment_uuid = new Cuid2;
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message = "Application {$resource->name} deployment queued.";
|
||||||
|
break;
|
||||||
|
case 'App\Models\Service':
|
||||||
|
StartService::run($resource);
|
||||||
|
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Database resource
|
||||||
|
StartDatabase::dispatch($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message = "Database {$resource->name} started.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Application;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
|
|
||||||
class Domains extends Controller
|
|
||||||
{
|
|
||||||
public function deleteDomains(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'uuid' => 'required|string|exists:applications,uuid',
|
|
||||||
'domains' => 'required|array',
|
|
||||||
'domains.*' => 'required|string|distinct',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Validation failed',
|
|
||||||
'errors' => $validator->errors(),
|
|
||||||
], 422);
|
|
||||||
}
|
|
||||||
|
|
||||||
$application = Application::where('uuid', $request->uuid)->first();
|
|
||||||
|
|
||||||
if (! $application) {
|
|
||||||
return response()->json([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Application not found',
|
|
||||||
], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$existingDomains = explode(',', $application->fqdn);
|
|
||||||
$domainsToDelete = $request->domains;
|
|
||||||
$updatedDomains = array_diff($existingDomains, $domainsToDelete);
|
|
||||||
$application->fqdn = implode(',', $updatedDomains);
|
|
||||||
$application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
|
||||||
$application->save();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Domains updated successfully',
|
|
||||||
'application' => $application,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EnvironmentVariablesController extends Controller
|
||||||
|
{
|
||||||
|
public function delete_env_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
||||||
|
if (! $env) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
||||||
|
if (! $found_app) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$env->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Environment variable deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
51
app/Http/Controllers/Api/OpenApi.php
Normal file
51
app/Http/Controllers/Api/OpenApi.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Info(title: 'Coolify', version: '0.1')]
|
||||||
|
#[OA\Server(url: 'https://app.coolify.io/api/v1', description: 'Coolify Cloud API. Change the host to your own instance if you are self-hosting.')]
|
||||||
|
#[OA\SecurityScheme(
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
securityScheme: 'bearerAuth',
|
||||||
|
description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.')]
|
||||||
|
#[OA\Components(
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
description: 'Invalid token.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Invalid token.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
description: 'Unauthenticated.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Unauthenticated.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Resource not found.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)]
|
||||||
|
class OpenApi
|
||||||
|
{
|
||||||
|
// This class is used to generate OpenAPI documentation
|
||||||
|
// for the Coolify API. It is not a controller and does
|
||||||
|
// not contain any routes. It is used to define the
|
||||||
|
// OpenAPI metadata and security scheme for the API.
|
||||||
|
}
|
||||||
183
app/Http/Controllers/Api/OtherController.php
Normal file
183
app/Http/Controllers/Api/OtherController.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class OtherController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Version',
|
||||||
|
description: 'Get Coolify version.',
|
||||||
|
path: '/version',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Returns the version of the application',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'v4.0.0',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function version(Request $request)
|
||||||
|
{
|
||||||
|
return response(config('version'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Enable API',
|
||||||
|
description: 'Enable API (only with root permissions).',
|
||||||
|
path: '/enable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Enable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to enable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function enable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => true]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API enabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Disable API',
|
||||||
|
description: 'Disable API (only with root permissions).',
|
||||||
|
path: '/disable',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Disable API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 403,
|
||||||
|
description: 'You are not allowed to disable the API.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
|
||||||
|
]
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function disable_api(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if ($teamId !== '0') {
|
||||||
|
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||||
|
}
|
||||||
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
|
$settings->update(['is_api_enabled' => false]);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'API disabled.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feedback(Request $request)
|
||||||
|
{
|
||||||
|
$content = $request->input('content');
|
||||||
|
$webhook_url = config('coolify.feedback_discord_webhook');
|
||||||
|
if ($webhook_url) {
|
||||||
|
Http::post($webhook_url, [
|
||||||
|
'content' => $content,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Feedback sent.'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Healthcheck',
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
path: '/healthcheck',
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Healthcheck endpoint.',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'OK',
|
||||||
|
)),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function healthcheck(Request $request)
|
||||||
|
{
|
||||||
|
return 'OK';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<?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 invalid_token();
|
|
||||||
}
|
|
||||||
$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 invalid_token();
|
|
||||||
}
|
|
||||||
$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 invalid_token();
|
|
||||||
}
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
425
app/Http/Controllers/Api/ProjectController.php
Normal file
425
app/Http/Controllers/Api/ProjectController.php
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Project;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ProjectController extends Controller
|
||||||
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'list projects.',
|
||||||
|
path: '/projects',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all projects.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Project')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function projects(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($projects),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get project by Uuid.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Project')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Project not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function project_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($project),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Environment',
|
||||||
|
description: 'Get environment by name.',
|
||||||
|
path: '/projects/{uuid}/{environment_name}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project details',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function environment_details(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
if (! $request->environment_name) {
|
||||||
|
return response()->json(['message' => 'Environment name is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
$environment = $project->environments()->whereName($request->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($environment));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Project.',
|
||||||
|
path: '/projects',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the project.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|required',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Project.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os'],
|
||||||
|
'name' => ['type' => 'string', 'example' => 'Project Name'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'Project Description'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$uuid = $request->uuid;
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->update($request->only($allowedFields));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
'name' => $project->name,
|
||||||
|
'description' => $project->description,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete project by UUID.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Project deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_project(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($project->resource_count() > 0) {
|
||||||
|
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Project deleted.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,42 @@ namespace App\Http\Controllers\Api;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
class Resources extends Controller
|
class ResourcesController extends Controller
|
||||||
{
|
{
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all resources.',
|
||||||
|
path: '/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Resources'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all resources',
|
||||||
|
content: new OA\JsonContent(
|
||||||
|
type: 'string',
|
||||||
|
example: 'Content is very complex. Will be implemented later.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
public function resources(Request $request)
|
public function resources(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = get_team_id_from_token();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalid_token();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
$resources = collect();
|
$resources = collect();
|
||||||
@@ -34,6 +62,6 @@ class Resources extends Controller
|
|||||||
return $payload;
|
return $payload;
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json($resources);
|
return response()->json(serializeApiResponse($resources));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
372
app/Http/Controllers/Api/SecurityController.php
Normal file
372
app/Http/Controllers/Api/SecurityController.php
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class SecurityController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'private_key',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all private keys.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function keys(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$keys = PrivateKey::where('team_id', $teamId)->get();
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get key by UUID.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all private keys.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function key_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a new private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The created private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->description) {
|
||||||
|
$request->offsetSet('description', 'Created by Coolify via API');
|
||||||
|
}
|
||||||
|
$key = PrivateKey::create([
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'private_key' => $request->private_key,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $key->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update a private key.',
|
||||||
|
path: '/security/keys',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: [
|
||||||
|
'application/json' => new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['private_key'],
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'description' => ['type' => 'string'],
|
||||||
|
'private_key' => ['type' => 'string'],
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'The updated private key\'s UUID.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_key(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'private_key'];
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|max:255',
|
||||||
|
'private_key' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($foundKey)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
$foundKey->update($request->all());
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse([
|
||||||
|
'uuid' => $foundKey->uuid,
|
||||||
|
]))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete a private key.',
|
||||||
|
path: '/security/keys/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Private Keys'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Private Key deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Private Key deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
description: 'Private Key not found.',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_key(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
if (is_null($key)) {
|
||||||
|
return response()->json(['message' => 'Private Key not found.'], 404);
|
||||||
|
}
|
||||||
|
$key->forceDelete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Private Key deleted.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Project;
|
|
||||||
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 invalid_token();
|
|
||||||
}
|
|
||||||
$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;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response()->json($servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function server_by_uuid(Request $request)
|
|
||||||
{
|
|
||||||
$with_resources = $request->query('resources');
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
|
||||||
if (is_null($server)) {
|
|
||||||
return response()->json(['error' => 'Server not found.'], 404);
|
|
||||||
}
|
|
||||||
if ($with_resources) {
|
|
||||||
$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;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$server->load(['settings']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get_domains_by_server(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$uuid = $request->query->get('uuid');
|
|
||||||
if ($uuid) {
|
|
||||||
$domains = Application::getDomainsByUuid($uuid);
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'uuid' => $uuid,
|
|
||||||
'domains' => $domains,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$projects = Project::where('team_id', $teamId)->get();
|
|
||||||
$domains = collect();
|
|
||||||
$applications = $projects->pluck('applications')->flatten();
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if ($applications->count() > 0) {
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
$ip = $application->destination->server->ip;
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$services = $projects->pluck('services')->flatten();
|
|
||||||
if ($services->count() > 0) {
|
|
||||||
foreach ($services as $service) {
|
|
||||||
$service_applications = $service->applications;
|
|
||||||
if ($service_applications->count() > 0) {
|
|
||||||
foreach ($service_applications as $application) {
|
|
||||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
|
||||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
|
||||||
});
|
|
||||||
if ($ip === 'host.docker.internal') {
|
|
||||||
if ($settings->public_ipv4) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $settings->public_ipv6,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$domains->push([
|
|
||||||
'domain' => $fqdn,
|
|
||||||
'ip' => $ip,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
|
||||||
return $domain->pluck('domain')->flatten();
|
|
||||||
})->map(function ($domain, $ip) {
|
|
||||||
return [
|
|
||||||
'ip' => $ip,
|
|
||||||
'domains' => $domain,
|
|
||||||
];
|
|
||||||
})->values();
|
|
||||||
|
|
||||||
return response()->json($domains);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
785
app/Http/Controllers/Api/ServersController.php
Normal file
785
app/Http/Controllers/Api/ServersController.php
Normal file
@@ -0,0 +1,785 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Server\ValidateServer;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\PrivateKey;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server as ModelsServer;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
class ServersController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveDataFromSettings($settings)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
$settings = $settings->makeHidden([
|
||||||
|
'metrics_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeSensitiveData($server)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$server->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serializeApiResponse($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all servers.',
|
||||||
|
path: '/servers',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all servers.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function servers(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port', 'description')->get()->load(['settings'])->map(function ($server) {
|
||||||
|
$server['is_reachable'] = $server->settings->is_reachable;
|
||||||
|
$server['is_usable'] = $server->settings->is_usable;
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
$servers = $servers->map(function ($server) {
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return $server;
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json($servers);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get server by UUID',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Server'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function server_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$with_resources = $request->query('resources');
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($with_resources) {
|
||||||
|
$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;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$server->load(['settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
data_set($server, 'settings', $settings);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Resources',
|
||||||
|
description: 'Get resources by server.',
|
||||||
|
path: '/servers/{uuid}/resources',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get resources by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'uuid' => ['type' => 'string'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'type' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string'],
|
||||||
|
'updated_at' => ['type' => 'string'],
|
||||||
|
'status' => ['type' => 'string'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function resources_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||||
|
if (is_null($server)) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$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;
|
||||||
|
});
|
||||||
|
$server = $this->removeSensitiveData($server);
|
||||||
|
ray($server);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse(data_get($server, 'resources')));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Domains',
|
||||||
|
description: 'Get domains by server.',
|
||||||
|
path: '/servers/{uuid}/domains',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get domains by server',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'ip' => ['type' => 'string'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function domains_by_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->get('uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$domains = Application::getDomainsByUuid($uuid);
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$domains = collect();
|
||||||
|
$applications = $projects->pluck('applications')->flatten();
|
||||||
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
|
if ($applications->count() > 0) {
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$ip = $application->destination->server->ip;
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$services = $projects->pluck('services')->flatten();
|
||||||
|
if ($services->count() > 0) {
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service_applications = $service->applications;
|
||||||
|
if ($service_applications->count() > 0) {
|
||||||
|
foreach ($service_applications as $application) {
|
||||||
|
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||||
|
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||||
|
|
||||||
|
return str(str($f[0])->explode(':')[0]);
|
||||||
|
})->filter(function (Stringable $fqdn) {
|
||||||
|
return $fqdn->isNotEmpty();
|
||||||
|
});
|
||||||
|
if ($ip === 'host.docker.internal') {
|
||||||
|
if ($settings->public_ipv4) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $settings->public_ipv6,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$domains->push([
|
||||||
|
'domain' => $fqdn,
|
||||||
|
'ip' => $ip,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||||
|
return $domain->pluck('domain')->flatten();
|
||||||
|
})->map(function ($domain, $ip) {
|
||||||
|
return [
|
||||||
|
'ip' => $ip,
|
||||||
|
'domains' => $domain,
|
||||||
|
];
|
||||||
|
})->values();
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($domains));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Server.',
|
||||||
|
path: '/servers',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'example' => 'My Server', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'My Server Description', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'example' => '127.0.0.1', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'example' => 22, 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'example' => 'root', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the server.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|required',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|required',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->user) {
|
||||||
|
$request->offsetSet('user', 'root');
|
||||||
|
}
|
||||||
|
if (is_null($request->port)) {
|
||||||
|
$request->offsetSet('port', 22);
|
||||||
|
}
|
||||||
|
if (is_null($request->is_build_server)) {
|
||||||
|
$request->offsetSet('is_build_server', false);
|
||||||
|
}
|
||||||
|
if (is_null($request->instant_validate)) {
|
||||||
|
$request->offsetSet('instant_validate', false);
|
||||||
|
}
|
||||||
|
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||||
|
if (! $privateKey) {
|
||||||
|
return response()->json(['message' => 'Private key not found.'], 404);
|
||||||
|
}
|
||||||
|
$allServers = ModelsServer::whereIp($request->ip)->get();
|
||||||
|
if ($allServers->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = ModelsServer::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'ip' => $request->ip,
|
||||||
|
'port' => $request->port,
|
||||||
|
'user' => $request->user,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::TRAEFIK->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $server->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Server.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|nullable',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|nullable',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||||
|
if ($request->is_build_server) {
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the server.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Server deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Server deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($server->definedResources()->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
|
}
|
||||||
|
$server->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Validate',
|
||||||
|
description: 'Validate server by UUID.',
|
||||||
|
path: '/servers/{uuid}/validate',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server validation started.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Validation started.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function validate_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Validation started.']);
|
||||||
|
}
|
||||||
|
}
|
||||||
704
app/Http/Controllers/Api/ServicesController.php
Normal file
704
app/Http/Controllers/Api/ServicesController.php
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Service\RestartService;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\DeleteResourceJob;
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\Service;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class ServicesController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($service)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$service->makeHidden([
|
||||||
|
'id',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service->makeHidden([
|
||||||
|
'docker_compose_raw',
|
||||||
|
'docker_compose',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'List all services.',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get all services',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Service')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function services(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
|
$services = collect();
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
$services->push($project->services()->get());
|
||||||
|
}
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$service = $this->removeSensitiveData($service);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($services->flatten());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create a one-click service',
|
||||||
|
path: '/services',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
|
||||||
|
properties: [
|
||||||
|
'type' => [
|
||||||
|
'description' => 'The one-click service type',
|
||||||
|
'type' => 'string',
|
||||||
|
'enum' => [
|
||||||
|
'activepieces',
|
||||||
|
'appsmith',
|
||||||
|
'appwrite',
|
||||||
|
'authentik',
|
||||||
|
'babybuddy',
|
||||||
|
'budge',
|
||||||
|
'changedetection',
|
||||||
|
'chatwoot',
|
||||||
|
'classicpress-with-mariadb',
|
||||||
|
'classicpress-with-mysql',
|
||||||
|
'classicpress-without-database',
|
||||||
|
'cloudflared',
|
||||||
|
'code-server',
|
||||||
|
'dashboard',
|
||||||
|
'directus',
|
||||||
|
'directus-with-postgresql',
|
||||||
|
'docker-registry',
|
||||||
|
'docuseal',
|
||||||
|
'docuseal-with-postgres',
|
||||||
|
'dokuwiki',
|
||||||
|
'duplicati',
|
||||||
|
'emby',
|
||||||
|
'embystat',
|
||||||
|
'fider',
|
||||||
|
'filebrowser',
|
||||||
|
'firefly',
|
||||||
|
'formbricks',
|
||||||
|
'ghost',
|
||||||
|
'gitea',
|
||||||
|
'gitea-with-mariadb',
|
||||||
|
'gitea-with-mysql',
|
||||||
|
'gitea-with-postgresql',
|
||||||
|
'glance',
|
||||||
|
'glances',
|
||||||
|
'glitchtip',
|
||||||
|
'grafana',
|
||||||
|
'grafana-with-postgresql',
|
||||||
|
'grocy',
|
||||||
|
'heimdall',
|
||||||
|
'homepage',
|
||||||
|
'jellyfin',
|
||||||
|
'kuzzle',
|
||||||
|
'listmonk',
|
||||||
|
'logto',
|
||||||
|
'mediawiki',
|
||||||
|
'meilisearch',
|
||||||
|
'metabase',
|
||||||
|
'metube',
|
||||||
|
'minio',
|
||||||
|
'moodle',
|
||||||
|
'n8n',
|
||||||
|
'n8n-with-postgresql',
|
||||||
|
'next-image-transformation',
|
||||||
|
'nextcloud',
|
||||||
|
'nocodb',
|
||||||
|
'odoo',
|
||||||
|
'openblocks',
|
||||||
|
'pairdrop',
|
||||||
|
'penpot',
|
||||||
|
'phpmyadmin',
|
||||||
|
'pocketbase',
|
||||||
|
'posthog',
|
||||||
|
'reactive-resume',
|
||||||
|
'rocketchat',
|
||||||
|
'shlink',
|
||||||
|
'slash',
|
||||||
|
'snapdrop',
|
||||||
|
'statusnook',
|
||||||
|
'stirling-pdf',
|
||||||
|
'supabase',
|
||||||
|
'syncthing',
|
||||||
|
'tolgee',
|
||||||
|
'trigger',
|
||||||
|
'trigger-with-external-database',
|
||||||
|
'twenty',
|
||||||
|
'umami',
|
||||||
|
'unleash-with-postgresql',
|
||||||
|
'unleash-without-database',
|
||||||
|
'uptime-kuma',
|
||||||
|
'vaultwarden',
|
||||||
|
'vikunja',
|
||||||
|
'weblate',
|
||||||
|
'whoogle',
|
||||||
|
'wordpress-with-mariadb',
|
||||||
|
'wordpress-with-mysql',
|
||||||
|
'wordpress-without-database',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
||||||
|
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
||||||
|
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
||||||
|
'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
|
||||||
|
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
||||||
|
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
||||||
|
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Create a service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'Service UUID.'],
|
||||||
|
'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_service(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'type' => 'string|required',
|
||||||
|
'project_uuid' => 'string|required',
|
||||||
|
'environment_name' => 'string|required',
|
||||||
|
'server_uuid' => 'string|required',
|
||||||
|
'destination_uuid' => 'string',
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'instant_deploy' => 'boolean',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$serverUuid = $request->server_uuid;
|
||||||
|
$instantDeploy = $request->instant_deploy ?? false;
|
||||||
|
if ($request->is_public && ! $request->public_port) {
|
||||||
|
$request->offsetSet('is_public', false);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
||||||
|
if (! $environment) {
|
||||||
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
|
}
|
||||||
|
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$destinations = $server->destinations();
|
||||||
|
if ($destinations->count() == 0) {
|
||||||
|
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||||
|
}
|
||||||
|
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
|
||||||
|
return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
|
||||||
|
}
|
||||||
|
$destination = $destinations->first();
|
||||||
|
$services = get_service_templates();
|
||||||
|
$serviceKeys = $services->keys();
|
||||||
|
if ($serviceKeys->contains($request->type)) {
|
||||||
|
$oneClickServiceName = $request->type;
|
||||||
|
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||||
|
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||||
|
if ($oneClickDotEnvs) {
|
||||||
|
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
|
||||||
|
return ! empty($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($oneClickService) {
|
||||||
|
$service_payload = [
|
||||||
|
'name' => "$oneClickServiceName-".str()->random(10),
|
||||||
|
'docker_compose_raw' => base64_decode($oneClickService),
|
||||||
|
'environment_id' => $environment->id,
|
||||||
|
'service_type' => $oneClickServiceName,
|
||||||
|
'server_id' => $server->id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination->getMorphClass(),
|
||||||
|
];
|
||||||
|
if ($oneClickServiceName === 'cloudflared') {
|
||||||
|
data_set($service_payload, 'connect_to_docker_network', true);
|
||||||
|
}
|
||||||
|
$service = Service::create($service_payload);
|
||||||
|
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||||
|
$service->save();
|
||||||
|
if ($oneClickDotEnvs?->count() > 0) {
|
||||||
|
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||||
|
$key = str()->before($value, '=');
|
||||||
|
$value = str(str()->after($value, '='));
|
||||||
|
$generatedValue = $value;
|
||||||
|
if ($value->contains('SERVICE_')) {
|
||||||
|
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||||
|
$generatedValue = generateEnvValue($command->value(), $service);
|
||||||
|
}
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => $key,
|
||||||
|
'value' => $generatedValue,
|
||||||
|
'service_id' => $service->id,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$service->parse(isNew: true);
|
||||||
|
if ($instantDeploy) {
|
||||||
|
StartService::dispatch($service);
|
||||||
|
}
|
||||||
|
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||||
|
$domains = $domains->map(function ($domain) {
|
||||||
|
return str($domain)->beforeLast(':')->value();
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $service->uuid,
|
||||||
|
'domains' => $domains,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Invalid service type.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Get a service by Uuid.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
ref: '#/components/schemas/Service'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function service_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = $service->load(['applications', 'databases']);
|
||||||
|
|
||||||
|
return response()->json($this->removeSensitiveData($service));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete service by UUID.',
|
||||||
|
path: '/services/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Delete a service by Uuid',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service deletion request queued.'],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_by_uuid(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
DeleteResourceJob::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Service deletion request queued.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Start',
|
||||||
|
description: 'Start service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/start',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Start service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_deploy(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('running')) {
|
||||||
|
return response()->json(['message' => 'Service is already running.'], 400);
|
||||||
|
}
|
||||||
|
StartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service starting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Stop',
|
||||||
|
description: 'Stop service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/stop',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Stop service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_stop(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||||
|
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||||
|
}
|
||||||
|
StopService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service stopping request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Restart',
|
||||||
|
description: 'Restart service. `Post` request is also accepted.',
|
||||||
|
path: '/services/{uuid}/restart',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Services'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the service.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Restart service.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
||||||
|
])
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function action_restart(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$uuid = $request->route('uuid');
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'UUID is required.'], 400);
|
||||||
|
}
|
||||||
|
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
|
}
|
||||||
|
RestartService::dispatch($service);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
[
|
||||||
|
'message' => 'Service restarting request queued.',
|
||||||
|
],
|
||||||
|
200
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class Team extends Controller
|
|
||||||
{
|
|
||||||
public function teams(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
|
|
||||||
return response()->json($teams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function team_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function members_by_id(Request $request)
|
|
||||||
{
|
|
||||||
$id = $request->id;
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$teams = auth()->user()->teams;
|
|
||||||
$team = $teams->where('id', $id)->first();
|
|
||||||
if (is_null($team)) {
|
|
||||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function current_team_members(Request $request)
|
|
||||||
{
|
|
||||||
$teamId = get_team_id_from_token();
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return invalid_token();
|
|
||||||
}
|
|
||||||
$team = auth()->user()->currentTeam();
|
|
||||||
|
|
||||||
return response()->json($team->members);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
270
app/Http/Controllers/Api/TeamController.php
Normal file
270
app/Http/Controllers/Api/TeamController.php
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
class TeamController extends Controller
|
||||||
|
{
|
||||||
|
private function removeSensitiveData($team)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$team->makeHidden([
|
||||||
|
'custom_server_limit',
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
if ($token->can('view:sensitive')) {
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
$team->makeHidden([
|
||||||
|
'smtp_username',
|
||||||
|
'smtp_password',
|
||||||
|
'resend_api_key',
|
||||||
|
'telegram_token',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return serializeApiResponse($team);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'List',
|
||||||
|
description: 'Get all teams.',
|
||||||
|
path: '/teams',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Team')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function teams(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams->sortBy('id');
|
||||||
|
$teams = $teams->map(function ($team) {
|
||||||
|
return $this->removeSensitiveData($team);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$teams,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Get',
|
||||||
|
description: 'Get team by TeamId.',
|
||||||
|
path: '/teams/{id}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of teams.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function team_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$team = $this->removeSensitiveData($team);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Members',
|
||||||
|
description: 'Get members by TeamId.',
|
||||||
|
path: '/teams/{id}/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'List of members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function members_by_id(Request $request)
|
||||||
|
{
|
||||||
|
$id = $request->id;
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$teams = auth()->user()->teams;
|
||||||
|
$team = $teams->where('id', $id)->first();
|
||||||
|
if (is_null($team)) {
|
||||||
|
return response()->json(['message' => 'Team not found.'], 404);
|
||||||
|
}
|
||||||
|
$members = $team->members;
|
||||||
|
$members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team',
|
||||||
|
description: 'Get currently authenticated team.',
|
||||||
|
path: '/teams/current',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Current Team.',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/Team')),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
$this->removeSensitiveData($team),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Authenticated Team Members',
|
||||||
|
description: 'Get currently authenticated team members.',
|
||||||
|
path: '/teams/current/members',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Teams'],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Currently authenticated team members.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/User')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function current_team_members(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
$team = auth()->user()->currentTeam();
|
||||||
|
$team->members->makeHidden([
|
||||||
|
'pivot',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json(
|
||||||
|
serializeApiResponse($team->members),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class UploadController extends BaseController
|
|||||||
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
|
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
|
||||||
|
|
||||||
if ($receiver->isUploaded() === false) {
|
if ($receiver->isUploaded() === false) {
|
||||||
throw new UploadMissingFileException();
|
throw new UploadMissingFileException;
|
||||||
}
|
}
|
||||||
|
|
||||||
$save = $receiver->receive();
|
$save = $receiver->receive();
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class Bitbucket extends Controller
|
|||||||
if ($x_bitbucket_event === 'repo:push') {
|
if ($x_bitbucket_event === 'repo:push') {
|
||||||
if ($application->isDeployable()) {
|
if ($application->isDeployable()) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -127,7 +127,7 @@ class Bitbucket extends Controller
|
|||||||
if ($x_bitbucket_event === 'pullrequest:created') {
|
if ($x_bitbucket_event === 'pullrequest:created') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
|
ray('Deploying preview for '.$application->name.' with branch '.$branch.' and base branch '.$base_branch.' and pull request id '.$pull_request_id);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class Gitea extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -162,7 +162,7 @@ class Gitea extends Controller
|
|||||||
if ($x_gitea_event === 'pull_request') {
|
if ($x_gitea_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class Github extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -167,7 +167,7 @@ class Github extends Controller
|
|||||||
if ($x_github_event === 'pull_request') {
|
if ($x_github_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
@@ -340,7 +340,6 @@ class Github extends Controller
|
|||||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$isFunctional = $application->destination->server->isFunctional();
|
$isFunctional = $application->destination->server->isFunctional();
|
||||||
if (! $isFunctional) {
|
if (! $isFunctional) {
|
||||||
@@ -358,7 +357,7 @@ class Github extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -397,7 +396,7 @@ class Github extends Controller
|
|||||||
if ($x_github_event === 'pull_request') {
|
if ($x_github_event === 'pull_request') {
|
||||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
ApplicationPreview::create([
|
ApplicationPreview::create([
|
||||||
@@ -432,8 +431,13 @@ class Github extends Controller
|
|||||||
if ($action === 'closed' || $action === 'close') {
|
if ($action === 'closed' || $action === 'close') {
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id, $pull_request_id);
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
if ($containers->isNotEmpty()) {
|
||||||
|
$containers->each(function ($container) use ($application) {
|
||||||
|
$container_name = data_get($container, 'Names');
|
||||||
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||||
$found->delete();
|
$found->delete();
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class Gitlab extends Controller
|
|||||||
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
|
||||||
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
if ($is_watch_path_triggered || is_null($application->watch_paths)) {
|
||||||
ray('Deploying '.$application->name.' with branch '.$branch);
|
ray('Deploying '.$application->name.' with branch '.$branch);
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
@@ -177,7 +177,7 @@ class Gitlab extends Controller
|
|||||||
if ($x_gitlab_event === 'merge_request') {
|
if ($x_gitlab_event === 'merge_request') {
|
||||||
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
||||||
if ($application->isPRDeployable()) {
|
if ($application->isPRDeployable()) {
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (! $found) {
|
if (! $found) {
|
||||||
if ($application->build_pack === 'dockercompose') {
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
|||||||
@@ -54,6 +54,34 @@ class Stripe extends Controller
|
|||||||
$type = data_get($event, 'type');
|
$type = data_get($event, 'type');
|
||||||
$data = data_get($event, 'data.object');
|
$data = data_get($event, 'data.object');
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
|
case 'radar.early_fraud_warning.created':
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$id = data_get($data, 'id');
|
||||||
|
$charge = data_get($data, 'charge');
|
||||||
|
if ($charge) {
|
||||||
|
$stripe->refunds->create(['charge' => $charge]);
|
||||||
|
}
|
||||||
|
$pi = data_get($data, 'payment_intent');
|
||||||
|
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||||
|
$customerId = data_get($piData, 'customer');
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if (! $subscription) {
|
||||||
|
Sleep::for(5)->seconds();
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
|
}
|
||||||
|
if ($subscription) {
|
||||||
|
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
||||||
|
$stripe->subscriptions->cancel($subscriptionId, []);
|
||||||
|
$subscription->update([
|
||||||
|
'stripe_invoice_paid' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||||
|
break;
|
||||||
case 'checkout.session.completed':
|
case 'checkout.session.completed':
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
if (is_null($clientReferenceId)) {
|
if (is_null($clientReferenceId)) {
|
||||||
@@ -231,7 +259,7 @@ class Stripe extends Controller
|
|||||||
'stripe_plan_id' => null,
|
'stripe_plan_id' => null,
|
||||||
'stripe_cancel_at_period_end' => false,
|
'stripe_cancel_at_period_end' => false,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
'stripe_trial_already_ended' => true,
|
'stripe_trial_already_ended' => false,
|
||||||
]);
|
]);
|
||||||
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -67,5 +67,7 @@ class Kernel extends HttpKernel
|
|||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||||
|
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
33
app/Http/Middleware/ApiAllowed.php
Normal file
33
app/Http/Middleware/ApiAllowed.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class ApiAllowed
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
ray()->clearAll();
|
||||||
|
if (isCloud()) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
|
if ($settings->is_api_enabled === false) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isDev()) {
|
||||||
|
if ($settings->allowed_ips) {
|
||||||
|
$allowedIps = explode(',', $settings->allowed_ips);
|
||||||
|
if (! in_array($request->ip(), $allowedIps)) {
|
||||||
|
return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class IgnoreReadOnlyApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
if ($token->can('read-only')) {
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class OnlyRootApiToken
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
if ($token->can('*')) {
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private string $dockerfile_location = '/Dockerfile';
|
private string $dockerfile_location = '/Dockerfile';
|
||||||
|
|
||||||
private string $docker_compose_location = '/docker-compose.yml';
|
private string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
private ?string $docker_compose_custom_start_command = null;
|
private ?string $docker_compose_custom_start_command = null;
|
||||||
|
|
||||||
@@ -157,6 +157,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private ?string $coolify_variables = null;
|
private ?string $coolify_variables = null;
|
||||||
|
|
||||||
|
private bool $preserveRepository = true;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
@@ -187,6 +189,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->server = $this->mainServer = $this->destination->server;
|
$this->server = $this->mainServer = $this->destination->server;
|
||||||
$this->serverUser = $this->server->user;
|
$this->serverUser = $this->server->user;
|
||||||
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
||||||
|
$this->preserveRepository = $this->application->settings->is_preserve_repository_enabled;
|
||||||
|
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||||
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
||||||
@@ -194,6 +197,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
|
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$this->container_name = $this->application->settings->custom_internal_name;
|
||||||
|
}
|
||||||
ray('New container name: ', $this->container_name);
|
ray('New container name: ', $this->container_name);
|
||||||
|
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
@@ -301,14 +307,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// $this->execute_remote_command(
|
|
||||||
// [
|
|
||||||
// "docker image prune -f >/dev/null 2>&1",
|
|
||||||
// "hidden" => true,
|
|
||||||
// "ignore_errors" => true,
|
|
||||||
// ]
|
|
||||||
// );
|
|
||||||
|
|
||||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,7 +457,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
|
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull";
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||||
);
|
);
|
||||||
@@ -484,40 +482,41 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
// Start compose file
|
// Start compose file
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$server_workdir = $this->application->workdir();
|
$server_workdir = $this->application->workdir();
|
||||||
|
$this->docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
$command .= " --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
|
||||||
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
['command' => $command, 'hidden' => true],
|
['command' => $command, 'hidden' => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||||
|
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,26 +601,53 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function write_deployment_configurations()
|
private function write_deployment_configurations()
|
||||||
{
|
{
|
||||||
|
if ($this->preserveRepository) {
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->original_server;
|
||||||
|
}
|
||||||
|
if (str($this->configuration_dir)->isNotEmpty()) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
],
|
||||||
|
// removing this now as we are using docker cp
|
||||||
|
// [
|
||||||
|
// "rm -rf $this->configuration_dir/{*,.*}",
|
||||||
|
// ],
|
||||||
|
[
|
||||||
|
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->build_server;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset($this->docker_compose_base64)) {
|
if (isset($this->docker_compose_base64)) {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
|
||||||
|
$mainDir = $this->configuration_dir;
|
||||||
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
|
$mainDir = $this->application->workdir();
|
||||||
|
}
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
$composeFileName = "$mainDir/docker-compose.yaml";
|
||||||
} else {
|
} else {
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
$composeFileName = "$mainDir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
|
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $mainDir",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
|
"echo '{$this->docker_compose_base64}' | base64 -d | tee $composeFileName > /dev/null",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
"echo '{$readme}' > $mainDir/README.md",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
@@ -845,8 +871,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$envs->push($env->key.'='.$real_value);
|
$envs->push($env->key.'='.$real_value);
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
$envs->push("PORT={$ports[0]}");
|
if ($this->application->environment_variables_preview->where('key', 'PORT')->isEmpty()) {
|
||||||
|
$envs->push("PORT={$ports[0]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add HOST if not exists
|
// Add HOST if not exists
|
||||||
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables_preview->where('key', 'HOST')->isEmpty()) {
|
||||||
@@ -889,15 +917,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$envs->push($env->key.'='.$real_value);
|
$envs->push($env->key.'='.$real_value);
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
if ($this->build_pack !== 'dockercompose') {
|
||||||
$envs->push("PORT={$ports[0]}");
|
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||||
|
$envs->push("PORT={$ports[0]}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add HOST if not exists
|
// Add HOST if not exists
|
||||||
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
if ($this->application->environment_variables->where('key', 'HOST')->isEmpty()) {
|
||||||
$envs->push('HOST=0.0.0.0');
|
$envs->push('HOST=0.0.0.0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($envs->isEmpty()) {
|
if ($envs->isEmpty()) {
|
||||||
$this->env_filename = null;
|
$this->env_filename = null;
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
@@ -962,7 +991,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
$nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first();
|
||||||
}
|
}
|
||||||
if (! $nixpacks_php_fallback_path) {
|
if (! $nixpacks_php_fallback_path) {
|
||||||
$nixpacks_php_fallback_path = new EnvironmentVariable();
|
$nixpacks_php_fallback_path = new EnvironmentVariable;
|
||||||
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||||
$nixpacks_php_fallback_path->value = '/index.php';
|
$nixpacks_php_fallback_path->value = '/index.php';
|
||||||
$nixpacks_php_fallback_path->is_build_time = false;
|
$nixpacks_php_fallback_path->is_build_time = false;
|
||||||
@@ -970,7 +999,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$nixpacks_php_fallback_path->save();
|
$nixpacks_php_fallback_path->save();
|
||||||
}
|
}
|
||||||
if (! $nixpacks_php_root_dir) {
|
if (! $nixpacks_php_root_dir) {
|
||||||
$nixpacks_php_root_dir = new EnvironmentVariable();
|
$nixpacks_php_root_dir = new EnvironmentVariable;
|
||||||
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
||||||
$nixpacks_php_root_dir->value = '/app/public';
|
$nixpacks_php_root_dir->value = '/app/public';
|
||||||
$nixpacks_php_root_dir->is_build_time = false;
|
$nixpacks_php_root_dir->is_build_time = false;
|
||||||
@@ -996,7 +1025,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty() || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
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.');
|
$this->application_deployment_queue->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
||||||
@@ -1004,7 +1033,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
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->application_deployment_queue->addLogEntry('Consistent container name feature enabled, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -1244,7 +1273,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// ray('Deploying to additional destination: ', $server->name);
|
// ray('Deploying to additional destination: ', $server->name);
|
||||||
$deployment_uuid = new Cuid2();
|
$deployment_uuid = new Cuid2;
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
@@ -1418,6 +1447,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||||
|
if ($this->nixpacks_type === 'rust') {
|
||||||
|
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
|
||||||
|
$this->application->health_check_enabled = false;
|
||||||
|
$this->application->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1520,7 +1554,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
} else {
|
} else {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
if (! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
@@ -1570,23 +1606,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
|
||||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
|
||||||
}
|
|
||||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if ($this->env_filename) {
|
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
|
|
||||||
// } else {
|
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
||||||
}
|
}
|
||||||
@@ -1638,12 +1657,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
if (data_get($this->application, 'swarm_placement_constraints')) {
|
||||||
|
$swarm_placement_constraints = Yaml::parse(base64_decode(data_get($this->application, 'swarm_placement_constraints')));
|
||||||
|
$docker_compose['services'][$this->container_name]['deploy'] = array_merge(
|
||||||
|
$docker_compose['services'][$this->container_name]['deploy'],
|
||||||
|
$swarm_placement_constraints
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
|
$docker_compose['services'][$this->container_name]['deploy']['placement']['constraints'][] = 'node.role == worker';
|
||||||
'constraints' => [
|
|
||||||
'node.role == worker',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
||||||
@@ -1697,32 +1719,28 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
// if ($this->build_pack === 'dockerfile') {
|
|
||||||
// $docker_compose['services'][$this->container_name]['build'] = [
|
|
||||||
// 'context' => $this->workdir,
|
|
||||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
if (! $this->application->settings->custom_internal_name) {
|
||||||
if (count($custom_compose) > 0) {
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
if (count($custom_compose) > 0) {
|
||||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||||
data_forget($custom_compose, 'ip');
|
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||||
data_forget($custom_compose, 'ip6');
|
data_forget($custom_compose, 'ip');
|
||||||
if ($ipv4 || $ipv6) {
|
data_forget($custom_compose, 'ip6');
|
||||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
if ($ipv4 || $ipv6) {
|
||||||
|
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||||
|
}
|
||||||
|
if ($ipv4) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||||
|
}
|
||||||
|
if ($ipv6) {
|
||||||
|
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
}
|
}
|
||||||
if ($ipv4) {
|
|
||||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
|
||||||
}
|
|
||||||
if ($ipv6) {
|
|
||||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
|
||||||
}
|
|
||||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (count($custom_compose) > 0) {
|
if (count($custom_compose) > 0) {
|
||||||
@@ -1746,7 +1764,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), 'hidden' => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_local_persistent_volumes()
|
private function generate_local_persistent_volumes()
|
||||||
@@ -2009,27 +2027,43 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
$this->application_deployment_queue->addLogEntry('Building docker image completed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $timeout in seconds
|
||||||
|
*/
|
||||||
|
private function graceful_shutdown_container(string $containerName, int $timeout = 30)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true],
|
||||||
|
["docker rm $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
);
|
||||||
|
} catch (\Exception $error) {
|
||||||
|
// report error if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private function stop_running_container(bool $force = false)
|
private function stop_running_container(bool $force = false)
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->addLogEntry('Removing old containers.');
|
$this->application_deployment_queue->addLogEntry('Removing old containers.');
|
||||||
if ($this->newVersionIsHealthy || $force) {
|
if ($this->newVersionIsHealthy || $force) {
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
if ($this->pull_request_id === 0) {
|
$this->graceful_shutdown_container($this->container_name);
|
||||||
$containers = $containers->filter(function ($container) {
|
} else {
|
||||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
|
if ($this->pull_request_id === 0) {
|
||||||
|
$containers = $containers->filter(function ($container) {
|
||||||
|
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name.'-pr-'.$this->pull_request_id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$containers->each(function ($container) {
|
||||||
|
$this->graceful_shutdown_container(data_get($container, 'Names'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$containers->each(function ($container) {
|
|
||||||
$containerName = data_get($container, 'Names');
|
|
||||||
$this->execute_remote_command(
|
|
||||||
["docker rm -f $containerName >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
|
||||||
$this->execute_remote_command(
|
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||||
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
$this->application_deployment_queue->addLogEntry('----------------------------------------');
|
||||||
@@ -2040,45 +2074,26 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||||
]);
|
]);
|
||||||
$this->execute_remote_command(
|
$this->graceful_shutdown_container($this->container_name);
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", 'hidden' => true, 'ignore_errors' => true],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function build_by_compose_file()
|
|
||||||
{
|
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), 'hidden' => true],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->addLogEntry('New images built.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function start_by_compose_file()
|
private function start_by_compose_file()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
if ($this->application->build_pack === 'dockerimage') {
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} pull"), 'hidden' => true],
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2234,7 +2249,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
ray($code);
|
ray($code);
|
||||||
if ($code !== 69420) {
|
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
|
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
if ($this->application->settings->is_consistent_container_name_enabled || str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
// do not remove already running container
|
// do not remove already running container
|
||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
$this->application_deployment_queue->addLogEntry('Deployment failed. Removing the new version of your application.', 'stderr');
|
||||||
|
|||||||
42
app/Jobs/CheckForUpdatesJob.php
Normal file
42
app/Jobs/CheckForUpdatesJob.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (isDev() || isCloud()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
|
if ($response->successful()) {
|
||||||
|
$versions = $response->json();
|
||||||
|
$latest_version = data_get($versions, 'coolify.v4.version');
|
||||||
|
$current_version = config('version');
|
||||||
|
|
||||||
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
|
// New version available
|
||||||
|
$settings->update(['new_version_available' => true]);
|
||||||
|
} else {
|
||||||
|
$settings->update(['new_version_available' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Consider implementing a notification to administrators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,6 +90,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
BackupCreated::dispatch($this->team->id);
|
BackupCreated::dispatch($this->team->id);
|
||||||
|
|
||||||
// Check if team is exists
|
// Check if team is exists
|
||||||
if (is_null($this->team)) {
|
if (is_null($this->team)) {
|
||||||
$this->backup->update(['status' => 'failed']);
|
$this->backup->update(['status' => 'failed']);
|
||||||
@@ -332,8 +333,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ray($this->database->toArray());
|
$url = $this->database->internal_db_url;
|
||||||
$url = $this->database->get_db_url(useInternal: true);
|
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
||||||
@@ -477,7 +477,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$network = $this->database->destination->network;
|
$network = $this->database->destination->network;
|
||||||
}
|
}
|
||||||
$commands[] = "docker run --pull=always -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
|
$commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||||
instant_remote_process($commands, $this->server);
|
instant_remote_process($commands, $this->server);
|
||||||
|
|||||||
@@ -28,15 +28,19 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
|
public function __construct(
|
||||||
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||||
|
public bool $deleteConfigurations = false,
|
||||||
|
public bool $deleteVolumes = false) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resource->forceDelete();
|
$persistentStorages = collect();
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
StopApplication::run($this->resource);
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
|
StopApplication::run($this->resource, previewDeployments: true);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
@@ -46,6 +50,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
case 'standalone-keydb':
|
case 'standalone-keydb':
|
||||||
case 'standalone-dragonfly':
|
case 'standalone-dragonfly':
|
||||||
case 'standalone-clickhouse':
|
case 'standalone-clickhouse':
|
||||||
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
@@ -53,6 +58,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
DeleteService::run($this->resource);
|
DeleteService::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->deleteVolumes && $this->resource->type() !== 'service') {
|
||||||
|
$this->resource?->delete_volumes($persistentStorages);
|
||||||
|
}
|
||||||
if ($this->deleteConfigurations) {
|
if ($this->deleteConfigurations) {
|
||||||
$this->resource?->delete_configurations();
|
$this->resource?->delete_configurations();
|
||||||
}
|
}
|
||||||
@@ -61,6 +70,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
$this->resource->forceDelete();
|
||||||
Artisan::queue('cleanup:stucked-resources');
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -20,47 +19,43 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 300;
|
public $timeout = 300;
|
||||||
|
|
||||||
public ?int $usageBefore = null;
|
public int|string|null $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$isInprogress = false;
|
|
||||||
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
|
||||||
if ($application->isDeploymentInprogress()) {
|
|
||||||
$isInprogress = true;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if ($isInprogress) {
|
|
||||||
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
|
||||||
}
|
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->server->settings->is_force_cleanup_enabled) {
|
||||||
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
|
CleanupDocker::run(server: $this->server, force: true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
ray('Usage before: '.$this->usageBefore);
|
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||||
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
|
CleanupDocker::run(server: $this->server, force: true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||||
ray('Cleaning up '.$this->server->name);
|
CleanupDocker::run(server: $this->server, force: false);
|
||||||
CleanupDocker::run($this->server);
|
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
||||||
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
||||||
} else {
|
} else {
|
||||||
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('No need to clean up '.$this->server->name);
|
|
||||||
Log::info('No need to clean up '.$this->server->name);
|
Log::info('No need to clean up '.$this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
|
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,16 +17,13 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 1000;
|
|
||||||
|
|
||||||
public function __construct() {}
|
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (isDev() || isCloud()) {
|
if (isDev() || isCloud()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
$server = Server::findOrFail(0);
|
$server = Server::findOrFail(0);
|
||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
@@ -36,7 +33,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$latest_version = get_latest_version_of_coolify();
|
$latest_version = get_latest_version_of_coolify();
|
||||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
$current_version = config('version');
|
$current_version = config('version');
|
||||||
if (! $settings->is_auto_update_enabled) {
|
if (! $settings->is_auto_update_enabled) {
|
||||||
return;
|
return;
|
||||||
@@ -47,10 +43,6 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (version_compare($latest_version, $current_version, '<')) {
|
if (version_compare($latest_version, $current_version, '<')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
instant_remote_process([
|
|
||||||
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
|
|
||||||
"bash /data/coolify/source/upgrade.sh $latest_version",
|
|
||||||
], $server);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,16 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! isDev()) {
|
if (isDev() || isCloud()) {
|
||||||
ray('PullTemplatesAndVersions service-templates');
|
return;
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
}
|
||||||
if ($response->successful()) {
|
ray('PullTemplatesAndVersions service-templates');
|
||||||
$services = $response->json();
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
if ($response->successful()) {
|
||||||
} else {
|
$services = $response->json();
|
||||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
}
|
} else {
|
||||||
|
send_internal_notification('PullTemplatesAndVersions failed with: '.$response->status().' '.$response->body());
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());
|
send_internal_notification('PullTemplatesAndVersions failed with: '.$e->getMessage());
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
|
$confirmation_url = base_url().'/webhooks/waitlist/confirm?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||||
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
|
$cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid;
|
||||||
$mail->view('emails.waitlist-confirmation',
|
$mail->view('emails.waitlist-confirmation',
|
||||||
|
|||||||
440
app/Jobs/ServerCheckJob.php
Normal file
440
app/Jobs/ServerCheckJob.php
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
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\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $tries = 3;
|
||||||
|
|
||||||
|
public $containers;
|
||||||
|
|
||||||
|
public $applications;
|
||||||
|
|
||||||
|
public $databases;
|
||||||
|
|
||||||
|
public $services;
|
||||||
|
|
||||||
|
public $previews;
|
||||||
|
|
||||||
|
public function backoff(): int
|
||||||
|
{
|
||||||
|
return isDev() ? 1 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): int
|
||||||
|
{
|
||||||
|
return $this->server->uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->applications = $this->server->applications();
|
||||||
|
$this->databases = $this->server->databases();
|
||||||
|
$this->services = $this->server->services()->get();
|
||||||
|
$this->previews = $this->server->previews();
|
||||||
|
|
||||||
|
$up = $this->serverStatus();
|
||||||
|
if (! $up) {
|
||||||
|
ray('Server is not reachable.');
|
||||||
|
|
||||||
|
return 'Server is not reachable.';
|
||||||
|
}
|
||||||
|
if (! $this->server->isFunctional()) {
|
||||||
|
ray('Server is not ready.');
|
||||||
|
|
||||||
|
return 'Server is not ready.';
|
||||||
|
}
|
||||||
|
if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
|
||||||
|
['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
|
||||||
|
if (is_null($this->containers)) {
|
||||||
|
return 'No containers found.';
|
||||||
|
}
|
||||||
|
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
$this->checkSentinel();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkSentinel()
|
||||||
|
{
|
||||||
|
if ($this->server->isSentinelEnabled()) {
|
||||||
|
$sentinelContainerFound = $this->containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-sentinel';
|
||||||
|
})->first();
|
||||||
|
if ($sentinelContainerFound) {
|
||||||
|
$status = data_get($sentinelContainerFound, 'State.Status');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
PullSentinelImageJob::dispatch($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function serverStatus()
|
||||||
|
{
|
||||||
|
['uptime' => $uptime] = $this->server->validateConnection();
|
||||||
|
if ($uptime) {
|
||||||
|
if ($this->server->unreachable_notification_sent === true) {
|
||||||
|
$this->server->update(['unreachable_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// $this->server->team?->notify(new Unreachable($this->server));
|
||||||
|
foreach ($this->applications as $application) {
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->databases as $database) {
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$app->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
$foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
return data_get($value, 'Name') === '/coolify-log-drain';
|
||||||
|
})->first();
|
||||||
|
if ($foundLogDrainContainer) {
|
||||||
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function containerStatus()
|
||||||
|
{
|
||||||
|
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = data_get($service_db, 'service.uuid');
|
||||||
|
if ($uuid) {
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $this->databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $this->services->where('id', $serviceLabelId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($this->services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
if ($name) {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = "$name, available at $fqdn";
|
||||||
|
} else {
|
||||||
|
$containerName = $name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($fqdn) {
|
||||||
|
$containerName = $fqdn;
|
||||||
|
} else {
|
||||||
|
$containerName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $this->previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $this->previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $this->databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $this->databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Server\HighDiskUsage;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -44,57 +43,18 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
|
||||||
$this->remove_unnecessary_coolify_yaml();
|
$this->remove_unnecessary_coolify_yaml();
|
||||||
if ($this->server->isSentinelEnabled()) {
|
if ($this->server->isSentinelEnabled()) {
|
||||||
$this->server->checkSentinel();
|
$this->server->checkSentinel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
// send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// $this->check_docker_engine();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function check_docker_engine()
|
|
||||||
{
|
|
||||||
$version = instant_remote_process([
|
|
||||||
'docker info',
|
|
||||||
], $this->server, false);
|
|
||||||
if (is_null($version)) {
|
|
||||||
$os = instant_remote_process([
|
|
||||||
'cat /etc/os-release | grep ^ID=',
|
|
||||||
], $this->server, false);
|
|
||||||
$os = str($os)->after('ID=')->trim();
|
|
||||||
if ($os === 'ubuntu') {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'systemctl start docker',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'service docker start',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_unnecessary_coolify_yaml()
|
private function remove_unnecessary_coolify_yaml()
|
||||||
@@ -108,28 +68,4 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
], $this->server, false);
|
], $this->server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cleanup(bool $notify = false): void
|
|
||||||
{
|
|
||||||
$this->disk_usage = $this->server->getDiskUsage();
|
|
||||||
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
|
|
||||||
if ($notify) {
|
|
||||||
if ($this->server->high_disk_usage_notification_sent) {
|
|
||||||
ray('high disk usage notification already sent');
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = true;
|
|
||||||
$this->server->save();
|
|
||||||
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DockerCleanupJob::dispatchSync($this->server);
|
|
||||||
$this->cleanup(notify: true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = false;
|
|
||||||
$this->server->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view('emails.subscription-invoice-failed', [
|
$mail->view('emails.subscription-invoice-failed', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
||||||
$mail->view('emails.trial-ended', [
|
$mail->view('emails.trial-ended', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
$session = getStripeCustomerPortalSession($this->team);
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->subject('You trial in Coolify Cloud ends soon.');
|
$mail->subject('You trial in Coolify Cloud ends soon.');
|
||||||
$mail->view('emails.trial-ends-soon', [
|
$mail->view('emails.trial-ends-soon', [
|
||||||
'stripeCustomerPortal' => $session->url,
|
'stripeCustomerPortal' => $session->url,
|
||||||
|
|||||||
51
app/Jobs/UpdateCoolifyJob.php
Normal file
51
app/Jobs/UpdateCoolifyJob.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\UpdateCoolify;
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 600;
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CheckForUpdatesJob::dispatchSync();
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (! $settings->new_version_available) {
|
||||||
|
Log::info('No new version available. Skipping update.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = Server::findOrFail(0);
|
||||||
|
if (! $server) {
|
||||||
|
Log::error('Server not found. Cannot proceed with update.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Starting Coolify update process...');
|
||||||
|
UpdateCoolify::run(false); // false means it's not a manual update
|
||||||
|
|
||||||
|
$settings->update(['new_version_available' => false]);
|
||||||
|
Log::info('Coolify update completed successfully.');
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('UpdateCoolifyJob failed: '.$e->getMessage());
|
||||||
|
// Consider implementing a notification to administrators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ class MaintenanceModeDisabledNotification
|
|||||||
$class = "App\Http\Controllers\Webhook\\".ucfirst(str($endpoint)->before('::')->value());
|
$class = "App\Http\Controllers\Webhook\\".ucfirst(str($endpoint)->before('::')->value());
|
||||||
$method = str($endpoint)->after('::')->value();
|
$method = str($endpoint)->after('::')->value();
|
||||||
try {
|
try {
|
||||||
$instance = new $class();
|
$instance = new $class;
|
||||||
$instance->$method($request);
|
$instance->$method($request);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
ray($th);
|
ray($th);
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
public function getProxyType()
|
public function getProxyType()
|
||||||
{
|
{
|
||||||
// Set Default Proxy Type
|
// Set Default Proxy Type
|
||||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
$this->selectProxy(ProxyTypes::TRAEFIK->value);
|
||||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||||
// if (!$proxyTypeSet) {
|
// if (!$proxyTypeSet) {
|
||||||
// $this->currentState = 'select-proxy';
|
// $this->currentState = 'select-proxy';
|
||||||
@@ -257,7 +257,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
$this->createdServer->settings->save();
|
$this->createdServer->settings->save();
|
||||||
$this->createdServer->addInitialNetwork();
|
|
||||||
$this->selectedExistingServer = $this->createdServer->id;
|
$this->selectedExistingServer = $this->createdServer->id;
|
||||||
$this->currentState = 'validate-server';
|
$this->currentState = 'validate-server';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class Docker extends Component
|
|||||||
if (request()->query('network_name')) {
|
if (request()->query('network_name')) {
|
||||||
$this->network = request()->query('network_name');
|
$this->network = request()->query('network_name');
|
||||||
} else {
|
} else {
|
||||||
$this->network = new Cuid2(7);
|
$this->network = new Cuid2;
|
||||||
}
|
}
|
||||||
if ($this->servers->count() > 0) {
|
if ($this->servers->count() > 0) {
|
||||||
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
@@ -39,7 +38,7 @@ class Help extends Component
|
|||||||
$this->rateLimit(3, 30);
|
$this->rateLimit(3, 30);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$debug = "Route: {$this->path}";
|
$debug = "Route: {$this->path}";
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage;
|
||||||
$mail->view(
|
$mail->view(
|
||||||
'emails.help',
|
'emails.help',
|
||||||
[
|
[
|
||||||
@@ -48,7 +47,7 @@ class Help extends Component
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP]: {$this->subject}");
|
$mail->subject("[HELP]: {$this->subject}");
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
if (! $type) {
|
if (! $type) {
|
||||||
$url = 'https://app.coolify.io/api/feedback';
|
$url = 'https://app.coolify.io/api/feedback';
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class MonacoEditor extends Component
|
|||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
if (is_null($this->id)) {
|
if (is_null($this->id)) {
|
||||||
$this->id = new Cuid2(7);
|
$this->id = new Cuid2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_null($this->name)) {
|
if (is_null($this->name)) {
|
||||||
|
|||||||
35
app/Livewire/NavbarDeleteTeam.php
Normal file
35
app/Livewire/NavbarDeleteTeam.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class NavbarDeleteTeam extends Component
|
||||||
|
{
|
||||||
|
public function delete()
|
||||||
|
{
|
||||||
|
$currentTeam = currentTeam();
|
||||||
|
$currentTeam->delete();
|
||||||
|
|
||||||
|
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||||
|
if ($user->id === auth()->user()->id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$user->teams()->detach($currentTeam);
|
||||||
|
$session = DB::table('sessions')->where('user_id', $user->id)->first();
|
||||||
|
if ($session) {
|
||||||
|
DB::table('sessions')->where('id', $session->id)->delete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshSession();
|
||||||
|
|
||||||
|
return redirect()->route('team.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.navbar-delete-team');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ class Discord extends Component
|
|||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test());
|
$this->team?->notify(new Test);
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Notifications;
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -173,7 +172,7 @@ class Email extends Component
|
|||||||
|
|
||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($settings->smtp_enabled) {
|
if ($settings->smtp_enabled) {
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
$team->update([
|
$team->update([
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class Telegram extends Component
|
|||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test());
|
$this->team?->notify(new Test);
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,11 +91,25 @@ class Advanced extends Component
|
|||||||
|
|
||||||
public function saveCustomName()
|
public function saveCustomName()
|
||||||
{
|
{
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
||||||
} else {
|
} else {
|
||||||
$this->application->settings->custom_internal_name = null;
|
$this->application->settings->custom_internal_name = null;
|
||||||
}
|
}
|
||||||
|
$customInternalName = $this->application->settings->custom_internal_name;
|
||||||
|
$server = $this->application->destination->server;
|
||||||
|
$allApplications = $server->applications();
|
||||||
|
|
||||||
|
$foundSameInternalName = $allApplications->filter(function ($application) {
|
||||||
|
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
|
||||||
|
});
|
||||||
|
if ($foundSameInternalName->isNotEmpty()) {
|
||||||
|
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
|
||||||
|
$this->application->settings->custom_internal_name = $customInternalName;
|
||||||
|
$this->application->settings->refresh();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Custom name saved.');
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ class General extends Component
|
|||||||
|
|
||||||
public ?string $initialDockerComposeLocation = null;
|
public ?string $initialDockerComposeLocation = null;
|
||||||
|
|
||||||
public ?string $initialDockerComposePrLocation = null;
|
|
||||||
|
|
||||||
public ?Collection $parsedServices;
|
public ?Collection $parsedServices;
|
||||||
|
|
||||||
public $parsedServiceDomains = [];
|
public $parsedServiceDomains = [];
|
||||||
@@ -72,11 +70,8 @@ class General extends Component
|
|||||||
'application.docker_registry_image_tag' => 'nullable',
|
'application.docker_registry_image_tag' => 'nullable',
|
||||||
'application.dockerfile_location' => 'nullable',
|
'application.dockerfile_location' => 'nullable',
|
||||||
'application.docker_compose_location' => 'nullable',
|
'application.docker_compose_location' => 'nullable',
|
||||||
'application.docker_compose_pr_location' => 'nullable',
|
|
||||||
'application.docker_compose' => 'nullable',
|
'application.docker_compose' => 'nullable',
|
||||||
'application.docker_compose_pr' => 'nullable',
|
|
||||||
'application.docker_compose_raw' => 'nullable',
|
'application.docker_compose_raw' => 'nullable',
|
||||||
'application.docker_compose_pr_raw' => 'nullable',
|
|
||||||
'application.dockerfile_target_build' => 'nullable',
|
'application.dockerfile_target_build' => 'nullable',
|
||||||
'application.docker_compose_custom_start_command' => 'nullable',
|
'application.docker_compose_custom_start_command' => 'nullable',
|
||||||
'application.docker_compose_custom_build_command' => 'nullable',
|
'application.docker_compose_custom_build_command' => 'nullable',
|
||||||
@@ -89,6 +84,8 @@ class General extends Component
|
|||||||
'application.settings.is_static' => 'boolean|required',
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
|
||||||
'application.watch_paths' => 'nullable',
|
'application.watch_paths' => 'nullable',
|
||||||
'application.redirect' => 'string|required',
|
'application.redirect' => 'string|required',
|
||||||
];
|
];
|
||||||
@@ -114,11 +111,8 @@ class General extends Component
|
|||||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||||
'application.dockerfile_location' => 'Dockerfile location',
|
'application.dockerfile_location' => 'Dockerfile location',
|
||||||
'application.docker_compose_location' => 'Docker compose location',
|
'application.docker_compose_location' => 'Docker compose location',
|
||||||
'application.docker_compose_pr_location' => 'Docker compose location',
|
|
||||||
'application.docker_compose' => 'Docker compose',
|
'application.docker_compose' => 'Docker compose',
|
||||||
'application.docker_compose_pr' => 'Docker compose',
|
|
||||||
'application.docker_compose_raw' => 'Docker compose raw',
|
'application.docker_compose_raw' => 'Docker compose raw',
|
||||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
|
||||||
'application.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||||
@@ -127,6 +121,8 @@ class General extends Component
|
|||||||
'application.settings.is_static' => 'Is static',
|
'application.settings.is_static' => 'Is static',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||||
|
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
|
||||||
|
'application.settings.is_preserve_repository_enabled' => 'Is preserve repository enabled',
|
||||||
'application.watch_paths' => 'Watch paths',
|
'application.watch_paths' => 'Watch paths',
|
||||||
'application.redirect' => 'Redirect',
|
'application.redirect' => 'Redirect',
|
||||||
];
|
];
|
||||||
@@ -151,7 +147,7 @@ class General extends Component
|
|||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -183,7 +179,7 @@ class General extends Component
|
|||||||
if ($isInit && $this->application->docker_compose_raw) {
|
if ($isInit && $this->application->docker_compose_raw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
|
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
||||||
if (is_null($this->parsedServices)) {
|
if (is_null($this->parsedServices)) {
|
||||||
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
||||||
|
|
||||||
@@ -218,11 +214,10 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
$this->dispatch('success', 'Docker compose file loaded.');
|
$this->dispatch('success', 'Docker compose file loaded.');
|
||||||
$this->dispatch('compose_loaded');
|
$this->dispatch('compose_loaded');
|
||||||
$this->dispatch('refresh_storages');
|
$this->dispatch('refreshStorages');
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('refreshEnvs');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||||
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -233,7 +228,7 @@ class General extends Component
|
|||||||
|
|
||||||
public function generateDomain(string $serviceName)
|
public function generateDomain(string $serviceName)
|
||||||
{
|
{
|
||||||
$uuid = new Cuid2(7);
|
$uuid = new Cuid2;
|
||||||
$domain = generateFqdn($this->application->destination->server, $uuid);
|
$domain = generateFqdn($this->application->destination->server, $uuid);
|
||||||
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
|
$this->parsedServiceDomains[$serviceName]['domain'] = $domain;
|
||||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
@@ -299,6 +294,9 @@ class General extends Component
|
|||||||
|
|
||||||
public function resetDefaultLabels()
|
public function resetDefaultLabels()
|
||||||
{
|
{
|
||||||
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
@@ -359,8 +357,7 @@ class General extends Component
|
|||||||
$this->checkFqdns();
|
$this->checkFqdns();
|
||||||
|
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -373,6 +370,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||||
$this->resetDefaultLabels();
|
$this->resetDefaultLabels();
|
||||||
}
|
}
|
||||||
@@ -399,6 +397,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
if ($this->application->build_pack === 'dockercompose') {
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
|
|
||||||
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
||||||
$domain = data_get($service, 'domain');
|
$domain = data_get($service, 'domain');
|
||||||
if ($domain) {
|
if ($domain) {
|
||||||
@@ -408,6 +407,9 @@ class General extends Component
|
|||||||
check_domain_usage(resource: $this->application);
|
check_domain_usage(resource: $this->application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->application->isDirty('docker_compose_domains')) {
|
||||||
|
$this->resetDefaultLabels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ namespace App\Livewire\Project\Application;
|
|||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Events\ApplicationStatusChanged;
|
use App\Events\ApplicationStatusChanged;
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Jobs\ServerStatusJob;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
@@ -46,11 +44,7 @@ class Heading extends Component
|
|||||||
{
|
{
|
||||||
if ($this->application->destination->server->isFunctional()) {
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
|
GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
|
||||||
// dispatch(new ContainerStatusJob($this->application->destination->server));
|
|
||||||
} else {
|
|
||||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($showNotification) {
|
if ($showNotification) {
|
||||||
$this->dispatch('success', 'Success', 'Application status updated.');
|
$this->dispatch('success', 'Success', 'Application status updated.');
|
||||||
}
|
}
|
||||||
@@ -102,7 +96,7 @@ class Heading extends Component
|
|||||||
|
|
||||||
protected function setDeploymentUuid()
|
protected function setDeploymentUuid()
|
||||||
{
|
{
|
||||||
$this->deploymentUuid = new Cuid2(7);
|
$this->deploymentUuid = new Cuid2;
|
||||||
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class Previews extends Component
|
|||||||
$template = $this->application->preview_url_template;
|
$template = $this->application->preview_url_template;
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
$random = new Cuid2(7);
|
$random = new Cuid2;
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
|
$preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn);
|
||||||
@@ -170,7 +170,7 @@ class Previews extends Component
|
|||||||
|
|
||||||
protected function setDeploymentUuid()
|
protected function setDeploymentUuid()
|
||||||
{
|
{
|
||||||
$this->deployment_uuid = new Cuid2(7);
|
$this->deployment_uuid = new Cuid2;
|
||||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class PreviewsCompose extends Component
|
|||||||
$template = $this->preview->application->preview_url_template;
|
$template = $this->preview->application->preview_url_template;
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$schema = $url->getScheme();
|
$schema = $url->getScheme();
|
||||||
$random = new Cuid2(7);
|
$random = new Cuid2;
|
||||||
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
|
$preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Rollback extends Component
|
|||||||
|
|
||||||
public function rollbackImage($commit)
|
public function rollbackImage($commit)
|
||||||
{
|
{
|
||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $this->application,
|
application: $this->application,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class CloneMe extends Component
|
|||||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||||
$this->project_id = $this->project->id;
|
$this->project_id = $this->project->id;
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2(7))->slug();
|
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
@@ -106,7 +106,7 @@ class CloneMe extends Component
|
|||||||
$databases = $this->environment->databases();
|
$databases = $this->environment->databases();
|
||||||
$services = $this->environment->services;
|
$services = $this->environment->services;
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$uuid = (string) new Cuid2(7);
|
$uuid = (string) new Cuid2;
|
||||||
$newApplication = $application->replicate()->fill([
|
$newApplication = $application->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'fqdn' => generateFqdn($this->server, $uuid),
|
'fqdn' => generateFqdn($this->server, $uuid),
|
||||||
@@ -133,7 +133,7 @@ class CloneMe extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($databases as $database) {
|
foreach ($databases as $database) {
|
||||||
$uuid = (string) new Cuid2(7);
|
$uuid = (string) new Cuid2;
|
||||||
$newDatabase = $database->replicate()->fill([
|
$newDatabase = $database->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
@@ -161,7 +161,7 @@ class CloneMe extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($services as $service) {
|
foreach ($services as $service) {
|
||||||
$uuid = (string) new Cuid2(7);
|
$uuid = (string) new Cuid2;
|
||||||
$newService = $service->replicate()->fill([
|
$newService = $service->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
|
|||||||
@@ -46,10 +46,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +85,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
|||||||
@@ -44,10 +44,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +100,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
|||||||
@@ -2,14 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Actions\Database\StartClickhouse;
|
use App\Actions\Database\RestartDatabase;
|
||||||
use App\Actions\Database\StartDragonfly;
|
use App\Actions\Database\StartDatabase;
|
||||||
use App\Actions\Database\StartKeydb;
|
|
||||||
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\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -47,7 +41,6 @@ class Heading extends Component
|
|||||||
public function check_status($showNotification = false)
|
public function check_status($showNotification = false)
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->database->destination->server);
|
GetContainersStatus::run($this->database->destination->server);
|
||||||
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
if ($showNotification) {
|
if ($showNotification) {
|
||||||
$this->dispatch('success', 'Database status updated.');
|
$this->dispatch('success', 'Database status updated.');
|
||||||
@@ -67,32 +60,15 @@ class Heading extends Component
|
|||||||
$this->check_status();
|
$this->check_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
$activity = RestartDatabase::run($this->database);
|
||||||
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
|
}
|
||||||
|
|
||||||
public function start()
|
public function start()
|
||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
$activity = StartDatabase::run($this->database);
|
||||||
$activity = StartPostgresql::run($this->database);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-redis') {
|
|
||||||
$activity = StartRedis::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mongodb') {
|
|
||||||
$activity = StartMongodb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mysql') {
|
|
||||||
$activity = StartMysql::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-mariadb') {
|
|
||||||
$activity = StartMariadb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-keydb') {
|
|
||||||
$activity = StartKeydb::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-dragonfly') {
|
|
||||||
$activity = StartDragonfly::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
} elseif ($this->database->type() === 'standalone-clickhouse') {
|
|
||||||
$activity = StartClickhouse::run($this->database);
|
|
||||||
$this->dispatch('activityMonitor', $activity->id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,8 @@ class General extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->get_db_url(true);
|
$this->db_url = $this->database->internal_db_url;
|
||||||
if ($this->database->is_public) {
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
}
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -108,13 +106,12 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = $this->database->get_db_url();
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->db_url_public = null;
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = ! $this->database->is_public;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user