mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-26 04:59:31 +00:00
Compare commits
431 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb1d86d08b | ||
|
|
88f3f628ef | ||
|
|
295bea37bc | ||
|
|
bd7d756254 | ||
|
|
4261147fe8 | ||
|
|
a70adc5eb3 | ||
|
|
06d40b8a81 | ||
|
|
2358510cba | ||
|
|
e6d13cb7d7 | ||
|
|
39e21c3f36 | ||
|
|
8da900ee72 | ||
|
|
9f4e81a1a3 | ||
|
|
0b918c2f51 | ||
|
|
085cd2a314 | ||
|
|
98d2399568 | ||
|
|
515d9a0008 | ||
|
|
aece1fa7d3 | ||
|
|
abc614ecfd | ||
|
|
1180d3fdde | ||
|
|
1639d1725a | ||
|
|
5df1deecbc | ||
|
|
fe3c0cf76e | ||
|
|
cc0df0182c | ||
|
|
eb354f639f | ||
|
|
02c530dcbe | ||
|
|
4ad7e1f8e6 | ||
|
|
2007ba0c3b | ||
|
|
2009dc11db | ||
|
|
62f2196a0c | ||
|
|
e63c65da4f | ||
|
|
570a082227 | ||
|
|
9b1ede3a59 | ||
|
|
c445fc0f8a | ||
|
|
699493cf24 | ||
|
|
6c89686f31 | ||
|
|
f55b861849 | ||
|
|
adf82c04ad | ||
|
|
1b80956fe8 | ||
|
|
de9da8caf9 | ||
|
|
967f42dd89 | ||
|
|
95e8b29fa2 | ||
|
|
2e3c815e53 | ||
|
|
132707caa7 | ||
|
|
0dad616c38 | ||
|
|
c0882dffde | ||
|
|
5e082c647c | ||
|
|
285c3c2f5d | ||
|
|
dcb29a80fe | ||
|
|
b45ad19732 | ||
|
|
f12d453b5f | ||
|
|
8a00b711be | ||
|
|
56204efc7a | ||
|
|
da638c270f | ||
|
|
ad4b974274 | ||
|
|
943a05edcc | ||
|
|
1a28e65e50 | ||
|
|
cd3af7fa39 | ||
|
|
8ccb0c88db | ||
|
|
127880cf8d | ||
|
|
2e56086661 | ||
|
|
a129be0dbd | ||
|
|
12c0760cb3 | ||
|
|
9d3ed85ffd | ||
|
|
850d57d0d2 | ||
|
|
7981bec1ed | ||
|
|
76373a8597 | ||
|
|
9913e7b70b | ||
|
|
a08bb25bfa | ||
|
|
28ec164bc2 | ||
|
|
3d5ea8629c | ||
|
|
4aaf59d034 | ||
|
|
14850476c7 | ||
|
|
bf5b6170fa | ||
|
|
6f91591448 | ||
|
|
3c723bcba2 | ||
|
|
e7dd13cffa | ||
|
|
ad91630faa | ||
|
|
57f746b584 | ||
|
|
a55720091c | ||
|
|
b461635834 | ||
|
|
1375580651 | ||
|
|
3d20433ad1 | ||
|
|
58447c6456 | ||
|
|
c6273e9177 | ||
|
|
ffdc158d44 | ||
|
|
876c81fad8 | ||
|
|
028ee6d7b1 | ||
|
|
ec00548f1b | ||
|
|
c4dc03e4a8 | ||
|
|
3a510a77ec | ||
|
|
98a785fced | ||
|
|
c48654160d | ||
|
|
55b80132c4 | ||
|
|
1f0c168936 | ||
|
|
6715bc750f | ||
|
|
04a48a626b | ||
|
|
2f9f0da7c6 | ||
|
|
513c4f9e29 | ||
|
|
3f078517a0 | ||
|
|
37036f0fca | ||
|
|
5789aadb5c | ||
|
|
a768ed718a | ||
|
|
9c6092f31f | ||
|
|
40d294a247 | ||
|
|
72844e4edc | ||
|
|
db0a71125a | ||
|
|
da244af39d | ||
|
|
067f502d3c | ||
|
|
fffc6b1e4e | ||
|
|
9121c6a078 | ||
|
|
9c4e581d8b | ||
|
|
dfadd31f46 | ||
|
|
0cfa6fff43 | ||
|
|
d61671c1a0 | ||
|
|
d4f10a9af3 | ||
|
|
03861af893 | ||
|
|
ae531c445d | ||
|
|
4b26aeef9a | ||
|
|
1e47b79b50 | ||
|
|
0c223dcec4 | ||
|
|
0f4536c3d3 | ||
|
|
f43c584463 | ||
|
|
91c558ec83 | ||
|
|
9d45ab3246 | ||
|
|
34ff6eb567 | ||
|
|
8793c00438 | ||
|
|
d7981d5c3e | ||
|
|
bcaae3b67b | ||
|
|
046d9f9597 | ||
|
|
81bd0301d2 | ||
|
|
530e7e494f | ||
|
|
d402fd5690 | ||
|
|
eebec3b92f | ||
|
|
211c6585fa | ||
|
|
e1b5c40ca0 | ||
|
|
747a9b521b | ||
|
|
c2d72ad309 | ||
|
|
596181b622 | ||
|
|
77c5270e1e | ||
|
|
a663c14df8 | ||
|
|
3bd9f00268 | ||
|
|
1aadda735d | ||
|
|
12035208e2 | ||
|
|
df8a9f673c | ||
|
|
aa5c8a2c56 | ||
|
|
a84540e6bb | ||
|
|
fb91b64063 | ||
|
|
94cc77ebca | ||
|
|
aac6981304 | ||
|
|
ca05828b68 | ||
|
|
8ec6b4c59c | ||
|
|
f1be5f5341 | ||
|
|
714c264002 | ||
|
|
eca58097ef | ||
|
|
281146e22b | ||
|
|
f3a19a5d02 | ||
|
|
9b9b6937f4 | ||
|
|
f54c0b7dff | ||
|
|
36c58ad286 | ||
|
|
a67f633259 | ||
|
|
f39a607c1a | ||
|
|
0cc67ed2e5 | ||
|
|
5f8402c645 | ||
|
|
3ab87cd11e | ||
|
|
d5620d305d | ||
|
|
35ebc5e842 | ||
|
|
66276be1d2 | ||
|
|
47c0d522db | ||
|
|
b654883d1a | ||
|
|
b4f9d29129 | ||
|
|
bec6b961f3 | ||
|
|
2ce8f34306 | ||
|
|
30d1ae59ec | ||
|
|
ac7d4e3645 | ||
|
|
868c4001f6 | ||
|
|
e99c44d967 | ||
|
|
48a877f160 | ||
|
|
cea894a8bd | ||
|
|
087e7b9311 | ||
|
|
39ba498293 | ||
|
|
fe7390bd4d | ||
|
|
75af551435 | ||
|
|
ae2d3ebb48 | ||
|
|
5ff6c53715 | ||
|
|
3c94723b23 | ||
|
|
c6a2e3e328 | ||
|
|
2dc5e10878 | ||
|
|
4086dfcf56 | ||
|
|
7937c2bab0 | ||
|
|
5ffa8e9936 | ||
|
|
c431cee517 | ||
|
|
375f17e728 | ||
|
|
d3f658c874 | ||
|
|
5e340a4cdd | ||
|
|
409a5b9f99 | ||
|
|
fba305020b | ||
|
|
bd4ce3ac45 | ||
|
|
733de60f7c | ||
|
|
c365a44e01 | ||
|
|
e94f450bf0 | ||
|
|
d5efc9ddde | ||
|
|
68895ba4a5 | ||
|
|
139aa7a0fc | ||
|
|
4955157e13 | ||
|
|
f2dd5cc75e | ||
|
|
2ad634dbc6 | ||
|
|
de13f65a24 | ||
|
|
8994dde8f0 | ||
|
|
b7303a0828 | ||
|
|
5bc330162a | ||
|
|
0ebc0217f3 | ||
|
|
95c810b80a | ||
|
|
82d7fb883d | ||
|
|
b96e710543 | ||
|
|
24e5e85225 | ||
|
|
7b8f81f1b2 | ||
|
|
62e60fc7ab | ||
|
|
ccd3d4aded | ||
|
|
f0cf155b5c | ||
|
|
b66f67d889 | ||
|
|
e5dc07bde1 | ||
|
|
a955eb0fec | ||
|
|
c070af9681 | ||
|
|
c15e060ef2 | ||
|
|
6d6f2454a7 | ||
|
|
9ff44ed46b | ||
|
|
adf3ef61b8 | ||
|
|
832107e0b8 | ||
|
|
0ea1e71808 | ||
|
|
3b9b3f8ffa | ||
|
|
fda8823050 | ||
|
|
b5756cb14f | ||
|
|
617d3dbe52 | ||
|
|
c18beb1c7c | ||
|
|
a6957b919c | ||
|
|
816a362534 | ||
|
|
7ce3ebde4e | ||
|
|
cc2f83c4d9 | ||
|
|
6ce492049e | ||
|
|
a7999de4b0 | ||
|
|
d4bdfabf19 | ||
|
|
85030ab804 | ||
|
|
2a9bd00a50 | ||
|
|
1c2d76e651 | ||
|
|
a97f7d225a | ||
|
|
2781848aac | ||
|
|
d179da2bee | ||
|
|
60e7922734 | ||
|
|
1ece37ec3c | ||
|
|
8dad865146 | ||
|
|
80d15e782b | ||
|
|
d24e4c6518 | ||
|
|
6def46544c | ||
|
|
d66bae32d3 | ||
|
|
1b753a4020 | ||
|
|
25e6a74a0a | ||
|
|
afde00a4be | ||
|
|
0022d380bb | ||
|
|
d80d2ab934 | ||
|
|
13c1734753 | ||
|
|
bf33d6c34e | ||
|
|
1b02f9bd5d | ||
|
|
9f63c645ff | ||
|
|
abf271fb68 | ||
|
|
363755c3bf | ||
|
|
ba2db666aa | ||
|
|
9f3677b694 | ||
|
|
e6024c997f | ||
|
|
3cb83e2286 | ||
|
|
780d03e5e1 | ||
|
|
214114e6ce | ||
|
|
274d3fe679 | ||
|
|
0ecf86d8a3 | ||
|
|
c2d4390a72 | ||
|
|
7627d59d43 | ||
|
|
71ce9a6b37 | ||
|
|
232018c925 | ||
|
|
9dfbbe58ff | ||
|
|
fa9738a2e0 | ||
|
|
94ecbc5921 | ||
|
|
3c68d317d7 | ||
|
|
56d4edfb9d | ||
|
|
c6c037ff17 | ||
|
|
44feba4d89 | ||
|
|
962f2c7380 | ||
|
|
22007426aa | ||
|
|
008e9a92d3 | ||
|
|
41139ee2ab | ||
|
|
845c40d23c | ||
|
|
a22f26c4c8 | ||
|
|
99ff020f56 | ||
|
|
f863b42b71 | ||
|
|
2e713b459e | ||
|
|
923241ce1e | ||
|
|
3a8929b9d7 | ||
|
|
eb92d39d40 | ||
|
|
bdc62a007e | ||
|
|
4b35db6291 | ||
|
|
c8282b215d | ||
|
|
c123669828 | ||
|
|
781fd0a1cd | ||
|
|
9bd99605fb | ||
|
|
dc626bd4f0 | ||
|
|
aa27aeafa1 | ||
|
|
cdb25cd0e9 | ||
|
|
dc2d15fd9c | ||
|
|
55cb788380 | ||
|
|
0f3b7fe643 | ||
|
|
4b812350a8 | ||
|
|
aec37164de | ||
|
|
dec02bd8db | ||
|
|
1bd6a8ed9e | ||
|
|
2030f714fa | ||
|
|
4416646954 | ||
|
|
52ba9dc02a | ||
|
|
dad3d42d14 | ||
|
|
0d12f3043b | ||
|
|
1225786fc0 | ||
|
|
71496d5229 | ||
|
|
eb0aa20fe1 | ||
|
|
c34de3d0a3 | ||
|
|
54e0a9fc28 | ||
|
|
4bcd034b3d | ||
|
|
111bd29cc8 | ||
|
|
e038865693 | ||
|
|
dfd29dc37a | ||
|
|
b0fcd23ca6 | ||
|
|
f80b1d31f5 | ||
|
|
811ea5b92a | ||
|
|
f9dfbd5800 | ||
|
|
88f1c36929 | ||
|
|
8bbe771f5b | ||
|
|
c578fa63e5 | ||
|
|
4448b86b93 | ||
|
|
17badf95dc | ||
|
|
a267ee40d2 | ||
|
|
8ef645b3c2 | ||
|
|
35625b22f5 | ||
|
|
221dcefd6c | ||
|
|
9c74a9c1db | ||
|
|
55fc3920fc | ||
|
|
5d60b5eb8b | ||
|
|
049d5166e8 | ||
|
|
f4019db3d1 | ||
|
|
9f3732d35b | ||
|
|
b4f17ac3c6 | ||
|
|
978e35d335 | ||
|
|
22cbbec960 | ||
|
|
21f3a70788 | ||
|
|
b4c6f80e1c | ||
|
|
e1198c42eb | ||
|
|
e09fdbcef0 | ||
|
|
b708e79929 | ||
|
|
cbaecff3b7 | ||
|
|
4f7d2630af | ||
|
|
92d3860240 | ||
|
|
3757d5da9f | ||
|
|
1d38a885bb | ||
|
|
dbd767e8f1 | ||
|
|
8b83c38127 | ||
|
|
f1ea01e709 | ||
|
|
12a1aeb0f8 | ||
|
|
413150012f | ||
|
|
8ef5604ce8 | ||
|
|
42e50c800b | ||
|
|
8fbd08003c | ||
|
|
877577efdb | ||
|
|
a6f457749b | ||
|
|
9afb713df1 | ||
|
|
8f660c0276 | ||
|
|
a7e86d9afd | ||
|
|
462eea90c0 | ||
|
|
79c30dfc91 | ||
|
|
410a78b366 | ||
|
|
065807a0bc | ||
|
|
1d93658e56 | ||
|
|
2b7865e6ea | ||
|
|
0cdba8c329 | ||
|
|
11b317b788 | ||
|
|
fb955e15f4 | ||
|
|
ae2d141f0d | ||
|
|
68c983923e | ||
|
|
bf252f7f20 | ||
|
|
324038486f | ||
|
|
bef5da49cf | ||
|
|
24e7f547fa | ||
|
|
3ee3ab0ad1 | ||
|
|
f734154da8 | ||
|
|
7a053ce697 | ||
|
|
25f250310e | ||
|
|
4eca05bbba | ||
|
|
45b0f791bb | ||
|
|
42415a81c1 | ||
|
|
6882e83d1e | ||
|
|
d4b7318413 | ||
|
|
a2b4d400af | ||
|
|
f07868d24e | ||
|
|
d46ee049f4 | ||
|
|
c62eda5627 | ||
|
|
7683164ed2 | ||
|
|
7090c16575 | ||
|
|
9e634fed13 | ||
|
|
9bb125cebd | ||
|
|
0c4850b91d | ||
|
|
0eb7688c4d | ||
|
|
f47cdb68d9 | ||
|
|
d3c3cded37 | ||
|
|
e91ea4ecbe | ||
|
|
680b20d199 | ||
|
|
ec97e04fd4 | ||
|
|
b0b2657fe0 | ||
|
|
d27426fd8f | ||
|
|
d8206c0e3e | ||
|
|
3f1841a188 | ||
|
|
cb478e0dc8 | ||
|
|
02c42a7e3a | ||
|
|
ef40f7349e | ||
|
|
86eebb35cb | ||
|
|
a901388887 | ||
|
|
6cd1c5de38 | ||
|
|
7489f172a1 | ||
|
|
702798c275 | ||
|
|
430d51866c | ||
|
|
9d08421f01 | ||
|
|
f4051874b2 | ||
|
|
bbe0690056 | ||
|
|
772c0d1e41 | ||
|
|
8eb9ca0260 | ||
|
|
bd27afe0da | ||
|
|
a3af21275a | ||
|
|
61eb155d13 |
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
|
.pnpm-store
|
||||||
build
|
build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
@@ -7,6 +8,9 @@ package
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
dist
|
dist
|
||||||
client
|
|
||||||
apps/api/db/*.db
|
apps/api/db/*.db
|
||||||
local-serve
|
local-serve
|
||||||
|
apps/api/db/migration.db-journal
|
||||||
|
apps/api/core*
|
||||||
|
logs
|
||||||
|
others/certificates
|
||||||
|
|||||||
10
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
10
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -9,13 +9,21 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this bug report! Please fill the form in English
|
Thanks for taking the time to fill out this bug report! Please fill the form in English.
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this?
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: repository
|
||||||
|
attributes:
|
||||||
|
label: Example public repository
|
||||||
|
description: "An example public git repository to reproduce the issue easily (if applicable)."
|
||||||
|
placeholder: "ex: https://github.com/coollabsio/coolify"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Description
|
||||||
|
|||||||
93
.github/workflows/fluent-bit-release.yml
vendored
Normal file
93
.github/workflows/fluent-bit-release.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
name: fluent-bit-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "others/fluentbit"
|
||||||
|
- ".github/workflows/fluent-bit-release.yml"
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
arm64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: others/fluentbit/
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify-fluent-bit:1.0.0-arm64
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: others/fluentbit/
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify-fluent-bit:1.0.0-amd64
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: others/fluentbit/
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify-fluent-bit:1.0.0-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [amd64, arm64, aarch64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create coollabsio/coolify-fluent-bit:1.0.0 --amend coollabsio/coolify-fluent-bit:1.0.0-amd64 --amend coollabsio/coolify-fluent-bit:1.0.0-arm64 --amend coollabsio/coolify-fluent-bit:1.0.0-aarch64
|
||||||
|
docker manifest push coollabsio/coolify-fluent-bit:1.0.0
|
||||||
93
.github/workflows/pocketbase-release.yml
vendored
Normal file
93
.github/workflows/pocketbase-release.yml
vendored
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
name: pocketbase-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "others/pocketbase"
|
||||||
|
- ".github/workflows/pocketbase-release.yml"
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
arm64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: others/pocketbase/
|
||||||
|
platforms: linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/pocketbase:0.8.0-arm64
|
||||||
|
amd64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: others/pocketbase/
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/pocketbase:0.8.0-amd64
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: others/pocketbase/
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/pocketbase:0.8.0-aarch64
|
||||||
|
merge-manifest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [amd64, arm64, aarch64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Create & publish manifest
|
||||||
|
run: |
|
||||||
|
docker manifest create coollabsio/pocketbase:0.8.0 --amend coollabsio/pocketbase:0.8.0-amd64 --amend coollabsio/pocketbase:0.8.0-arm64 --amend coollabsio/pocketbase:0.8.0-aarch64
|
||||||
|
docker manifest push coollabsio/pocketbase:0.8.0
|
||||||
34
.github/workflows/production-release.yml
vendored
34
.github/workflows/production-release.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
types: [released]
|
types: [released]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
arm64-build:
|
arm64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
||||||
amd64-build:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -57,9 +57,35 @@ jobs:
|
|||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||||
|
aarch64:
|
||||||
|
runs-on: [self-hosted, arm64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Get current package version
|
||||||
|
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
||||||
|
id: package-version
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||||
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64
|
||||||
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-aarch64,mode=max
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [amd64-build, arm64-build]
|
needs: [amd64, arm64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -77,7 +103,7 @@ jobs:
|
|||||||
id: package-version
|
id: package-version
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||||
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
16
.github/workflows/staging-release.yml
vendored
16
.github/workflows/staging-release.yml
vendored
@@ -2,11 +2,17 @@ name: staging-release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
paths:
|
||||||
|
- "**"
|
||||||
|
- "!others/fluentbit"
|
||||||
|
- "!others/pocketbase"
|
||||||
|
- "!.github/workflows/fluent-bit-release.yml"
|
||||||
|
- "!.github/workflows/pocketbase-release.yml"
|
||||||
branches:
|
branches:
|
||||||
- next
|
- next
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
arm64-making-something-cool:
|
arm64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -34,7 +40,7 @@ jobs:
|
|||||||
tags: coollabsio/coolify:next-arm64
|
tags: coollabsio/coolify:next-arm64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
||||||
amd64-making-something-cool:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -59,12 +65,12 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:next-amd64,coollabsio/coolify:next-test
|
tags: coollabsio/coolify:next-amd64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||||
merge-manifest-to-be-cool:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
needs: [arm64, amd64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -8,10 +8,16 @@ package
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
dist
|
dist
|
||||||
client
|
|
||||||
apps/api/db/*.db
|
apps/api/db/*.db
|
||||||
local-serve
|
|
||||||
apps/api/db/migration.db-journal
|
apps/api/db/migration.db-journal
|
||||||
apps/api/core*
|
apps/api/core*
|
||||||
|
apps/backup/backups/*
|
||||||
|
!apps/backup/backups/.gitkeep
|
||||||
logs
|
logs
|
||||||
others/certificates
|
others/certificates
|
||||||
|
backups/*
|
||||||
|
!backups/.gitkeep
|
||||||
|
|
||||||
|
# Trpc
|
||||||
|
apps/server/db/*.db
|
||||||
|
apps/server/db/*.db-journal
|
||||||
21
.vscode/settings.json
vendored
21
.vscode/settings.json
vendored
@@ -1,11 +1,22 @@
|
|||||||
{
|
{
|
||||||
"i18n-ally.localesPaths": ["src/lib/locales"],
|
"i18n-ally.localesPaths": [
|
||||||
|
"src/lib/locales"
|
||||||
|
],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"i18n-ally.extract.ignoredByFiles": {
|
"i18n-ally.extract.ignoredByFiles": {
|
||||||
"src\\routes\\__layout.svelte": ["Coolify", "coolLabs logo"]
|
"src\\routes\\__layout.svelte": [
|
||||||
|
"Coolify",
|
||||||
|
"coolLabs logo"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"i18n-ally.sourceLanguage": "en",
|
"i18n-ally.sourceLanguage": "en",
|
||||||
"i18n-ally.enabledFrameworks": ["svelte"],
|
"i18n-ally.enabledFrameworks": [
|
||||||
"i18n-ally.enabledParsers": ["js", "ts", "json"],
|
"svelte"
|
||||||
|
],
|
||||||
|
"i18n-ally.enabledParsers": [
|
||||||
|
"js",
|
||||||
|
"ts",
|
||||||
|
"json"
|
||||||
|
],
|
||||||
"i18n-ally.extract.autoDetect": true
|
"i18n-ally.extract.autoDetect": true
|
||||||
}
|
}
|
||||||
137
CONTRIBUTION.md
137
CONTRIBUTION.md
@@ -1,115 +1,48 @@
|
|||||||
# Contribution
|
# Contributing
|
||||||
|
|
||||||
First, thanks for considering to contribute to my project. It really means a lot! :)
|
> "First, thanks for considering to contribute to my project.
|
||||||
|
It really means a lot! 😁" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||||
|
|
||||||
You can ask for guidance anytime on our Discord server in the #contribution channel.
|
You can ask for guidance anytime on our
|
||||||
|
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||||
|
|
||||||
## Setup your development environment
|
You'll need a set of skills to [get started](docs/contribution/GettingStarted.md).
|
||||||
### Github codespaces
|
|
||||||
|
|
||||||
If you have github codespaces enabled then you can just create a codespace and run `pnpm dev` to run your the dev environment. All the required dependencies and packages has been configured for you already.
|
## 1) Setup your development environment
|
||||||
|
|
||||||
### Gitpod
|
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recomended*
|
||||||
|
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
|
||||||
|
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md)
|
||||||
|
- ☁️ [GitPod](docs/dev_setup/GitPod.md)
|
||||||
|
- 🍏 [Local Mac](docs/dev_setup/Mac.md)
|
||||||
|
|
||||||
If you have a [Gitpod](https://gitpod.io), you can just create a workspace from this repository, run `pnpm install && pnpm db:push && pnpm db:seed` and then `pnpm dev`. All the required dependencies and packages has been configured for you already.
|
## 2) Basic requirements
|
||||||
|
|
||||||
### Local Machine
|
- [Install Pnpm](https://pnpm.io/installation)
|
||||||
> At the moment, Coolify `doesn't support Windows`. You must use `Linux` or `MacOS` or consider using Gitpod or Github Codespaces.
|
- [Install Docker Engine](https://docs.docker.com/engine/install/)
|
||||||
|
- [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/)
|
||||||
|
- [Setup GIT LFS Support](https://git-lfs.github.com/)
|
||||||
|
|
||||||
- Due to the lock file, this repository is best with [pnpm](https://pnpm.io). I recommend you try and use `pnpm` because it is cool and efficient!
|
## 3) Setup Coolify
|
||||||
|
|
||||||
- You need to have [Docker Engine](https://docs.docker.com/engine/install/) installed locally.
|
- Copy `apps/api/.env.example` to `apps/api/.env`
|
||||||
- You need to have [Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/) installed locally.
|
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||||
- You need to have [GIT LFS Support](https://git-lfs.github.com/) installed locally.
|
- Run `pnpm install` to install dependencies.
|
||||||
|
- Run `pnpm db:push` to o create a local SQlite database. This will apply all migrations at `db/dev.db`.
|
||||||
|
- Run `pnpm db:seed` seed the database.
|
||||||
|
- Run `pnpm dev` start coding.
|
||||||
|
|
||||||
Optional:
|
```sh
|
||||||
- To test Heroku buildpacks, you need [pack](https://github.com/buildpacks/pack) binary installed locally.
|
# Or... Copy and paste commands bellow:
|
||||||
|
cp apps/api/.env.example apps/api/.env
|
||||||
|
pnpm install
|
||||||
|
pnpm db:push
|
||||||
|
pnpm db:seed
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
### Inside a Docker container
|
## 4) Start Coding
|
||||||
`WIP`
|
|
||||||
|
|
||||||
## Setup Coolify
|
You should be able to access `http://localhost:3000`.
|
||||||
- Copy `apps/api/.env.template` to `apps/api/.env.template` and set the `COOLIFY_APP_ID` environment variable to something cool.
|
|
||||||
- `pnpm install` to install dependencies.
|
|
||||||
- `pnpm db:push` to o create a local SQlite database.
|
|
||||||
|
|
||||||
This will apply all migrations at `db/dev.db`.
|
1. Click `Register` and setup your first user.
|
||||||
|
|
||||||
- `pnpm db:seed` seed the database.
|
|
||||||
- `pnpm dev` start coding.
|
|
||||||
|
|
||||||
## Technical skills required
|
|
||||||
|
|
||||||
- **Languages**: Node.js / Javascript / Typescript
|
|
||||||
- **Framework JS/TS**: [SvelteKit](https://kit.svelte.dev/) & [Fastify](https://www.fastify.io/)
|
|
||||||
- **Database ORM**: [Prisma.io](https://www.prisma.io/)
|
|
||||||
- **Docker Engine API**
|
|
||||||
|
|
||||||
## Add a new service
|
|
||||||
### Which service is eligable to add to Coolify?
|
|
||||||
The following statements needs to be true:
|
|
||||||
|
|
||||||
- Self-hostable
|
|
||||||
- Open-source
|
|
||||||
- Maintained (I do not want to add software full of bugs)
|
|
||||||
|
|
||||||
### Create Prisma / Database schema for the new service.
|
|
||||||
All data that needs to be persist for a service should be saved to the database in `cleartext` or `encrypted`.
|
|
||||||
|
|
||||||
very password/api key/passphrase needs to be encrypted. If you are not sure, whether it should be encrypted or not, just encrypt it.
|
|
||||||
|
|
||||||
Update Prisma schema in [src/apps/api/prisma/schema.prisma](https://github.com/coollabsio/coolify/blob/main/apps/api/prisma/schema.prisma).
|
|
||||||
|
|
||||||
- Add new model with the new service name.
|
|
||||||
- Make a relationship with `Service` model.
|
|
||||||
- In the `Service` model, the name of the new field should be with low-capital.
|
|
||||||
- If the service needs a database, define a `publicPort` field to be able to make it's database public, example field name in case of PostgreSQL: `postgresqlPublicPort`. It should be a optional field.
|
|
||||||
|
|
||||||
Once done, create Prisma schema with `pnpm db:push`.
|
|
||||||
> You may also need to restart `Typescript Language Server` in your IDE to get the new types.
|
|
||||||
|
|
||||||
### Add available versions
|
|
||||||
|
|
||||||
Versions are hardcoded into Coolify at the moment and based on Docker image tags.
|
|
||||||
- Update `supportedServiceTypesAndVersions` function [here](apps/api/src/lib/services/supportedVersions.ts)
|
|
||||||
|
|
||||||
### Include the new service in queries
|
|
||||||
|
|
||||||
At [here](apps/api/src/lib/services/common.ts) in `includeServices` function add the new table name, so it will be included in all places in the database queries where it is required.
|
|
||||||
|
|
||||||
### Define auto-generated fields
|
|
||||||
|
|
||||||
At [here](apps/api/src/lib/services/common.ts) in `configureServiceType` function add the initial auto-generated details such as password, users etc, and the encryption process of secrets (if applicable).
|
|
||||||
|
|
||||||
### Define input field details
|
|
||||||
|
|
||||||
At [here](apps/api/src/lib/services/serviceFields.ts) add details about the input fields shown in the UI, so every component (API/UI) will know what to do with the values (decrypt/show it by default/readonly/etc).
|
|
||||||
|
|
||||||
### Define the start process
|
|
||||||
|
|
||||||
- At [here](apps/api/src/lib/services/handlers.ts), define how the service should start. It could be complex and based on `docker-compose` definitions.
|
|
||||||
|
|
||||||
> See `startUmamiService()` function as example.
|
|
||||||
|
|
||||||
- At [here](apps/api/src/routes/api/v1/services/handlers.ts), add the new start service process to `startService` function.
|
|
||||||
|
|
||||||
### Define the deletion process
|
|
||||||
|
|
||||||
[Here](apps/api/src/lib/services/common.ts) in `removeService` add the database deletion process.
|
|
||||||
|
|
||||||
### Custom logo
|
|
||||||
|
|
||||||
- At [here](apps/ui/src/lib/components/svg/services) add the service custom log as a Svelte component and export it [here](apps/ui/src/lib/components/svg/services/index.ts).
|
|
||||||
|
|
||||||
> SVG is recommended, but you can use PNG as well. It should have the `isAbsolute` variable with the suitable CSS classes, primarily for sizing and positioning.
|
|
||||||
|
|
||||||
- At [here](apps/ui/src/lib/components/svg/services/ServiceIcons.svelte) include the new logo with `isAbsolute` property.
|
|
||||||
|
|
||||||
- At [here](apps/ui/src/routes/services/[id]/_ServiceLinks.svelte) add links to the documentation of the service.
|
|
||||||
|
|
||||||
### Custom fields on the UI
|
|
||||||
By default the URL and name are shown on the UI. Everything else needs to be added [here](apps/ui/src/routes/services/[id]/_Services/_Services.svelte)
|
|
||||||
|
|
||||||
> If you need to show more details on the frontend, such as users/passwords, you need to add Svelte component [here](apps/ui/src/routes/services/[id]/_Services) with an underscore. For example, see other [here](apps/ui/src/routes/services/[id]/_Services/_Umami.svelte).
|
|
||||||
|
|
||||||
Good job! 👏
|
|
||||||
23
Dockerfile
23
Dockerfile
@@ -1,5 +1,4 @@
|
|||||||
ARG PNPM_VERSION=7.11.0
|
ARG PNPM_VERSION=7.11.0
|
||||||
ARG NPM_VERSION=8.19.1
|
|
||||||
|
|
||||||
FROM node:18-slim as build
|
FROM node:18-slim as build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -17,20 +16,26 @@ WORKDIR /app
|
|||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# https://download.docker.com/linux/static/stable/
|
||||||
|
ARG DOCKER_VERSION=20.10.18
|
||||||
|
# https://github.com/docker/compose/releases
|
||||||
|
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||||
|
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||||
|
# https://github.com/buildpacks/pack/releases
|
||||||
|
ARG PACK_VERSION=v0.27.0
|
||||||
|
|
||||||
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
||||||
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
RUN npm install -g npm@${PNPM_VERSION}
|
RUN npm install -g npm@${PNPM_VERSION}
|
||||||
|
|
||||||
RUN mkdir -p ~/.docker/cli-plugins/
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
# https://download.docker.com/linux/static/stable/
|
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-20.10.9 -o /usr/bin/docker
|
|
||||||
# https://github.com/docker/compose/releases
|
|
||||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
|
||||||
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-2.6.1 -o ~/.docker/cli-plugins/docker-compose
|
|
||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker
|
|
||||||
|
|
||||||
RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack)
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-$DOCKER_COMPOSE_VERSION -o ~/.docker/cli-plugins/docker-compose
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /usr/local/bin/pack
|
||||||
|
|
||||||
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
|
||||||
|
|
||||||
COPY --from=build /app/apps/api/build/ .
|
COPY --from=build /app/apps/api/build/ .
|
||||||
COPY --from=build /app/others/fluentbit/ ./fluentbit
|
COPY --from=build /app/others/fluentbit/ ./fluentbit
|
||||||
@@ -38,6 +43,8 @@ COPY --from=build /app/apps/ui/build/ ./public
|
|||||||
COPY --from=build /app/apps/api/prisma/ ./prisma
|
COPY --from=build /app/apps/api/prisma/ ./prisma
|
||||||
COPY --from=build /app/apps/api/package.json .
|
COPY --from=build /app/apps/api/package.json .
|
||||||
COPY --from=build /app/docker-compose.yaml .
|
COPY --from=build /app/docker-compose.yaml .
|
||||||
|
COPY --from=build /app/apps/api/tags.json .
|
||||||
|
COPY --from=build /app/apps/api/templates.json .
|
||||||
|
|
||||||
RUN pnpm install -p
|
RUN pnpm install -p
|
||||||
|
|
||||||
|
|||||||
31
Dockerfile-dev
Normal file
31
Dockerfile-dev
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM node:18-slim
|
||||||
|
ENV NODE_ENV development
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG PNPM_VERSION=7.11.0
|
||||||
|
ARG NPM_VERSION=8.19.1
|
||||||
|
# https://download.docker.com/linux/static/stable/
|
||||||
|
ARG DOCKER_VERSION=20.10.18
|
||||||
|
# https://github.com/docker/compose/releases
|
||||||
|
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
||||||
|
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||||
|
# https://github.com/buildpacks/pack/releases
|
||||||
|
ARG PACK_VERSION=v0.27.0
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
|
||||||
|
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3
|
||||||
|
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
|
||||||
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
RUN npm install -g npm@${PNPM_VERSION}
|
||||||
|
|
||||||
|
RUN mkdir -p ~/.docker/cli-plugins/
|
||||||
|
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-compose-linux-$DOCKER_COMPOSE_VERSION -o ~/.docker/cli-plugins/docker-compose
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /usr/local/bin/pack
|
||||||
|
|
||||||
|
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV CHECKPOINT_DISABLE=1
|
||||||
33
README.md
33
README.md
@@ -77,6 +77,7 @@ Deploy your resource to:
|
|||||||
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
<a href="https://redis.io"><svg style="width:40px;height:40px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ><defs ><path id="a" d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" /><path id="b" d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z" /></defs ><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)" ><use fill="#a41e11" xlink:href="#a" /><path d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z" fill="#d82c20" /><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z" fill="#a41e11" /><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff" ><path d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z" /><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g ><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z" fill="#ad2115" /></g ></svg ></a>
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
|
|
||||||
- [Appwrite](https://appwrite.io)
|
- [Appwrite](https://appwrite.io)
|
||||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
||||||
- [Ghost](https://ghost.org)
|
- [Ghost](https://ghost.org)
|
||||||
@@ -93,19 +94,39 @@ Deploy your resource to:
|
|||||||
- [Fider](https://fider.io)
|
- [Fider](https://fider.io)
|
||||||
- [Hasura](https://hasura.io)
|
- [Hasura](https://hasura.io)
|
||||||
- [GlitchTip](https://glitchtip.com)
|
- [GlitchTip](https://glitchtip.com)
|
||||||
|
- And more...
|
||||||
## Migration from v1
|
|
||||||
|
|
||||||
A fresh installation is necessary. v2 and v3 are not compatible with v1.
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
|
||||||
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
|
||||||
|
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||||
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
|
||||||
- Discord: [Invitation](https://coollabs.io/discord)
|
- Discord: [Invitation](https://coollabs.io/discord)
|
||||||
|
|
||||||
## Financial Contributors
|
---
|
||||||
|
|
||||||
|
## ⚗️ Expertise Contributions
|
||||||
|
|
||||||
|
Coolify is developed under the [Apache License](./LICENSE) and you can help to make it grow.
|
||||||
|
Our community will be glad to have you on board!
|
||||||
|
|
||||||
|
Learn how to contribute to Coolify as as ...
|
||||||
|
|
||||||
|
→ [👩🏾💻 Software developer](./CONTRIBUTION.md)
|
||||||
|
|
||||||
|
→ [🧑🏻🏫 Translator](./docs/contribution/Translating.md)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
→ 🧑🏽🎨 Designer
|
||||||
|
→ 🙋♀️ Community Managemer
|
||||||
|
→ 🧙🏻♂️ Text Content Creator
|
||||||
|
→ 👨🏼🎤 Video Content Creator
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💰 Financial Contributors
|
||||||
|
|
||||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
COOLIFY_APP_ID=local-dev
|
COOLIFY_APP_ID=local-dev
|
||||||
# 32 bits long secret key
|
# 32 bits long secret key
|
||||||
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
COOLIFY_SECRET_KEY=12341234123412341234123412341234
|
||||||
COOLIFY_DATABASE_URL=file:../db/dev.db
|
COOLIFY_DATABASE_URL=file:../db/dev.db
|
||||||
COOLIFY_SENTRY_DSN=
|
|
||||||
|
COOLIFY_IS_ON=docker
|
||||||
COOLIFY_IS_ON=docker
|
COOLIFY_WHITE_LABELED=false
|
||||||
COOLIFY_WHITE_LABELED=false
|
COOLIFY_WHITE_LABELED_ICON=
|
||||||
COOLIFY_WHITE_LABELED_ICON=
|
COOLIFY_AUTO_UPDATE=
|
||||||
COOLIFY_AUTO_UPDATE=
|
|
||||||
|
|||||||
BIN
apps/api/db/dev.db.bak
Normal file
BIN
apps/api/db/dev.db.bak
Normal file
Binary file not shown.
1
apps/api/devTags.json
Normal file
1
apps/api/devTags.json
Normal file
File diff suppressed because one or more lines are too long
3363
apps/api/devTemplates.yaml
Normal file
3363
apps/api/devTemplates.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"watch": ["src"],
|
"watch": [
|
||||||
"ignore": ["src/**/*.test.ts"],
|
"src"
|
||||||
"ext": "ts,mjs,json,graphql",
|
],
|
||||||
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\)` --minify=true --platform=node --outdir=build --format=cjs && node build",
|
"ignore": [
|
||||||
"legacyWatch": true
|
"src/**/*.test.ts"
|
||||||
}
|
],
|
||||||
|
"ext": "ts,mjs,json,graphql",
|
||||||
|
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\)` --platform=node --outdir=build --format=cjs && node build",
|
||||||
|
"legacyWatch": true
|
||||||
|
}
|
||||||
@@ -1,77 +1,85 @@
|
|||||||
{
|
{
|
||||||
"name": "api",
|
"name": "api",
|
||||||
"description": "Coolify's Fastify API",
|
"description": "Coolify's Fastify API",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"db:push": "prisma db push && prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:seed": "prisma db seed",
|
"db:push": "prisma db push && prisma generate",
|
||||||
"db:studio": "prisma studio",
|
"db:seed": "prisma db seed",
|
||||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
"db:studio": "prisma studio",
|
||||||
"dev": "nodemon",
|
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --minify=true --platform=node --outdir=build --format=cjs",
|
"dev": "nodemon",
|
||||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||||
"start": "NODE_ENV=production npx -y prisma migrate deploy && npx prisma generate && npx prisma db seed && node index.js"
|
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
||||||
},
|
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||||
"dependencies": {
|
},
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"dependencies": {
|
||||||
"@fastify/autoload": "5.4.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/cookie": "8.3.0",
|
"@fastify/autoload": "5.5.0",
|
||||||
"@fastify/cors": "8.1.0",
|
"@fastify/cookie": "8.3.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/cors": "8.2.0",
|
||||||
"@fastify/jwt": "6.3.2",
|
"@fastify/env": "4.1.0",
|
||||||
"@fastify/multipart": "7.2.0",
|
"@fastify/jwt": "6.3.3",
|
||||||
"@fastify/static": "6.5.0",
|
"@fastify/multipart": "7.3.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@fastify/static": "6.5.1",
|
||||||
"@ladjs/graceful": "3.0.2",
|
"@iarna/toml": "2.2.5",
|
||||||
"@prisma/client": "4.4.0",
|
"@ladjs/graceful": "3.0.2",
|
||||||
"axios": "0.27.2",
|
"@prisma/client": "4.6.1",
|
||||||
"bcryptjs": "2.4.3",
|
"@sentry/node": "7.21.1",
|
||||||
"bree": "9.1.2",
|
"@sentry/tracing": "7.21.1",
|
||||||
"cabin": "9.1.2",
|
"axe": "11.0.0",
|
||||||
"compare-versions": "5.0.1",
|
"bcryptjs": "2.4.3",
|
||||||
"csv-parse": "^5.3.0",
|
"bree": "9.1.2",
|
||||||
"csvtojson": "^2.0.10",
|
"cabin": "11.0.1",
|
||||||
"cuid": "2.1.8",
|
"compare-versions": "5.0.1",
|
||||||
"dayjs": "1.11.5",
|
"csv-parse": "5.3.2",
|
||||||
"dockerode": "3.3.4",
|
"csvtojson": "2.0.10",
|
||||||
"dotenv-extended": "2.9.0",
|
"cuid": "2.1.8",
|
||||||
"execa": "6.1.0",
|
"dayjs": "1.11.6",
|
||||||
"fastify": "4.7.0",
|
"dockerode": "3.3.4",
|
||||||
"fastify-plugin": "4.2.1",
|
"dotenv-extended": "2.9.0",
|
||||||
"generate-password": "1.7.0",
|
"execa": "6.1.0",
|
||||||
"got": "12.5.1",
|
"fastify": "4.10.2",
|
||||||
"is-ip": "5.0.0",
|
"fastify-plugin": "4.3.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"fastify-socket.io": "4.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"generate-password": "1.7.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"got": "12.5.3",
|
||||||
"node-forge": "1.3.1",
|
"is-ip": "5.0.0",
|
||||||
"node-os-utils": "1.3.7",
|
"is-port-reachable": "4.0.0",
|
||||||
"p-all": "4.0.0",
|
"js-yaml": "4.1.0",
|
||||||
"p-throttle": "5.0.0",
|
"jsonwebtoken": "8.5.1",
|
||||||
"public-ip": "6.0.1",
|
"minimist": "^1.2.7",
|
||||||
"pump": "^3.0.0",
|
"node-forge": "1.3.1",
|
||||||
"ssh-config": "4.1.6",
|
"node-os-utils": "1.3.7",
|
||||||
"strip-ansi": "7.0.1",
|
"p-all": "4.0.0",
|
||||||
"unique-names-generator": "4.7.1"
|
"p-throttle": "5.0.0",
|
||||||
},
|
"prisma": "4.6.1",
|
||||||
"devDependencies": {
|
"public-ip": "6.0.1",
|
||||||
"@types/node": "18.7.23",
|
"pump": "3.0.0",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"shell-quote": "^1.7.4",
|
||||||
"@typescript-eslint/eslint-plugin": "5.38.1",
|
"socket.io": "4.5.3",
|
||||||
"@typescript-eslint/parser": "5.38.1",
|
"ssh-config": "4.1.6",
|
||||||
"esbuild": "0.15.10",
|
"strip-ansi": "7.0.1",
|
||||||
"eslint": "8.23.0",
|
"unique-names-generator": "4.7.1"
|
||||||
"eslint-config-prettier": "8.5.0",
|
},
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"devDependencies": {
|
||||||
"nodemon": "2.0.20",
|
"@types/node": "18.11.9",
|
||||||
"prettier": "2.7.1",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"prisma": "4.4.0",
|
"@typescript-eslint/eslint-plugin": "5.44.0",
|
||||||
"rimraf": "3.0.2",
|
"@typescript-eslint/parser": "5.44.0",
|
||||||
"tsconfig-paths": "4.1.0",
|
"esbuild": "0.15.15",
|
||||||
"typescript": "4.8.4"
|
"eslint": "8.28.0",
|
||||||
},
|
"eslint-config-prettier": "8.5.0",
|
||||||
"prisma": {
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"seed": "node prisma/seed.js"
|
"nodemon": "2.0.20",
|
||||||
}
|
"prettier": "2.7.1",
|
||||||
}
|
"rimraf": "3.0.2",
|
||||||
|
"tsconfig-paths": "4.1.0",
|
||||||
|
"types-fastify-socket.io": "0.0.1",
|
||||||
|
"typescript": "4.9.3"
|
||||||
|
},
|
||||||
|
"prisma": {
|
||||||
|
"seed": "node prisma/seed.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeFile" TEXT;
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeFileLocation" TEXT;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerComposeConfiguration" TEXT;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ServiceSetting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ServiceSetting_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ServiceSetting_serviceId_name_key" ON "ServiceSetting"("serviceId", "name");
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ServicePersistentStorage" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"path" TEXT NOT NULL,
|
||||||
|
"volumeName" TEXT,
|
||||||
|
"predefined" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"containerId" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ServicePersistentStorage_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ServicePersistentStorage" ("createdAt", "id", "path", "serviceId", "updatedAt") SELECT "createdAt", "id", "path", "serviceId", "updatedAt" FROM "ServicePersistentStorage";
|
||||||
|
DROP TABLE "ServicePersistentStorage";
|
||||||
|
ALTER TABLE "new_ServicePersistentStorage" RENAME TO "ServicePersistentStorage";
|
||||||
|
CREATE UNIQUE INDEX "ServicePersistentStorage_serviceId_path_key" ON "ServicePersistentStorage"("serviceId", "path");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `variableName` to the `ServiceSetting` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ServiceSetting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"variableName" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ServiceSetting_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ServiceSetting" ("createdAt", "id", "name", "serviceId", "updatedAt", "value") SELECT "createdAt", "id", "name", "serviceId", "updatedAt", "value" FROM "ServiceSetting";
|
||||||
|
DROP TABLE "ServiceSetting";
|
||||||
|
ALTER TABLE "new_ServiceSetting" RENAME TO "ServiceSetting";
|
||||||
|
CREATE UNIQUE INDEX "ServiceSetting_serviceId_name_key" ON "ServiceSetting"("serviceId", "name");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Service" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"exposePort" INTEGER,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"type" TEXT,
|
||||||
|
"version" TEXT,
|
||||||
|
"templateVersion" TEXT NOT NULL DEFAULT '0.0.0',
|
||||||
|
"destinationDockerId" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Service" ("createdAt", "destinationDockerId", "dualCerts", "exposePort", "fqdn", "id", "name", "type", "updatedAt", "version") SELECT "createdAt", "destinationDockerId", "dualCerts", "exposePort", "fqdn", "id", "name", "type", "updatedAt", "version" FROM "Service";
|
||||||
|
DROP TABLE "Service";
|
||||||
|
ALTER TABLE "new_Service" RENAME TO "Service";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[serviceId,containerId,path]` on the table `ServicePersistentStorage` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "ServicePersistentStorage_serviceId_path_key";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "ServicePersistentStorage_serviceId_containerId_path_key" ON "ServicePersistentStorage"("serviceId", "containerId", "path");
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Wordpress" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"extraConfig" TEXT,
|
||||||
|
"tablePrefix" TEXT,
|
||||||
|
"ownMysql" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"mysqlHost" TEXT,
|
||||||
|
"mysqlPort" INTEGER,
|
||||||
|
"mysqlUser" TEXT,
|
||||||
|
"mysqlPassword" TEXT,
|
||||||
|
"mysqlRootUser" TEXT,
|
||||||
|
"mysqlRootUserPassword" TEXT,
|
||||||
|
"mysqlDatabase" TEXT,
|
||||||
|
"mysqlPublicPort" INTEGER,
|
||||||
|
"ftpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"ftpUser" TEXT,
|
||||||
|
"ftpPassword" TEXT,
|
||||||
|
"ftpPublicPort" INTEGER,
|
||||||
|
"ftpHostKey" TEXT,
|
||||||
|
"ftpHostKeyPrivate" TEXT,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlHost", "mysqlPassword", "mysqlPort", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "ownMysql", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "ftpEnabled", "ftpHostKey", "ftpHostKeyPrivate", "ftpPassword", "ftpPublicPort", "ftpUser", "id", "mysqlDatabase", "mysqlHost", "mysqlPassword", "mysqlPort", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "ownMysql", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress";
|
||||||
|
DROP TABLE "Wordpress";
|
||||||
|
ALTER TABLE "new_Wordpress" RENAME TO "Wordpress";
|
||||||
|
CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "proxyDefaultRedirect" TEXT;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"proxyPassword" TEXT NOT NULL,
|
||||||
|
"proxyUser" TEXT NOT NULL,
|
||||||
|
"proxyHash" TEXT,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"DNSServers" TEXT,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "DNSServers", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
CREATE TABLE "new_ApplicationPersistentStorage" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"path" TEXT NOT NULL,
|
||||||
|
"oldPath" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationPersistentStorage_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationPersistentStorage" ("applicationId", "createdAt", "id", "path", "updatedAt") SELECT "applicationId", "createdAt", "id", "path", "updatedAt" FROM "ApplicationPersistentStorage";
|
||||||
|
DROP TABLE "ApplicationPersistentStorage";
|
||||||
|
ALTER TABLE "new_ApplicationPersistentStorage" RENAME TO "ApplicationPersistentStorage";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationPersistentStorage_applicationId_path_key" ON "ApplicationPersistentStorage"("applicationId", "path");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `proxyHash` on the `Setting` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `proxyPassword` on the `Setting` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `proxyUser` on the `Setting` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"DNSServers" TEXT,
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "DockerRegistry" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"username" TEXT,
|
||||||
|
"password" TEXT,
|
||||||
|
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"teamId" TEXT,
|
||||||
|
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Application" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"repository" TEXT,
|
||||||
|
"configHash" TEXT,
|
||||||
|
"branch" TEXT,
|
||||||
|
"buildPack" TEXT,
|
||||||
|
"projectId" INTEGER,
|
||||||
|
"port" INTEGER,
|
||||||
|
"exposePort" INTEGER,
|
||||||
|
"installCommand" TEXT,
|
||||||
|
"buildCommand" TEXT,
|
||||||
|
"startCommand" TEXT,
|
||||||
|
"baseDirectory" TEXT,
|
||||||
|
"publishDirectory" TEXT,
|
||||||
|
"deploymentType" TEXT,
|
||||||
|
"phpModules" TEXT,
|
||||||
|
"pythonWSGI" TEXT,
|
||||||
|
"pythonModule" TEXT,
|
||||||
|
"pythonVariable" TEXT,
|
||||||
|
"dockerFileLocation" TEXT,
|
||||||
|
"denoMainFile" TEXT,
|
||||||
|
"denoOptions" TEXT,
|
||||||
|
"dockerComposeFile" TEXT,
|
||||||
|
"dockerComposeFileLocation" TEXT,
|
||||||
|
"dockerComposeConfiguration" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"destinationDockerId" TEXT,
|
||||||
|
"gitSourceId" TEXT,
|
||||||
|
"baseImage" TEXT,
|
||||||
|
"baseBuildImage" TEXT,
|
||||||
|
"dockerRegistryId" TEXT NOT NULL DEFAULT '0',
|
||||||
|
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "exposePort", "fqdn", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "exposePort", "fqdn", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
|
||||||
|
DROP TABLE "Application";
|
||||||
|
ALTER TABLE "new_Application" RENAME TO "Application";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"DNSServers" TEXT,
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"DNSServers" TEXT,
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", coalesce("isAPIDebuggingEnabled", false) AS "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
CREATE TABLE "new_GlitchTip" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"postgresqlUser" TEXT NOT NULL,
|
||||||
|
"postgresqlPassword" TEXT NOT NULL,
|
||||||
|
"postgresqlDatabase" TEXT NOT NULL,
|
||||||
|
"postgresqlPublicPort" INTEGER,
|
||||||
|
"secretKeyBase" TEXT,
|
||||||
|
"defaultEmail" TEXT NOT NULL,
|
||||||
|
"defaultUsername" TEXT NOT NULL,
|
||||||
|
"defaultPassword" TEXT NOT NULL,
|
||||||
|
"defaultEmailFrom" TEXT NOT NULL DEFAULT 'glitchtip@domain.tdl',
|
||||||
|
"emailSmtpHost" TEXT DEFAULT 'domain.tdl',
|
||||||
|
"emailSmtpPort" INTEGER DEFAULT 25,
|
||||||
|
"emailSmtpUser" TEXT,
|
||||||
|
"emailSmtpPassword" TEXT,
|
||||||
|
"emailSmtpUseTls" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"emailSmtpUseSsl" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"emailBackend" TEXT,
|
||||||
|
"mailgunApiKey" TEXT,
|
||||||
|
"sendgridApiKey" TEXT,
|
||||||
|
"enableOpenUserRegistration" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"serviceId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "GlitchTip_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_GlitchTip" ("createdAt", "defaultEmail", "defaultEmailFrom", "defaultPassword", "defaultUsername", "emailBackend", "emailSmtpHost", "emailSmtpPassword", "emailSmtpPort", "emailSmtpUseSsl", "emailSmtpUseTls", "emailSmtpUser", "enableOpenUserRegistration", "id", "mailgunApiKey", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "sendgridApiKey", "serviceId", "updatedAt") SELECT "createdAt", "defaultEmail", "defaultEmailFrom", "defaultPassword", "defaultUsername", "emailBackend", "emailSmtpHost", "emailSmtpPassword", "emailSmtpPort", coalesce("emailSmtpUseSsl", false) AS "emailSmtpUseSsl", coalesce("emailSmtpUseTls", false) AS "emailSmtpUseTls", "emailSmtpUser", "enableOpenUserRegistration", "id", "mailgunApiKey", "postgresqlDatabase", "postgresqlPassword", "postgresqlPublicPort", "postgresqlUser", "secretKeyBase", "sendgridApiKey", "serviceId", "updatedAt" FROM "GlitchTip";
|
||||||
|
DROP TABLE "GlitchTip";
|
||||||
|
ALTER TABLE "new_GlitchTip" RENAME TO "GlitchTip";
|
||||||
|
CREATE UNIQUE INDEX "GlitchTip_serviceId_key" ON "GlitchTip"("serviceId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Setting" ADD COLUMN "sentryDSN" TEXT;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"DNSServers" TEXT NOT NULL DEFAULT '1.1.1.1,8.8.8.8',
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"sentryDSN" TEXT,
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "sentryDSN", "updatedAt") SELECT coalesce("DNSServers", '1.1.1.1,8.8.8.8') AS "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "proxyDefaultRedirect", "sentryDSN", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Setting" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"minPort" INTEGER NOT NULL DEFAULT 9000,
|
||||||
|
"maxPort" INTEGER NOT NULL DEFAULT 9100,
|
||||||
|
"DNSServers" TEXT NOT NULL DEFAULT '1.1.1.1,8.8.8.8',
|
||||||
|
"ipv4" TEXT,
|
||||||
|
"ipv6" TEXT,
|
||||||
|
"arch" TEXT,
|
||||||
|
"concurrentBuilds" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"applicationStoragePathMigrationFinished" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"numberOfDockerImagesKeptLocally" INTEGER NOT NULL DEFAULT 3,
|
||||||
|
"proxyDefaultRedirect" TEXT,
|
||||||
|
"doNotTrack" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"sentryDSN" TEXT,
|
||||||
|
"previewSeparator" TEXT NOT NULL DEFAULT '.',
|
||||||
|
"isAPIDebuggingEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isTraefikUsed" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Setting" ("DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", "numberOfDockerImagesKeptLocally", "proxyDefaultRedirect", "sentryDSN", "updatedAt") SELECT "DNSServers", "applicationStoragePathMigrationFinished", "arch", "concurrentBuilds", "createdAt", "doNotTrack", "dualCerts", "fqdn", "id", "ipv4", "ipv6", "isAPIDebuggingEnabled", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "isTraefikUsed", "maxPort", "minPort", 3, "proxyDefaultRedirect", "sentryDSN", "updatedAt" FROM "Setting";
|
||||||
|
DROP TABLE "Setting";
|
||||||
|
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||||
|
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "gitCommitHash" TEXT;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `isSystemWide` on the `DockerRegistry` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_DockerRegistry" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"url" TEXT NOT NULL,
|
||||||
|
"username" TEXT,
|
||||||
|
"password" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"teamId" TEXT,
|
||||||
|
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_DockerRegistry" ("createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username") SELECT "createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username" FROM "DockerRegistry";
|
||||||
|
DROP TABLE "DockerRegistry";
|
||||||
|
ALTER TABLE "new_DockerRegistry" RENAME TO "DockerRegistry";
|
||||||
|
CREATE TABLE "new_Application" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"fqdn" TEXT,
|
||||||
|
"repository" TEXT,
|
||||||
|
"configHash" TEXT,
|
||||||
|
"branch" TEXT,
|
||||||
|
"buildPack" TEXT,
|
||||||
|
"projectId" INTEGER,
|
||||||
|
"port" INTEGER,
|
||||||
|
"exposePort" INTEGER,
|
||||||
|
"installCommand" TEXT,
|
||||||
|
"buildCommand" TEXT,
|
||||||
|
"startCommand" TEXT,
|
||||||
|
"baseDirectory" TEXT,
|
||||||
|
"publishDirectory" TEXT,
|
||||||
|
"deploymentType" TEXT,
|
||||||
|
"phpModules" TEXT,
|
||||||
|
"pythonWSGI" TEXT,
|
||||||
|
"pythonModule" TEXT,
|
||||||
|
"pythonVariable" TEXT,
|
||||||
|
"dockerFileLocation" TEXT,
|
||||||
|
"denoMainFile" TEXT,
|
||||||
|
"denoOptions" TEXT,
|
||||||
|
"dockerComposeFile" TEXT,
|
||||||
|
"dockerComposeFileLocation" TEXT,
|
||||||
|
"dockerComposeConfiguration" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"destinationDockerId" TEXT,
|
||||||
|
"gitSourceId" TEXT,
|
||||||
|
"gitCommitHash" TEXT,
|
||||||
|
"baseImage" TEXT,
|
||||||
|
"baseBuildImage" TEXT,
|
||||||
|
"dockerRegistryId" TEXT,
|
||||||
|
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
|
||||||
|
DROP TABLE "Application";
|
||||||
|
ALTER TABLE "new_Application" RENAME TO "Application";
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "simpleDockerfile" TEXT;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "dockerRegistryImageName" TEXT;
|
||||||
@@ -19,26 +19,29 @@ model Certificate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Setting {
|
model Setting {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
fqdn String? @unique
|
fqdn String? @unique
|
||||||
isAPIDebuggingEnabled Boolean? @default(false)
|
dualCerts Boolean @default(false)
|
||||||
isRegistrationEnabled Boolean @default(false)
|
minPort Int @default(9000)
|
||||||
dualCerts Boolean @default(false)
|
maxPort Int @default(9100)
|
||||||
minPort Int @default(9000)
|
DNSServers String @default("1.1.1.1,8.8.8.8")
|
||||||
maxPort Int @default(9100)
|
ipv4 String?
|
||||||
proxyPassword String
|
ipv6 String?
|
||||||
proxyUser String
|
arch String?
|
||||||
proxyHash String?
|
concurrentBuilds Int @default(1)
|
||||||
isAutoUpdateEnabled Boolean @default(false)
|
applicationStoragePathMigrationFinished Boolean @default(false)
|
||||||
isDNSCheckEnabled Boolean @default(true)
|
numberOfDockerImagesKeptLocally Int @default(3)
|
||||||
DNSServers String?
|
proxyDefaultRedirect String?
|
||||||
isTraefikUsed Boolean @default(true)
|
doNotTrack Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
sentryDSN String?
|
||||||
updatedAt DateTime @updatedAt
|
previewSeparator String @default(".")
|
||||||
ipv4 String?
|
isAPIDebuggingEnabled Boolean @default(false)
|
||||||
ipv6 String?
|
isRegistrationEnabled Boolean @default(true)
|
||||||
arch String?
|
isAutoUpdateEnabled Boolean @default(false)
|
||||||
concurrentBuilds Int @default(1)
|
isDNSCheckEnabled Boolean @default(true)
|
||||||
|
isTraefikUsed Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
@@ -81,6 +84,7 @@ model Team {
|
|||||||
service Service[]
|
service Service[]
|
||||||
users User[]
|
users User[]
|
||||||
certificate Certificate[]
|
certificate Certificate[]
|
||||||
|
dockerRegistry DockerRegistry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model TeamInvitation {
|
model TeamInvitation {
|
||||||
@@ -94,43 +98,52 @@ model TeamInvitation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Application {
|
model Application {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
fqdn String?
|
fqdn String?
|
||||||
repository String?
|
repository String?
|
||||||
configHash String?
|
configHash String?
|
||||||
branch String?
|
branch String?
|
||||||
buildPack String?
|
buildPack String?
|
||||||
projectId Int?
|
projectId Int?
|
||||||
port Int?
|
port Int?
|
||||||
exposePort Int?
|
exposePort Int?
|
||||||
installCommand String?
|
installCommand String?
|
||||||
buildCommand String?
|
buildCommand String?
|
||||||
startCommand String?
|
startCommand String?
|
||||||
baseDirectory String?
|
baseDirectory String?
|
||||||
publishDirectory String?
|
publishDirectory String?
|
||||||
deploymentType String?
|
deploymentType String?
|
||||||
phpModules String?
|
phpModules String?
|
||||||
pythonWSGI String?
|
pythonWSGI String?
|
||||||
pythonModule String?
|
pythonModule String?
|
||||||
pythonVariable String?
|
pythonVariable String?
|
||||||
dockerFileLocation String?
|
dockerFileLocation String?
|
||||||
denoMainFile String?
|
denoMainFile String?
|
||||||
denoOptions String?
|
denoOptions String?
|
||||||
createdAt DateTime @default(now())
|
dockerComposeFile String?
|
||||||
updatedAt DateTime @updatedAt
|
dockerComposeFileLocation String?
|
||||||
destinationDockerId String?
|
dockerComposeConfiguration String?
|
||||||
gitSourceId String?
|
createdAt DateTime @default(now())
|
||||||
baseImage String?
|
updatedAt DateTime @updatedAt
|
||||||
baseBuildImage String?
|
destinationDockerId String?
|
||||||
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
gitSourceId String?
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
gitCommitHash String?
|
||||||
persistentStorage ApplicationPersistentStorage[]
|
baseImage String?
|
||||||
settings ApplicationSettings?
|
baseBuildImage String?
|
||||||
secrets Secret[]
|
settings ApplicationSettings?
|
||||||
teams Team[]
|
dockerRegistryId String?
|
||||||
connectedDatabase ApplicationConnectedDatabase?
|
dockerRegistryImageName String?
|
||||||
previewApplication PreviewApplication[]
|
simpleDockerfile String?
|
||||||
|
|
||||||
|
persistentStorage ApplicationPersistentStorage[]
|
||||||
|
secrets Secret[]
|
||||||
|
teams Team[]
|
||||||
|
connectedDatabase ApplicationConnectedDatabase?
|
||||||
|
previewApplication PreviewApplication[]
|
||||||
|
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
|
||||||
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
|
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
model PreviewApplication {
|
model PreviewApplication {
|
||||||
@@ -182,6 +195,7 @@ model ApplicationPersistentStorage {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
applicationId String
|
applicationId String
|
||||||
path String
|
path String
|
||||||
|
oldPath Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
@@ -190,14 +204,17 @@ model ApplicationPersistentStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ServicePersistentStorage {
|
model ServicePersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
serviceId String
|
serviceId String
|
||||||
path String
|
path String
|
||||||
createdAt DateTime @default(now())
|
volumeName String?
|
||||||
updatedAt DateTime @updatedAt
|
predefined Boolean @default(false)
|
||||||
service Service @relation(fields: [serviceId], references: [id])
|
containerId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
|
||||||
@@unique([serviceId, path])
|
@@unique([serviceId, containerId, path])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
@@ -287,6 +304,19 @@ model SshKey {
|
|||||||
destinationDocker DestinationDocker[]
|
destinationDocker DestinationDocker[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model DockerRegistry {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
url String
|
||||||
|
username String?
|
||||||
|
password String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
teamId String?
|
||||||
|
team Team? @relation(fields: [teamId], references: [id])
|
||||||
|
application Application[]
|
||||||
|
}
|
||||||
|
|
||||||
model GitSource {
|
model GitSource {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
@@ -389,12 +419,14 @@ model Service {
|
|||||||
dualCerts Boolean @default(false)
|
dualCerts Boolean @default(false)
|
||||||
type String?
|
type String?
|
||||||
version String?
|
version String?
|
||||||
|
templateVersion String @default("0.0.0")
|
||||||
destinationDockerId String?
|
destinationDockerId String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||||
persistentStorage ServicePersistentStorage[]
|
persistentStorage ServicePersistentStorage[]
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
|
serviceSetting ServiceSetting[]
|
||||||
teams Team[]
|
teams Team[]
|
||||||
|
|
||||||
fider Fider?
|
fider Fider?
|
||||||
@@ -414,6 +446,19 @@ model Service {
|
|||||||
taiga Taiga?
|
taiga Taiga?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model ServiceSetting {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
serviceId String
|
||||||
|
name String
|
||||||
|
value String
|
||||||
|
variableName String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
service Service @relation(fields: [serviceId], references: [id])
|
||||||
|
|
||||||
|
@@unique([serviceId, name])
|
||||||
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
email String?
|
email String?
|
||||||
@@ -459,10 +504,10 @@ model Wordpress {
|
|||||||
ownMysql Boolean @default(false)
|
ownMysql Boolean @default(false)
|
||||||
mysqlHost String?
|
mysqlHost String?
|
||||||
mysqlPort Int?
|
mysqlPort Int?
|
||||||
mysqlUser String
|
mysqlUser String?
|
||||||
mysqlPassword String
|
mysqlPassword String?
|
||||||
mysqlRootUser String
|
mysqlRootUser String?
|
||||||
mysqlRootUserPassword String
|
mysqlRootUserPassword String?
|
||||||
mysqlDatabase String?
|
mysqlDatabase String?
|
||||||
mysqlPublicPort Int?
|
mysqlPublicPort Int?
|
||||||
ftpEnabled Boolean @default(false)
|
ftpEnabled Boolean @default(false)
|
||||||
@@ -602,8 +647,8 @@ model GlitchTip {
|
|||||||
emailSmtpPort Int? @default(25)
|
emailSmtpPort Int? @default(25)
|
||||||
emailSmtpUser String?
|
emailSmtpUser String?
|
||||||
emailSmtpPassword String?
|
emailSmtpPassword String?
|
||||||
emailSmtpUseTls Boolean? @default(false)
|
emailSmtpUseTls Boolean @default(false)
|
||||||
emailSmtpUseSsl Boolean? @default(false)
|
emailSmtpUseSsl Boolean @default(false)
|
||||||
emailBackend String?
|
emailBackend String?
|
||||||
mailgunApiKey String?
|
mailgunApiKey String?
|
||||||
sendgridApiKey String?
|
sendgridApiKey String?
|
||||||
|
|||||||
@@ -1,18 +1,8 @@
|
|||||||
const dotEnvExtended = require('dotenv-extended');
|
const dotEnvExtended = require('dotenv-extended');
|
||||||
dotEnvExtended.load();
|
dotEnvExtended.load();
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const generator = require('generate-password');
|
|
||||||
const cuid = require('cuid');
|
|
||||||
const { PrismaClient } = require('@prisma/client');
|
const { PrismaClient } = require('@prisma/client');
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
function generatePassword(length = 24) {
|
|
||||||
return generator.generate({
|
|
||||||
length,
|
|
||||||
numbers: true,
|
|
||||||
strict: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
@@ -21,11 +11,8 @@ async function main() {
|
|||||||
if (!settingsFound) {
|
if (!settingsFound) {
|
||||||
await prisma.setting.create({
|
await prisma.setting.create({
|
||||||
data: {
|
data: {
|
||||||
isRegistrationEnabled: true,
|
id: '0',
|
||||||
proxyPassword: encrypt(generatePassword()),
|
|
||||||
proxyUser: cuid(),
|
|
||||||
arch: process.arch,
|
arch: process.arch,
|
||||||
DNSServers: '1.1.1.1,8.8.8.8'
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -34,11 +21,11 @@ async function main() {
|
|||||||
id: settingsFound.id
|
id: settingsFound.id
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
isTraefikUsed: true,
|
id: '0'
|
||||||
proxyHash: null
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Create local docker engine
|
||||||
const localDocker = await prisma.destinationDocker.findFirst({
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
where: { engine: '/var/run/docker.sock' }
|
where: { engine: '/var/run/docker.sock' }
|
||||||
});
|
});
|
||||||
@@ -55,23 +42,18 @@ async function main() {
|
|||||||
|
|
||||||
// Set auto-update based on env variable
|
// Set auto-update based on env variable
|
||||||
const isAutoUpdateEnabled = process.env['COOLIFY_AUTO_UPDATE'] === 'true';
|
const isAutoUpdateEnabled = process.env['COOLIFY_AUTO_UPDATE'] === 'true';
|
||||||
const settings = await prisma.setting.findFirst({});
|
await prisma.setting.update({
|
||||||
if (settings) {
|
where: {
|
||||||
await prisma.setting.update({
|
id: '0'
|
||||||
where: {
|
},
|
||||||
id: settings.id
|
data: {
|
||||||
},
|
isAutoUpdateEnabled
|
||||||
data: {
|
}
|
||||||
isAutoUpdateEnabled
|
});
|
||||||
}
|
// Create public github source
|
||||||
});
|
|
||||||
}
|
|
||||||
const github = await prisma.gitSource.findFirst({
|
const github = await prisma.gitSource.findFirst({
|
||||||
where: { htmlUrl: 'https://github.com', forPublic: true }
|
where: { htmlUrl: 'https://github.com', forPublic: true }
|
||||||
});
|
});
|
||||||
const gitlab = await prisma.gitSource.findFirst({
|
|
||||||
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
|
||||||
});
|
|
||||||
if (!github) {
|
if (!github) {
|
||||||
await prisma.gitSource.create({
|
await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -83,6 +65,10 @@ async function main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Create public gitlab source
|
||||||
|
const gitlab = await prisma.gitSource.findFirst({
|
||||||
|
where: { htmlUrl: 'https://gitlab.com', forPublic: true }
|
||||||
|
});
|
||||||
if (!gitlab) {
|
if (!gitlab) {
|
||||||
await prisma.gitSource.create({
|
await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
67
apps/api/scripts/generateTags.mjs
Normal file
67
apps/api/scripts/generateTags.mjs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import got from 'got';
|
||||||
|
|
||||||
|
const repositories = [];
|
||||||
|
const templates = await fs.readFile('./apps/api/devTemplates.yaml', 'utf8');
|
||||||
|
const devTemplates = yaml.load(templates);
|
||||||
|
for (const template of devTemplates) {
|
||||||
|
let image = template.services['$$id'].image.replaceAll(':$$core_version', '');
|
||||||
|
if (!image.includes('/')) {
|
||||||
|
image = `library/${image}`;
|
||||||
|
}
|
||||||
|
repositories.push({ image, name: template.type });
|
||||||
|
}
|
||||||
|
const services = []
|
||||||
|
const numberOfTags = 30;
|
||||||
|
// const semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/g)
|
||||||
|
for (const repository of repositories) {
|
||||||
|
console.log('Querying', repository.name, 'at', repository.image);
|
||||||
|
let semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/g)
|
||||||
|
if (repository.name.startsWith('wordpress')) {
|
||||||
|
semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-php(0|[1-9]\d*)$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('minio')) {
|
||||||
|
semverRegex = new RegExp(/^RELEASE.*$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('fider')) {
|
||||||
|
semverRegex = new RegExp(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-([0-9]+)$/g)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('searxng')) {
|
||||||
|
semverRegex = new RegExp(/^\d{4}[\.\-](0?[1-9]|[12][0-9]|3[01])[\.\-](0?[1-9]|1[012]).*$/)
|
||||||
|
}
|
||||||
|
if (repository.name.startsWith('umami')) {
|
||||||
|
semverRegex = new RegExp(/^postgresql-v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-([0-9]+)$/g)
|
||||||
|
}
|
||||||
|
if (repository.image.includes('ghcr.io')) {
|
||||||
|
const { execaCommand } = await import('execa');
|
||||||
|
const { stdout } = await execaCommand(`docker run --rm quay.io/skopeo/stable list-tags docker://${repository.image}`);
|
||||||
|
if (stdout) {
|
||||||
|
const json = JSON.parse(stdout);
|
||||||
|
const semverTags = json.Tags.filter((tag) => semverRegex.test(tag))
|
||||||
|
let tags = semverTags.length > 10 ? semverTags.sort().reverse().slice(0, numberOfTags) : json.Tags.sort().reverse().slice(0, numberOfTags)
|
||||||
|
if (!tags.includes('latest')) {
|
||||||
|
tags.push('latest')
|
||||||
|
}
|
||||||
|
services.push({ name: repository.name, image: repository.image, tags })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { token } = await got.get(`https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository.image}:pull`).json()
|
||||||
|
let data = await got.get(`https://registry-1.docker.io/v2/${repository.image}/tags/list`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}).json()
|
||||||
|
const semverTags = data.tags.filter((tag) => semverRegex.test(tag))
|
||||||
|
let tags = semverTags.length > 10 ? semverTags.sort().reverse().slice(0, numberOfTags) : data.tags.sort().reverse().slice(0, numberOfTags)
|
||||||
|
if (!tags.includes('latest')) {
|
||||||
|
tags.push('latest')
|
||||||
|
}
|
||||||
|
services.push({
|
||||||
|
name: repository.name,
|
||||||
|
image: repository.image,
|
||||||
|
tags
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fs.writeFile('./apps/api/devTags.json', JSON.stringify(services));
|
||||||
@@ -6,17 +6,26 @@ import cookie from '@fastify/cookie';
|
|||||||
import multipart from '@fastify/multipart';
|
import multipart from '@fastify/multipart';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import { asyncExecShell, createRemoteEngineConfiguration, getDomain, isDev, listSettings, prisma, version } from './lib/common';
|
import socketIO from 'fastify-socket.io'
|
||||||
|
import socketIOServer from './realtime'
|
||||||
|
|
||||||
|
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
import { scheduler } from './lib/scheduler';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import Graceful from '@ladjs/graceful'
|
import Graceful from '@ladjs/graceful'
|
||||||
|
import yaml from 'js-yaml'
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||||
|
import { checkContainer } from './lib/docker';
|
||||||
|
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||||
|
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
config: {
|
config: {
|
||||||
COOLIFY_APP_ID: string,
|
COOLIFY_APP_ID: string,
|
||||||
COOLIFY_SECRET_KEY: string,
|
COOLIFY_SECRET_KEY: string,
|
||||||
COOLIFY_DATABASE_URL: string,
|
COOLIFY_DATABASE_URL: string,
|
||||||
COOLIFY_SENTRY_DSN: string,
|
|
||||||
COOLIFY_IS_ON: string,
|
COOLIFY_IS_ON: string,
|
||||||
COOLIFY_WHITE_LABELED: string,
|
COOLIFY_WHITE_LABELED: string,
|
||||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
COOLIFY_WHITE_LABELED_ICON: string | null,
|
||||||
@@ -27,7 +36,9 @@ declare module 'fastify' {
|
|||||||
|
|
||||||
const port = isDev ? 3001 : 3000;
|
const port = isDev ? 3001 : 3000;
|
||||||
const host = '0.0.0.0';
|
const host = '0.0.0.0';
|
||||||
prisma.setting.findFirst().then(async (settings) => {
|
|
||||||
|
(async () => {
|
||||||
|
const settings = await prisma.setting.findFirst()
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
logger: settings?.isAPIDebuggingEnabled || false,
|
logger: settings?.isAPIDebuggingEnabled || false,
|
||||||
trustProxy: true
|
trustProxy: true
|
||||||
@@ -47,10 +58,6 @@ prisma.setting.findFirst().then(async (settings) => {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'file:../db/dev.db'
|
default: 'file:../db/dev.db'
|
||||||
},
|
},
|
||||||
COOLIFY_SENTRY_DSN: {
|
|
||||||
type: 'string',
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
COOLIFY_IS_ON: {
|
COOLIFY_IS_ON: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'docker'
|
default: 'docker'
|
||||||
@@ -70,7 +77,6 @@ prisma.setting.findFirst().then(async (settings) => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
schema,
|
schema,
|
||||||
dotenv: true
|
dotenv: true
|
||||||
@@ -99,30 +105,40 @@ prisma.setting.findFirst().then(async (settings) => {
|
|||||||
});
|
});
|
||||||
fastify.register(cookie)
|
fastify.register(cookie)
|
||||||
fastify.register(cors);
|
fastify.register(cors);
|
||||||
fastify.addHook('onRequest', async (request, reply) => {
|
fastify.register(socketIO, {
|
||||||
let allowedList = ['coolify:3000'];
|
cors: {
|
||||||
const { ipv4, ipv6, fqdn } = await prisma.setting.findFirst({})
|
origin: isDev ? "*" : ''
|
||||||
|
|
||||||
ipv4 && allowedList.push(`${ipv4}:3000`);
|
|
||||||
ipv6 && allowedList.push(ipv6);
|
|
||||||
fqdn && allowedList.push(getDomain(fqdn));
|
|
||||||
isDev && allowedList.push('localhost:3000') && allowedList.push('localhost:3001') && allowedList.push('host.docker.internal:3001');
|
|
||||||
const remotes = await prisma.destinationDocker.findMany({ where: { remoteEngine: true, remoteVerified: true } })
|
|
||||||
if (remotes.length > 0) {
|
|
||||||
remotes.forEach(remote => {
|
|
||||||
allowedList.push(`${remote.remoteIpAddress}:3000`);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!allowedList.includes(request.headers.host)) {
|
|
||||||
// console.log('not allowed', request.headers.host)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
fastify.listen({ port, host }, async (err: any, address: any) => {
|
// To detect allowed origins
|
||||||
if (err) {
|
// fastify.addHook('onRequest', async (request, reply) => {
|
||||||
console.error(err);
|
// console.log(request.headers.host)
|
||||||
process.exit(1);
|
// let allowedList = ['coolify:3000'];
|
||||||
}
|
// const { ipv4, ipv6, fqdn } = await prisma.setting.findFirst({})
|
||||||
|
|
||||||
|
// ipv4 && allowedList.push(`${ipv4}:3000`);
|
||||||
|
// ipv6 && allowedList.push(ipv6);
|
||||||
|
// fqdn && allowedList.push(getDomain(fqdn));
|
||||||
|
// isDev && allowedList.push('localhost:3000') && allowedList.push('localhost:3001') && allowedList.push('host.docker.internal:3001');
|
||||||
|
// const remotes = await prisma.destinationDocker.findMany({ where: { remoteEngine: true, remoteVerified: true } })
|
||||||
|
// if (remotes.length > 0) {
|
||||||
|
// remotes.forEach(remote => {
|
||||||
|
// allowedList.push(`${remote.remoteIpAddress}:3000`);
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// if (!allowedList.includes(request.headers.host)) {
|
||||||
|
// // console.log('not allowed', request.headers.host)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fastify.listen({ port, host })
|
||||||
|
await socketIOServer(fastify)
|
||||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||||
|
|
||||||
|
migrateServicesToNewTemplate();
|
||||||
|
await migrateApplicationPersistentStorage();
|
||||||
await initServer();
|
await initServer();
|
||||||
|
|
||||||
const graceful = new Graceful({ brees: [scheduler] });
|
const graceful = new Graceful({ brees: [scheduler] });
|
||||||
@@ -132,38 +148,54 @@ prisma.setting.findFirst().then(async (settings) => {
|
|||||||
if (!scheduler.workers.has('deployApplication')) {
|
if (!scheduler.workers.has('deployApplication')) {
|
||||||
scheduler.run('deployApplication');
|
scheduler.run('deployApplication');
|
||||||
}
|
}
|
||||||
if (!scheduler.workers.has('infrastructure')) {
|
|
||||||
scheduler.run('infrastructure');
|
|
||||||
}
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
|
||||||
// autoUpdater
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:autoUpdater")
|
await autoUpdater()
|
||||||
}, isDev ? 5000 : 60000 * 15)
|
}, 60000 * 15)
|
||||||
|
|
||||||
// cleanupStorage
|
// cleanupStorage
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:cleanupStorage")
|
await cleanupStorage()
|
||||||
}, isDev ? 6000 : 60000 * 10)
|
}, 60000 * 10)
|
||||||
|
|
||||||
// checkProxies and checkFluentBit
|
// checkProxies, checkFluentBit & refresh templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkProxies")
|
await checkProxies();
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:checkFluentBit")
|
await checkFluentBit();
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
|
// Refresh and check templates
|
||||||
|
setInterval(async () => {
|
||||||
|
await refreshTemplates()
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await refreshTags()
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await migrateServicesToNewTemplate()
|
||||||
|
}, isDev ? 10000 : 60000)
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
await copySSLCertificates();
|
||||||
}, 10000)
|
}, 10000)
|
||||||
|
|
||||||
setInterval(async () => {
|
|
||||||
scheduler.workers.has('infrastructure') && scheduler.workers.get('infrastructure').postMessage("action:copySSLCertificates")
|
|
||||||
}, 2000)
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
getTagsTemplates(),
|
||||||
getArch(),
|
getArch(),
|
||||||
getIPAddress(),
|
getIPAddress(),
|
||||||
configureRemoteDockers(),
|
configureRemoteDockers(),
|
||||||
])
|
])
|
||||||
});
|
|
||||||
})
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
async function getIPAddress() {
|
async function getIPAddress() {
|
||||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||||
@@ -171,31 +203,86 @@ async function getIPAddress() {
|
|||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
if (!settings.ipv4) {
|
if (!settings.ipv4) {
|
||||||
const ipv4 = await publicIpv4({ timeout: 2000 })
|
const ipv4 = await publicIpv4({ timeout: 2000 })
|
||||||
|
console.log(`Getting public IPv4 address...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.ipv6) {
|
if (!settings.ipv6) {
|
||||||
const ipv6 = await publicIpv6({ timeout: 2000 })
|
const ipv6 = await publicIpv6({ timeout: 2000 })
|
||||||
|
console.log(`Getting public IPv6 address...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
async function initServer() {
|
async function getTagsTemplates() {
|
||||||
|
const { default: got } = await import('got')
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(`docker network create --attachable coolify`);
|
if (isDev) {
|
||||||
|
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||||
|
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||||
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
|
||||||
|
await fs.writeFile('./tags.json', tags)
|
||||||
|
console.log('[004] Tags and templates loaded in dev mode...')
|
||||||
|
} else {
|
||||||
|
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||||
|
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||||
|
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||||
|
await fs.writeFile('/app/tags.json', tags)
|
||||||
|
console.log('[004] Tags and templates loaded...')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Couldn't get latest templates.")
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function initServer() {
|
||||||
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
|
const settings = await prisma.setting.findUnique({ where: { id: '0' } })
|
||||||
|
try {
|
||||||
|
if (settings.doNotTrack === true) {
|
||||||
|
console.log('[000] Telemetry disabled...')
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (settings.sentryDSN !== sentryDSN) {
|
||||||
|
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } })
|
||||||
|
}
|
||||||
|
// Initialize Sentry
|
||||||
|
// Sentry.init({
|
||||||
|
// dsn: sentryDSN,
|
||||||
|
// environment: isDev ? 'development' : 'production',
|
||||||
|
// release: version
|
||||||
|
// });
|
||||||
|
// console.log('[000] Sentry initialized...')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log(`[001] Initializing server...`);
|
||||||
|
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
try {
|
try {
|
||||||
|
console.log(`[002] Cleanup stucked builds...`);
|
||||||
const isOlder = compareVersions('3.8.1', version);
|
const isOlder = compareVersions('3.8.1', version);
|
||||||
if (isOlder === 1) {
|
if (isOlder === 1) {
|
||||||
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
|
try {
|
||||||
|
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||||
|
await fs.rm('/tmp/build-sources', { recursive: true, force: true })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getArch() {
|
async function getArch() {
|
||||||
try {
|
try {
|
||||||
const settings = await prisma.setting.findFirst({})
|
const settings = await prisma.setting.findFirst({})
|
||||||
if (settings && !settings.arch) {
|
if (settings && !settings.arch) {
|
||||||
|
console.log(`Getting architecture...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
@@ -207,9 +294,244 @@ async function configureRemoteDockers() {
|
|||||||
where: { remoteVerified: true, remoteEngine: true }
|
where: { remoteVerified: true, remoteEngine: true }
|
||||||
});
|
});
|
||||||
if (remoteDocker.length > 0) {
|
if (remoteDocker.length > 0) {
|
||||||
|
console.log(`Verifying Remote Docker Engines...`);
|
||||||
for (const docker of remoteDocker) {
|
for (const docker of remoteDocker) {
|
||||||
await createRemoteEngineConfiguration(docker.id)
|
console.log('Verifying:', docker.remoteIpAddress)
|
||||||
|
await verifyRemoteDockerEngineFn(docker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function autoUpdater() {
|
||||||
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
|
const currentVersion = version;
|
||||||
|
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||||
|
searchParams: {
|
||||||
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
|
version: currentVersion
|
||||||
|
}
|
||||||
|
}).json()
|
||||||
|
const latestVersion = coolify.main.version;
|
||||||
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
|
if (isUpdateAvailable === 1) {
|
||||||
|
const activeCount = 0
|
||||||
|
if (activeCount === 0) {
|
||||||
|
if (!isDev) {
|
||||||
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
|
if (isAutoUpdateEnabled) {
|
||||||
|
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
|
||||||
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` })
|
||||||
|
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
|
||||||
|
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Updating (not really in dev mode).');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkFluentBit() {
|
||||||
|
try {
|
||||||
|
if (!isDev) {
|
||||||
|
const engine = '/var/run/docker.sock';
|
||||||
|
const { id } = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { engine, network: 'coolify' }
|
||||||
|
});
|
||||||
|
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
|
||||||
|
if (!found) {
|
||||||
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||||
|
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function checkProxies() {
|
||||||
|
try {
|
||||||
|
const { default: isReachable } = await import('is-port-reachable');
|
||||||
|
let portReachable;
|
||||||
|
|
||||||
|
const { arch, ipv4, ipv6 } = await listSettings();
|
||||||
|
|
||||||
|
// Coolify Proxy local
|
||||||
|
const engine = '/var/run/docker.sock';
|
||||||
|
const localDocker = await prisma.destinationDocker.findFirst({
|
||||||
|
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||||
|
});
|
||||||
|
if (localDocker) {
|
||||||
|
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikProxy(localDocker.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Coolify Proxy remote
|
||||||
|
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||||
|
where: { remoteEngine: true, remoteVerified: true }
|
||||||
|
});
|
||||||
|
if (remoteDocker.length > 0) {
|
||||||
|
for (const docker of remoteDocker) {
|
||||||
|
if (docker.isCoolifyProxyUsed) {
|
||||||
|
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
||||||
|
if (!portReachable) {
|
||||||
|
await startTraefikProxy(docker.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await createRemoteEngineConfiguration(docker.id)
|
||||||
|
} catch (error) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TCP Proxies
|
||||||
|
const databasesWithPublicPort = await prisma.database.findMany({
|
||||||
|
where: { publicPort: { not: null } },
|
||||||
|
include: { settings: true, destinationDocker: true }
|
||||||
|
});
|
||||||
|
for (const database of databasesWithPublicPort) {
|
||||||
|
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
||||||
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
||||||
|
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpressWithFtp = await prisma.wordpress.findMany({
|
||||||
|
where: { ftpPublicPort: { not: null } },
|
||||||
|
include: { service: { include: { destinationDocker: true } } }
|
||||||
|
});
|
||||||
|
for (const ftp of wordpressWithFtp) {
|
||||||
|
const { service, ftpPublicPort } = ftp;
|
||||||
|
const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Proxies
|
||||||
|
// const minioInstances = await prisma.minio.findMany({
|
||||||
|
// where: { publicPort: { not: null } },
|
||||||
|
// include: { service: { include: { destinationDocker: true } } }
|
||||||
|
// });
|
||||||
|
// for (const minio of minioInstances) {
|
||||||
|
// const { service, publicPort } = minio;
|
||||||
|
// const { destinationDockerId, destinationDocker, id } = service;
|
||||||
|
// if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
|
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copySSLCertificates() {
|
||||||
|
try {
|
||||||
|
const pAll = await import('p-all');
|
||||||
|
const actions = []
|
||||||
|
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
||||||
|
const teamIds = certificates.map(c => c.teamId)
|
||||||
|
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
||||||
|
for (const certificate of certificates) {
|
||||||
|
const { id, key, cert } = certificate
|
||||||
|
const decryptedKey = decrypt(key)
|
||||||
|
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
||||||
|
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
||||||
|
for (const destination of destinations) {
|
||||||
|
if (destination.remoteEngine) {
|
||||||
|
if (destination.remoteVerified) {
|
||||||
|
const { id: dockerId, remoteIpAddress } = destination
|
||||||
|
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actions.push(async () => copyLocalCertificates(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await pAll.default(actions, { concurrency: 1 })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
||||||
|
try {
|
||||||
|
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
|
||||||
|
await executeCommand({ sshCommand: true, shell: true, dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
|
||||||
|
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||||
|
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function copyLocalCertificates(id: string) {
|
||||||
|
try {
|
||||||
|
await executeCommand({ command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`, shell: true })
|
||||||
|
await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||||
|
await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cleanupStorage() {
|
||||||
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
|
let enginesDone = new Set()
|
||||||
|
for (const destination of destinationDockers) {
|
||||||
|
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||||
|
if (destination.engine) enginesDone.add(destination.engine)
|
||||||
|
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||||
|
|
||||||
|
let lowDiskSpace = false;
|
||||||
|
try {
|
||||||
|
let stdout = null
|
||||||
|
if (!isDev) {
|
||||||
|
const output = await executeCommand({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`, shell: true })
|
||||||
|
stdout = output.stdout;
|
||||||
|
} else {
|
||||||
|
const output = await executeCommand({
|
||||||
|
command:
|
||||||
|
`df -kPT /`
|
||||||
|
});
|
||||||
|
stdout = output.stdout;
|
||||||
|
}
|
||||||
|
let lines = stdout.trim().split('\n');
|
||||||
|
let header = lines[0];
|
||||||
|
let regex =
|
||||||
|
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||||
|
const boundaries = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = regex.exec(header))) {
|
||||||
|
boundaries.push(match[0].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
boundaries[boundaries.length - 1] = -1;
|
||||||
|
const data = lines.slice(1).map((line) => {
|
||||||
|
const cl = boundaries.map((boundary) => {
|
||||||
|
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||||
|
line = line.slice(boundary);
|
||||||
|
return column.trim();
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
capacity: Number.parseInt(cl[5], 10) / 100
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (data.length > 0) {
|
||||||
|
const { capacity } = data[0];
|
||||||
|
if (capacity > 0.8) {
|
||||||
|
lowDiskSpace = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,25 @@ import crypto from 'crypto';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
|
import {
|
||||||
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication } from '../lib/common';
|
copyBaseConfigurationFiles,
|
||||||
|
makeLabelForSimpleDockerfile,
|
||||||
|
makeLabelForStandaloneApplication,
|
||||||
|
saveBuildLog,
|
||||||
|
saveDockerRegistryCredentials,
|
||||||
|
setDefaultConfiguration
|
||||||
|
} from '../lib/buildPacks/common';
|
||||||
|
import {
|
||||||
|
createDirectories,
|
||||||
|
decrypt,
|
||||||
|
defaultComposeConfiguration,
|
||||||
|
getDomain,
|
||||||
|
prisma,
|
||||||
|
decryptApplication,
|
||||||
|
isDev,
|
||||||
|
pushToRegistry,
|
||||||
|
executeCommand
|
||||||
|
} from '../lib/common';
|
||||||
import * as importers from '../lib/importers';
|
import * as importers from '../lib/importers';
|
||||||
import * as buildpacks from '../lib/buildPacks';
|
import * as buildpacks from '../lib/buildPacks';
|
||||||
|
|
||||||
@@ -14,79 +31,336 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
if (message === 'error') throw new Error('oops');
|
if (message === 'error') throw new Error('oops');
|
||||||
if (message === 'cancel') {
|
if (message === 'cancel') {
|
||||||
parentPort.postMessage('cancelled');
|
parentPort.postMessage('cancelled');
|
||||||
await prisma.$disconnect()
|
await prisma.$disconnect();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const pThrottle = await import('p-throttle')
|
const pThrottle = await import('p-throttle');
|
||||||
const throttle = pThrottle.default({
|
const throttle = pThrottle.default({
|
||||||
limit: 1,
|
limit: 1,
|
||||||
interval: 2000
|
interval: 2000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const th = throttle(async () => {
|
const th = throttle(async () => {
|
||||||
try {
|
try {
|
||||||
const queuedBuilds = await prisma.build.findMany({ where: { status: { in: ['queued', 'running'] } }, orderBy: { createdAt: 'asc' } });
|
const queuedBuilds = await prisma.build.findMany({
|
||||||
const { concurrentBuilds } = await prisma.setting.findFirst({})
|
where: { status: { in: ['queued', 'running'] } },
|
||||||
|
orderBy: { createdAt: 'asc' }
|
||||||
|
});
|
||||||
|
const { concurrentBuilds } = await prisma.setting.findFirst({});
|
||||||
if (queuedBuilds.length > 0) {
|
if (queuedBuilds.length > 0) {
|
||||||
parentPort.postMessage({ deploying: true });
|
parentPort.postMessage({ deploying: true });
|
||||||
const concurrency = concurrentBuilds;
|
const concurrency = concurrentBuilds;
|
||||||
const pAll = await import('p-all');
|
const pAll = await import('p-all');
|
||||||
const actions = []
|
const actions = [];
|
||||||
|
|
||||||
for (const queueBuild of queuedBuilds) {
|
for (const queueBuild of queuedBuilds) {
|
||||||
actions.push(async () => {
|
actions.push(async () => {
|
||||||
let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
let application = await prisma.application.findUnique({
|
||||||
let { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild
|
where: { id: queueBuild.applicationId },
|
||||||
application = decryptApplication(application)
|
include: {
|
||||||
const originalApplicationId = application.id
|
dockerRegistry: true,
|
||||||
if (pullmergeRequestId) {
|
destinationDocker: true,
|
||||||
const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } })
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
if (previewApplications.length > 0) {
|
persistentStorage: true,
|
||||||
previewApplicationId = previewApplications[0].id
|
secrets: true,
|
||||||
}
|
settings: true,
|
||||||
}
|
teams: true
|
||||||
const usableApplicationId = previewApplicationId || originalApplicationId
|
|
||||||
try {
|
|
||||||
if (queueBuild.status === 'running') {
|
|
||||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let {
|
||||||
|
id: buildId,
|
||||||
|
type,
|
||||||
|
gitSourceId,
|
||||||
|
sourceBranch = null,
|
||||||
|
pullmergeRequestId = null,
|
||||||
|
previewApplicationId = null,
|
||||||
|
forceRebuild,
|
||||||
|
sourceRepository = null
|
||||||
|
} = queueBuild;
|
||||||
|
application = decryptApplication(application);
|
||||||
|
|
||||||
|
if (!gitSourceId && application.simpleDockerfile) {
|
||||||
const {
|
const {
|
||||||
id: applicationId,
|
id: applicationId,
|
||||||
name,
|
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
gitSource,
|
|
||||||
configHash,
|
|
||||||
fqdn,
|
|
||||||
projectId,
|
|
||||||
secrets,
|
secrets,
|
||||||
phpModules,
|
|
||||||
settings,
|
|
||||||
persistentStorage,
|
|
||||||
pythonWSGI,
|
|
||||||
pythonModule,
|
|
||||||
pythonVariable,
|
|
||||||
denoOptions,
|
|
||||||
exposePort,
|
|
||||||
baseImage,
|
|
||||||
baseBuildImage,
|
|
||||||
deploymentType,
|
|
||||||
} = application
|
|
||||||
let {
|
|
||||||
branch,
|
|
||||||
repository,
|
|
||||||
buildPack,
|
|
||||||
port,
|
port,
|
||||||
installCommand,
|
persistentStorage,
|
||||||
buildCommand,
|
exposePort,
|
||||||
startCommand,
|
simpleDockerfile,
|
||||||
baseDirectory,
|
dockerRegistry
|
||||||
publishDirectory,
|
} = application;
|
||||||
dockerFileLocation,
|
const { workdir } = await createDirectories({ repository: applicationId, buildId });
|
||||||
denoMainFile
|
try {
|
||||||
} = application
|
if (queueBuild.status === 'running') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: 'Building halted, restarting...',
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const volumes =
|
||||||
|
persistentStorage?.map((storage) => {
|
||||||
|
if (storage.oldPath) {
|
||||||
|
return `${applicationId}${storage.path
|
||||||
|
.replace(/\//gi, '-')
|
||||||
|
.replace('-app', '')}:${storage.path}`;
|
||||||
|
}
|
||||||
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
if (destinationDockerId) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'running' }
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
|
||||||
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containerArray = containers.split('\n');
|
||||||
|
if (containerArray.length > 0) {
|
||||||
|
for (const container of containerArray) {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const envs = [`PORT='${port}'`];
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
const isSecretFound = secrets.filter(
|
||||||
|
(s) => s.name === secret.name && s.isPRMRSecret
|
||||||
|
);
|
||||||
|
if (isSecretFound.length > 0) {
|
||||||
|
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
||||||
|
} else {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||||
|
if (dockerRegistry) {
|
||||||
|
const { url, username, password } = dockerRegistry;
|
||||||
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
|
}
|
||||||
|
|
||||||
|
const labels = makeLabelForSimpleDockerfile({
|
||||||
|
applicationId,
|
||||||
|
type,
|
||||||
|
port: exposePort ? `${exposePort}:${port}` : port
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const composeVolumes = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const composeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[applicationId]: {
|
||||||
|
build: {
|
||||||
|
context: workdir
|
||||||
|
},
|
||||||
|
image: `${applicationId}:${buildId}`,
|
||||||
|
container_name: applicationId,
|
||||||
|
volumes,
|
||||||
|
labels,
|
||||||
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
|
depends_on: [],
|
||||||
|
expose: [port],
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
...defaultComposeConfiguration(destinationDocker.network)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[destinationDocker.network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
|
};
|
||||||
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
|
await executeCommand({
|
||||||
|
debug: true,
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker compose --project-directory ${workdir} up -d`
|
||||||
|
});
|
||||||
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error !== 1) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||||
|
}
|
||||||
|
if (error instanceof Error) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: error.message,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.rm(workdir, { recursive: true, force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (application.dockerRegistryImageName) {
|
||||||
|
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
|
||||||
|
const imageName = application.dockerRegistryImageName.split(':')[0];
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
await pushToRegistry(application, workdir, buildId, imageName, customTag);
|
||||||
|
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.stdout) {
|
||||||
|
await saveBuildLog({ line: error.stdout, buildId, applicationId });
|
||||||
|
}
|
||||||
|
if (error.stderr) {
|
||||||
|
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await fs.rm(workdir, { recursive: true, force: true });
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'success' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalApplicationId = application.id;
|
||||||
|
const {
|
||||||
|
id: applicationId,
|
||||||
|
name,
|
||||||
|
destinationDocker,
|
||||||
|
destinationDockerId,
|
||||||
|
gitSource,
|
||||||
|
configHash,
|
||||||
|
fqdn,
|
||||||
|
projectId,
|
||||||
|
secrets,
|
||||||
|
phpModules,
|
||||||
|
settings,
|
||||||
|
persistentStorage,
|
||||||
|
pythonWSGI,
|
||||||
|
pythonModule,
|
||||||
|
pythonVariable,
|
||||||
|
denoOptions,
|
||||||
|
exposePort,
|
||||||
|
baseImage,
|
||||||
|
baseBuildImage,
|
||||||
|
deploymentType,
|
||||||
|
gitCommitHash,
|
||||||
|
dockerRegistry
|
||||||
|
} = application;
|
||||||
|
|
||||||
|
let {
|
||||||
|
branch,
|
||||||
|
repository,
|
||||||
|
buildPack,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory,
|
||||||
|
dockerFileLocation,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
denoMainFile
|
||||||
|
} = application;
|
||||||
|
|
||||||
|
let imageId = applicationId;
|
||||||
|
let domain = getDomain(fqdn);
|
||||||
|
|
||||||
|
let location = null;
|
||||||
|
|
||||||
|
let tag = null;
|
||||||
|
let customTag = null;
|
||||||
|
let imageName = null;
|
||||||
|
|
||||||
|
let imageFoundLocally = false;
|
||||||
|
let imageFoundRemotely = false;
|
||||||
|
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
const previewApplications = await prisma.previewApplication.findMany({
|
||||||
|
where: { applicationId: originalApplicationId, pullmergeRequestId }
|
||||||
|
});
|
||||||
|
if (previewApplications.length > 0) {
|
||||||
|
previewApplicationId = previewApplications[0].id;
|
||||||
|
}
|
||||||
|
// Previews, we need to get the source branch and set subdomain
|
||||||
|
branch = sourceBranch;
|
||||||
|
domain = `${pullmergeRequestId}.${domain}`;
|
||||||
|
imageId = `${applicationId}-${pullmergeRequestId}`;
|
||||||
|
repository = sourceRepository || repository;
|
||||||
|
}
|
||||||
|
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||||
|
try {
|
||||||
|
if (queueBuild.status === 'running') {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: 'Building halted, restarting...',
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const currentHash = crypto
|
const currentHash = crypto
|
||||||
.createHash('sha256')
|
.createHash('sha256')
|
||||||
.update(
|
.update(
|
||||||
@@ -112,32 +386,26 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
)
|
)
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
const { debug } = settings;
|
const { debug } = settings;
|
||||||
// if (concurrency === 1) {
|
if (!debug) {
|
||||||
// await prisma.build.updateMany({
|
await saveBuildLog({
|
||||||
// where: {
|
line: `Debug logging is disabled. Enable it above if necessary!`,
|
||||||
// status: { in: ['queued', 'running'] },
|
buildId,
|
||||||
// id: { not: buildId },
|
applicationId
|
||||||
// applicationId,
|
});
|
||||||
// createdAt: { lt: new Date(new Date().getTime() - 10 * 1000) }
|
}
|
||||||
// },
|
|
||||||
// data: { status: 'failed' }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
let imageId = applicationId;
|
|
||||||
let domain = getDomain(fqdn);
|
|
||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
if (storage.oldPath) {
|
||||||
}${storage.path}`;
|
return `${applicationId}${storage.path
|
||||||
|
.replace(/\//gi, '-')
|
||||||
|
.replace('-app', '')}:${storage.path}`;
|
||||||
|
}
|
||||||
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
// Previews, we need to get the source branch and set subdomain
|
|
||||||
if (pullmergeRequestId) {
|
|
||||||
branch = sourceBranch;
|
|
||||||
domain = `${pullmergeRequestId}.${domain}`;
|
|
||||||
imageId = `${applicationId}-${pullmergeRequestId}`;
|
|
||||||
repository = sourceRepository || repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration);
|
||||||
|
} catch (error) {}
|
||||||
let deployNeeded = true;
|
let deployNeeded = true;
|
||||||
let destinationType;
|
let destinationType;
|
||||||
|
|
||||||
@@ -145,8 +413,11 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
destinationType = 'docker';
|
destinationType = 'docker';
|
||||||
}
|
}
|
||||||
if (destinationType === 'docker') {
|
if (destinationType === 'docker') {
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
await prisma.build.update({
|
||||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
where: { id: buildId },
|
||||||
|
data: { status: 'running' }
|
||||||
|
});
|
||||||
|
|
||||||
const configuration = await setDefaultConfiguration(application);
|
const configuration = await setDefaultConfiguration(application);
|
||||||
|
|
||||||
buildPack = configuration.buildPack;
|
buildPack = configuration.buildPack;
|
||||||
@@ -157,6 +428,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
publishDirectory = configuration.publishDirectory;
|
publishDirectory = configuration.publishDirectory;
|
||||||
baseDirectory = configuration.baseDirectory || '';
|
baseDirectory = configuration.baseDirectory || '';
|
||||||
dockerFileLocation = configuration.dockerFileLocation;
|
dockerFileLocation = configuration.dockerFileLocation;
|
||||||
|
dockerComposeFileLocation = configuration.dockerComposeFileLocation;
|
||||||
denoMainFile = configuration.denoMainFile;
|
denoMainFile = configuration.denoMainFile;
|
||||||
const commit = await importers[gitSource.type]({
|
const commit = await importers[gitSource.type]({
|
||||||
applicationId,
|
applicationId,
|
||||||
@@ -166,6 +438,8 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
githubAppId: gitSource.githubApp?.id,
|
githubAppId: gitSource.githubApp?.id,
|
||||||
gitlabAppId: gitSource.gitlabApp?.id,
|
gitlabAppId: gitSource.gitlabApp?.id,
|
||||||
customPort: gitSource.customPort,
|
customPort: gitSource.customPort,
|
||||||
|
gitCommitHash,
|
||||||
|
configuration,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
@@ -179,20 +453,35 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
if (!commit) {
|
if (!commit) {
|
||||||
throw new Error('No commit found?');
|
throw new Error('No commit found?');
|
||||||
}
|
}
|
||||||
let tag = commit.slice(0, 7);
|
tag = commit.slice(0, 7);
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||||
}
|
}
|
||||||
|
if (application.dockerRegistryImageName) {
|
||||||
|
imageName = application.dockerRegistryImageName.split(':')[0];
|
||||||
|
customTag = application.dockerRegistryImageName.split(':')[1] || tag;
|
||||||
|
} else {
|
||||||
|
customTag = tag;
|
||||||
|
imageName = applicationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
customTag = `${customTag}-${pullmergeRequestId}`;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||||
} catch (err) { }
|
} catch (err) {}
|
||||||
|
|
||||||
if (!pullmergeRequestId) {
|
if (!pullmergeRequestId) {
|
||||||
if (configHash !== currentHash) {
|
if (configHash !== currentHash) {
|
||||||
deployNeeded = true;
|
deployNeeded = true;
|
||||||
if (configHash) {
|
if (configHash) {
|
||||||
await saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
|
await saveBuildLog({
|
||||||
|
line: 'Configuration changed',
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deployNeeded = false;
|
deployNeeded = false;
|
||||||
@@ -201,28 +490,78 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
deployNeeded = true;
|
deployNeeded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let imageFound = false;
|
|
||||||
try {
|
try {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker image inspect ${applicationId}:${tag}`
|
command: `docker image inspect ${applicationId}:${tag}`
|
||||||
})
|
});
|
||||||
imageFound = true;
|
imageFoundLocally = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
if (dockerRegistry) {
|
||||||
if (forceRebuild) deployNeeded = true
|
const { url, username, password } = dockerRegistry;
|
||||||
if (!imageFound || deployNeeded) {
|
location = await saveDockerRegistryCredentials({
|
||||||
// if (true) {
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
workdir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker ${
|
||||||
|
location ? `--config ${location}` : ''
|
||||||
|
} pull ${imageName}:${customTag}`
|
||||||
|
});
|
||||||
|
imageFoundRemotely = true;
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
let imageFound = `${applicationId}:${tag}`;
|
||||||
|
if (imageFoundRemotely) {
|
||||||
|
imageFound = `${imageName}:${customTag}`;
|
||||||
|
}
|
||||||
|
await copyBaseConfigurationFiles(
|
||||||
|
buildPack,
|
||||||
|
workdir,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
baseImage
|
||||||
|
);
|
||||||
|
const labels = makeLabelForStandaloneApplication({
|
||||||
|
applicationId,
|
||||||
|
fqdn,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
|
pullmergeRequestId,
|
||||||
|
buildPack,
|
||||||
|
repository,
|
||||||
|
branch,
|
||||||
|
projectId,
|
||||||
|
port: exposePort ? `${exposePort}:${port}` : port,
|
||||||
|
commit,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory
|
||||||
|
});
|
||||||
|
if (forceRebuild) deployNeeded = true;
|
||||||
|
if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) {
|
||||||
if (buildpacks[buildPack])
|
if (buildpacks[buildPack])
|
||||||
await buildpacks[buildPack]({
|
await buildpacks[buildPack]({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
|
network: destinationDocker.network,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
domain,
|
domain,
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
volumes,
|
||||||
|
labels,
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
buildPack,
|
buildPack,
|
||||||
repository,
|
repository,
|
||||||
@@ -244,129 +583,212 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
pythonModule,
|
pythonModule,
|
||||||
pythonVariable,
|
pythonVariable,
|
||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
dockerComposeFileLocation,
|
||||||
denoMainFile,
|
denoMainFile,
|
||||||
denoOptions,
|
denoOptions,
|
||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType
|
deploymentType,
|
||||||
|
forceRebuild
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
await saveBuildLog({
|
||||||
|
line: `Build pack ${buildPack} not found`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
throw new Error(`Build pack ${buildPack} not found.`);
|
throw new Error(`Build pack ${buildPack} not found.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
|
if (imageFoundRemotely || deployNeeded) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Container image ${imageFound} found in Docker Registry - reuising it`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (imageFoundLocally || deployNeeded) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Container image ${imageFound} found locally - reuising it`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
|
if (buildPack === 'compose') {
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
|
try {
|
||||||
} catch (error) {
|
const { stdout: containers } = await executeCommand({
|
||||||
//
|
dockerId: destinationDockerId,
|
||||||
}
|
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
|
||||||
const envs = [
|
});
|
||||||
`PORT=${port}`
|
if (containers) {
|
||||||
];
|
const containerArray = containers.split('\n');
|
||||||
if (secrets.length > 0) {
|
if (containerArray.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
for (const container of containerArray) {
|
||||||
if (pullmergeRequestId) {
|
await executeCommand({
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
dockerId: destinationDockerId,
|
||||||
if (isSecretFound.length > 0) {
|
command: `docker stop -t 0 ${container}`
|
||||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
});
|
||||||
} else {
|
await executeCommand({
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
dockerId: destinationDockerId,
|
||||||
}
|
command: `docker rm --force ${container}`
|
||||||
} else {
|
});
|
||||||
if (!secret.isPRMRSecret) {
|
}
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
}
|
//
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
}
|
||||||
const labels = makeLabelForStandaloneApplication({
|
try {
|
||||||
applicationId,
|
await executeCommand({
|
||||||
fqdn,
|
debug,
|
||||||
name,
|
buildId,
|
||||||
type,
|
applicationId,
|
||||||
pullmergeRequestId,
|
dockerId: destinationDocker.id,
|
||||||
buildPack,
|
command: `docker compose --project-directory ${workdir} up -d`
|
||||||
repository,
|
});
|
||||||
branch,
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
projectId,
|
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
|
||||||
commit,
|
|
||||||
installCommand,
|
|
||||||
buildCommand,
|
|
||||||
startCommand,
|
|
||||||
baseDirectory,
|
|
||||||
publishDirectory
|
|
||||||
});
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
|
|
||||||
const composeVolumes = volumes.map((volume) => {
|
|
||||||
return {
|
|
||||||
[`${volume.split(':')[0]}`]: {
|
|
||||||
name: volume.split(':')[0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const composeFile = {
|
|
||||||
version: '3.8',
|
|
||||||
services: {
|
|
||||||
[imageId]: {
|
|
||||||
image: `${applicationId}:${tag}`,
|
|
||||||
container_name: imageId,
|
|
||||||
volumes,
|
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
|
||||||
labels,
|
|
||||||
depends_on: [],
|
|
||||||
expose: [port],
|
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
|
||||||
// logging: {
|
|
||||||
// driver: 'fluentd',
|
|
||||||
// },
|
|
||||||
...defaultComposeConfiguration(destinationDocker.network),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
networks: {
|
|
||||||
[destinationDocker.network]: {
|
|
||||||
external: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
volumes: Object.assign({}, ...composeVolumes)
|
|
||||||
};
|
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
|
||||||
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
|
|
||||||
} catch (error) {
|
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
|
||||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
|
||||||
if (foundBuild) {
|
|
||||||
await prisma.build.update({
|
await prisma.build.update({
|
||||||
where: { id: buildId },
|
where: { id: buildId },
|
||||||
data: {
|
data: { status: 'success' }
|
||||||
status: 'failed'
|
});
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id: applicationId },
|
||||||
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${
|
||||||
|
pullmergeRequestId ? imageId : applicationId
|
||||||
|
}' --format {{.ID}}`
|
||||||
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containerArray = containers.split('\n');
|
||||||
|
if (containerArray.length > 0) {
|
||||||
|
for (const container of containerArray) {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const envs = [`PORT='${port}'`];
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
const isSecretFound = secrets.filter(
|
||||||
|
(s) => s.name === secret.name && s.isPRMRSecret
|
||||||
|
);
|
||||||
|
if (isSecretFound.length > 0) {
|
||||||
|
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
||||||
|
} else {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error(error);
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
if (dockerRegistry) {
|
||||||
|
const { url, username, password } = dockerRegistry;
|
||||||
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
|
}
|
||||||
|
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const composeVolumes = volumes.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const composeFile = {
|
||||||
|
version: '3.8',
|
||||||
|
services: {
|
||||||
|
[imageId]: {
|
||||||
|
image: imageFound,
|
||||||
|
container_name: imageId,
|
||||||
|
volumes,
|
||||||
|
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||||
|
labels,
|
||||||
|
depends_on: [],
|
||||||
|
expose: [port],
|
||||||
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
|
...defaultComposeConfiguration(destinationDocker.network)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
networks: {
|
||||||
|
[destinationDocker.network]: {
|
||||||
|
external: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
|
};
|
||||||
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
|
await executeCommand({
|
||||||
|
debug,
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker compose --project-directory ${workdir} up -d`
|
||||||
|
});
|
||||||
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
|
} catch (error) {
|
||||||
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
|
if (foundBuild) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pullmergeRequestId)
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id: applicationId },
|
||||||
|
data: { configHash: currentHash }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
|
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
|
||||||
if (!pullmergeRequestId) await prisma.application.update({
|
|
||||||
where: { id: applicationId },
|
|
||||||
data: { configHash: currentHash }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
|
||||||
if (foundBuild) {
|
if (foundBuild) {
|
||||||
await prisma.build.update({
|
await prisma.build.update({
|
||||||
where: { id: buildId },
|
where: { id: buildId },
|
||||||
@@ -378,17 +800,47 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
if (error !== 1) {
|
if (error !== 1) {
|
||||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
|
if (error instanceof Error) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: error.message,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.rm(workdir, { recursive: true, force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (application.dockerRegistryImageName && (!imageFoundRemotely || forceRebuild)) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
await pushToRegistry(application, workdir, tag, imageName, customTag);
|
||||||
|
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.stdout) {
|
||||||
|
await saveBuildLog({ line: error.stdout, buildId, applicationId });
|
||||||
|
}
|
||||||
|
if (error.stderr) {
|
||||||
|
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await fs.rm(workdir, { recursive: true, force: true });
|
||||||
|
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await pAll.default(actions, { concurrency })
|
await pAll.default(actions, { concurrency });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
while (true) {
|
while (true) {
|
||||||
await th()
|
await th();
|
||||||
}
|
}
|
||||||
} else process.exit(0);
|
} else process.exit(0);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1,296 +0,0 @@
|
|||||||
import { parentPort } from 'node:worker_threads';
|
|
||||||
import axios from 'axios';
|
|
||||||
import { compareVersions } from 'compare-versions';
|
|
||||||
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, listSettings, version, createRemoteEngineConfiguration, decrypt, executeSSHCmd } from '../lib/common';
|
|
||||||
import { checkContainer } from '../lib/docker';
|
|
||||||
import fs from 'fs/promises'
|
|
||||||
async function autoUpdater() {
|
|
||||||
try {
|
|
||||||
const currentVersion = version;
|
|
||||||
const { data: versions } = await axios
|
|
||||||
.get(
|
|
||||||
`https://get.coollabs.io/versions.json`
|
|
||||||
, {
|
|
||||||
params: {
|
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
|
||||||
version: currentVersion
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const latestVersion = versions['coolify'].main.version;
|
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
|
||||||
if (isUpdateAvailable === 1) {
|
|
||||||
const activeCount = 0
|
|
||||||
if (activeCount === 0) {
|
|
||||||
if (!isDev) {
|
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
|
||||||
if (isAutoUpdateEnabled) {
|
|
||||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
|
||||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
|
||||||
await asyncExecShell(
|
|
||||||
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
|
||||||
);
|
|
||||||
await asyncExecShell(
|
|
||||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('Updating (not really in dev mode).');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) { }
|
|
||||||
}
|
|
||||||
async function checkFluentBit() {
|
|
||||||
if (!isDev) {
|
|
||||||
const engine = '/var/run/docker.sock';
|
|
||||||
const { id } = await prisma.destinationDocker.findFirst({
|
|
||||||
where: { engine, network: 'coolify' }
|
|
||||||
});
|
|
||||||
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit' });
|
|
||||||
if (!found) {
|
|
||||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
|
||||||
await asyncExecShell(`docker compose up -d fluent-bit`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
|
||||||
try {
|
|
||||||
await asyncExecShell(`scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`)
|
|
||||||
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
|
|
||||||
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
|
||||||
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
|
||||||
} catch (error) {
|
|
||||||
console.log({ error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function copyLocalCertificates(id: string) {
|
|
||||||
try {
|
|
||||||
await asyncExecShell(`docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`)
|
|
||||||
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`)
|
|
||||||
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`)
|
|
||||||
} catch (error) {
|
|
||||||
console.log({ error })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function copySSLCertificates() {
|
|
||||||
try {
|
|
||||||
const pAll = await import('p-all');
|
|
||||||
const actions = []
|
|
||||||
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
|
||||||
const teamIds = certificates.map(c => c.teamId)
|
|
||||||
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
|
||||||
for (const certificate of certificates) {
|
|
||||||
const { id, key, cert } = certificate
|
|
||||||
const decryptedKey = decrypt(key)
|
|
||||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
|
||||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
|
||||||
for (const destination of destinations) {
|
|
||||||
if (destination.remoteEngine) {
|
|
||||||
if (destination.remoteVerified) {
|
|
||||||
const { id: dockerId, remoteIpAddress } = destination
|
|
||||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actions.push(async () => copyLocalCertificates(id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await pAll.default(actions, { concurrency: 1 })
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
} finally {
|
|
||||||
await asyncExecShell(`find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function checkProxies() {
|
|
||||||
try {
|
|
||||||
const { default: isReachable } = await import('is-port-reachable');
|
|
||||||
let portReachable;
|
|
||||||
|
|
||||||
const { arch, ipv4, ipv6 } = await listSettings();
|
|
||||||
|
|
||||||
// Coolify Proxy local
|
|
||||||
const engine = '/var/run/docker.sock';
|
|
||||||
const localDocker = await prisma.destinationDocker.findFirst({
|
|
||||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
|
||||||
});
|
|
||||||
if (localDocker) {
|
|
||||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
|
||||||
if (!portReachable) {
|
|
||||||
await startTraefikProxy(localDocker.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Coolify Proxy remote
|
|
||||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
|
||||||
where: { remoteEngine: true, remoteVerified: true }
|
|
||||||
});
|
|
||||||
if (remoteDocker.length > 0) {
|
|
||||||
for (const docker of remoteDocker) {
|
|
||||||
if (docker.isCoolifyProxyUsed) {
|
|
||||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
|
||||||
if (!portReachable) {
|
|
||||||
await startTraefikProxy(docker.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await createRemoteEngineConfiguration(docker.id)
|
|
||||||
} catch (error) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TCP Proxies
|
|
||||||
const databasesWithPublicPort = await prisma.database.findMany({
|
|
||||||
where: { publicPort: { not: null } },
|
|
||||||
include: { settings: true, destinationDocker: true }
|
|
||||||
});
|
|
||||||
for (const database of databasesWithPublicPort) {
|
|
||||||
const { destinationDockerId, destinationDocker, publicPort, id } = database;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
const { privatePort } = generateDatabaseConfiguration(database, arch);
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const wordpressWithFtp = await prisma.wordpress.findMany({
|
|
||||||
where: { ftpPublicPort: { not: null } },
|
|
||||||
include: { service: { include: { destinationDocker: true } } }
|
|
||||||
});
|
|
||||||
for (const ftp of wordpressWithFtp) {
|
|
||||||
const { service, ftpPublicPort } = ftp;
|
|
||||||
const { destinationDockerId, destinationDocker, id } = service;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Proxies
|
|
||||||
const minioInstances = await prisma.minio.findMany({
|
|
||||||
where: { publicPort: { not: null } },
|
|
||||||
include: { service: { include: { destinationDocker: true } } }
|
|
||||||
});
|
|
||||||
for (const minio of minioInstances) {
|
|
||||||
const { service, publicPort } = minio;
|
|
||||||
const { destinationDockerId, destinationDocker, id } = service;
|
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
|
||||||
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function cleanupPrismaEngines() {
|
|
||||||
if (!isDev) {
|
|
||||||
try {
|
|
||||||
const { stdout } = await asyncExecShell(`ps -ef | grep /app/prisma-engines/query-engine | grep -v grep | wc -l | xargs`)
|
|
||||||
if (stdout.trim() != null && stdout.trim() != '' && Number(stdout.trim()) > 1) {
|
|
||||||
await asyncExecShell(`killall -q -e /app/prisma-engines/query-engine -o 1m`)
|
|
||||||
}
|
|
||||||
} catch (error) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function cleanupStorage() {
|
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
|
||||||
let enginesDone = new Set()
|
|
||||||
for (const destination of destinationDockers) {
|
|
||||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
|
||||||
if (destination.engine) enginesDone.add(destination.engine)
|
|
||||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
|
||||||
|
|
||||||
let lowDiskSpace = false;
|
|
||||||
try {
|
|
||||||
let stdout = null
|
|
||||||
if (!isDev) {
|
|
||||||
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
|
|
||||||
stdout = output.stdout;
|
|
||||||
} else {
|
|
||||||
const output = await asyncExecShell(
|
|
||||||
`df -kPT /`
|
|
||||||
);
|
|
||||||
stdout = output.stdout;
|
|
||||||
}
|
|
||||||
let lines = stdout.trim().split('\n');
|
|
||||||
let header = lines[0];
|
|
||||||
let regex =
|
|
||||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
|
||||||
const boundaries = [];
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(header))) {
|
|
||||||
boundaries.push(match[0].length);
|
|
||||||
}
|
|
||||||
|
|
||||||
boundaries[boundaries.length - 1] = -1;
|
|
||||||
const data = lines.slice(1).map((line) => {
|
|
||||||
const cl = boundaries.map((boundary) => {
|
|
||||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
|
||||||
line = line.slice(boundary);
|
|
||||||
return column.trim();
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
capacity: Number.parseInt(cl[5], 10) / 100
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (data.length > 0) {
|
|
||||||
const { capacity } = data[0];
|
|
||||||
if (capacity > 0.8) {
|
|
||||||
lowDiskSpace = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) { }
|
|
||||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
let status = {
|
|
||||||
cleanupStorage: false,
|
|
||||||
autoUpdater: false,
|
|
||||||
copySSLCertificates: false,
|
|
||||||
}
|
|
||||||
if (parentPort) {
|
|
||||||
parentPort.on('message', async (message) => {
|
|
||||||
if (parentPort) {
|
|
||||||
if (message === 'error') throw new Error('oops');
|
|
||||||
if (message === 'cancel') {
|
|
||||||
parentPort.postMessage('cancelled');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
if (message === 'action:cleanupStorage') {
|
|
||||||
if (!status.autoUpdater) {
|
|
||||||
status.cleanupStorage = true
|
|
||||||
await cleanupStorage();
|
|
||||||
status.cleanupStorage = false
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === 'action:cleanupPrismaEngines') {
|
|
||||||
await cleanupPrismaEngines();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === 'action:checkProxies') {
|
|
||||||
await checkProxies();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === 'action:checkFluentBit') {
|
|
||||||
await checkFluentBit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === 'action:copySSLCertificates') {
|
|
||||||
if (!status.copySSLCertificates) {
|
|
||||||
status.copySSLCertificates = true
|
|
||||||
await copySSLCertificates();
|
|
||||||
status.copySSLCertificates = false
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message === 'action:autoUpdater') {
|
|
||||||
if (!status.cleanupStorage) {
|
|
||||||
status.autoUpdater = true
|
|
||||||
await autoUpdater();
|
|
||||||
status.autoUpdater = false
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else process.exit(0);
|
|
||||||
})();
|
|
||||||
530
apps/api/src/lib.ts
Normal file
530
apps/api/src/lib.ts
Normal file
@@ -0,0 +1,530 @@
|
|||||||
|
import cuid from "cuid";
|
||||||
|
import { decrypt, encrypt, fixType, generatePassword, generateToken, prisma } from "./lib/common";
|
||||||
|
import { getTemplates } from "./lib/services";
|
||||||
|
|
||||||
|
export async function migrateApplicationPersistentStorage() {
|
||||||
|
const settings = await prisma.setting.findFirst()
|
||||||
|
if (settings) {
|
||||||
|
const { id: settingsId, applicationStoragePathMigrationFinished } = settings
|
||||||
|
try {
|
||||||
|
if (!applicationStoragePathMigrationFinished) {
|
||||||
|
const applications = await prisma.application.findMany({ include: { persistentStorage: true } });
|
||||||
|
for (const application of applications) {
|
||||||
|
if (application.persistentStorage && application.persistentStorage.length > 0 && application?.buildPack !== 'docker') {
|
||||||
|
for (const storage of application.persistentStorage) {
|
||||||
|
let { id, path } = storage
|
||||||
|
if (!path.startsWith('/app')) {
|
||||||
|
path = `/app${path}`
|
||||||
|
await prisma.applicationPersistentStorage.update({ where: { id }, data: { path, oldPath: true } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
} finally {
|
||||||
|
await prisma.setting.update({ where: { id: settingsId }, data: { applicationStoragePathMigrationFinished: true } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function migrateServicesToNewTemplate() {
|
||||||
|
// This function migrates old hardcoded services to the new template based services
|
||||||
|
try {
|
||||||
|
let templates = await getTemplates()
|
||||||
|
const services: any = await prisma.service.findMany({
|
||||||
|
include: {
|
||||||
|
destinationDocker: true,
|
||||||
|
persistentStorage: true,
|
||||||
|
serviceSecret: true,
|
||||||
|
serviceSetting: true,
|
||||||
|
minio: true,
|
||||||
|
plausibleAnalytics: true,
|
||||||
|
vscodeserver: true,
|
||||||
|
wordpress: true,
|
||||||
|
ghost: true,
|
||||||
|
meiliSearch: true,
|
||||||
|
umami: true,
|
||||||
|
hasura: true,
|
||||||
|
fider: true,
|
||||||
|
moodle: true,
|
||||||
|
appwrite: true,
|
||||||
|
glitchTip: true,
|
||||||
|
searxng: true,
|
||||||
|
weblate: true,
|
||||||
|
taiga: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const service of services) {
|
||||||
|
try {
|
||||||
|
const { id } = service
|
||||||
|
if (!service.type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let template = templates.find(t => fixType(t.type) === fixType(service.type));
|
||||||
|
if (template) {
|
||||||
|
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))
|
||||||
|
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics) await plausibleAnalytics(service, template)
|
||||||
|
if (service.type === 'fider' && service.fider) await fider(service, template)
|
||||||
|
if (service.type === 'minio' && service.minio) await minio(service, template)
|
||||||
|
if (service.type === 'vscodeserver' && service.vscodeserver) await vscodeserver(service, template)
|
||||||
|
if (service.type === 'wordpress' && service.wordpress) await wordpress(service, template)
|
||||||
|
if (service.type === 'ghost' && service.ghost) await ghost(service, template)
|
||||||
|
if (service.type === 'meilisearch' && service.meiliSearch) await meilisearch(service, template)
|
||||||
|
if (service.type === 'umami' && service.umami) await umami(service, template)
|
||||||
|
if (service.type === 'hasura' && service.hasura) await hasura(service, template)
|
||||||
|
if (service.type === 'glitchTip' && service.glitchTip) await glitchtip(service, template)
|
||||||
|
if (service.type === 'searxng' && service.searxng) await searxng(service, template)
|
||||||
|
if (service.type === 'weblate' && service.weblate) await weblate(service, template)
|
||||||
|
if (service.type === 'appwrite' && service.appwrite) await appwrite(service, template)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createVolumes(service, template);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
if (template.variables) {
|
||||||
|
if (template.variables.length > 0) {
|
||||||
|
for (const variable of template.variables) {
|
||||||
|
const { defaultValue } = variable;
|
||||||
|
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||||
|
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||||
|
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||||
|
variable.value = generatePassword({ length });
|
||||||
|
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||||
|
variable.value = generatePassword({ length, isHex: true });
|
||||||
|
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||||
|
variable.value = cuid();
|
||||||
|
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
||||||
|
variable.value = generateToken()
|
||||||
|
} else {
|
||||||
|
variable.value = variable.defaultValue || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const variable of template.variables) {
|
||||||
|
if (variable.id.startsWith('$$secret_')) {
|
||||||
|
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.serviceSecret.create({
|
||||||
|
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (variable.id.startsWith('$$config_')) {
|
||||||
|
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.serviceSetting.create({
|
||||||
|
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const s of Object.keys(template.services)) {
|
||||||
|
if (service.type === 'plausibleanalytics') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (template.services[s].volumes) {
|
||||||
|
for (const volume of template.services[s].volumes) {
|
||||||
|
const [volumeName, path] = volume.split(':')
|
||||||
|
if (!volumeName.startsWith('/')) {
|
||||||
|
const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.servicePersistentStorage.create({
|
||||||
|
data: { volumeName, path, containerId: s, predefined: true, service: { connect: { id } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await prisma.service.update({ where: { id }, data: { templateVersion: template.templateVersion } })
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function appwrite(service: any, template: any) {
|
||||||
|
const { opensslKeyV1, executorSecret, mariadbHost, mariadbPort, mariadbUser, mariadbPassword, mariadbRootUserPassword, mariadbDatabase } = service.appwrite
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`_APP_EXECUTOR_SECRET@@@${executorSecret}`,
|
||||||
|
`_APP_OPENSSL_KEY_V1@@@${opensslKeyV1}`,
|
||||||
|
`_APP_DB_PASS@@@${mariadbPassword}`,
|
||||||
|
`_APP_DB_ROOT_PASS@@@${mariadbRootUserPassword}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
`_APP_DB_HOST@@@${mariadbHost}`,
|
||||||
|
`_APP_DB_PORT@@@${mariadbPort}`,
|
||||||
|
`_APP_DB_USER@@@${mariadbUser}`,
|
||||||
|
`_APP_DB_SCHEMA@@@${mariadbDatabase}`,
|
||||||
|
]
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { appwrite: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function weblate(service: any, template: any) {
|
||||||
|
const { adminPassword, postgresqlUser, postgresqlPassword, postgresqlDatabase } = service.weblate
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`WEBLATE_ADMIN_PASSWORD@@@${adminPassword}`,
|
||||||
|
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
`WEBLATE_SITE_DOMAIN@@@$$generate_domain`,
|
||||||
|
`POSTGRES_USER@@@${postgresqlUser}`,
|
||||||
|
`POSTGRES_DATABASE@@@${postgresqlDatabase}`,
|
||||||
|
`POSTGRES_DB@@@${postgresqlDatabase}`,
|
||||||
|
`POSTGRES_HOST@@@$$id-postgres`,
|
||||||
|
`POSTGRES_PORT@@@5432`,
|
||||||
|
`REDIS_HOST@@@$$id-redis`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { weblate: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function searxng(service: any, template: any) {
|
||||||
|
const { secretKey, redisPassword } = service.searxng
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`SECRET_KEY@@@${secretKey}`,
|
||||||
|
`REDIS_PASSWORD@@@${redisPassword}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
`SEARXNG_BASE_URL@@@$$generate_fqdn`
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { searxng: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function glitchtip(service: any, template: any) {
|
||||||
|
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, secretKeyBase, defaultEmail, defaultUsername, defaultPassword, defaultEmailFrom, emailSmtpHost, emailSmtpPort, emailSmtpUser, emailSmtpPassword, emailSmtpUseTls, emailSmtpUseSsl, emailBackend, mailgunApiKey, sendgridApiKey, enableOpenUserRegistration } = service.glitchTip
|
||||||
|
const { id } = service
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
`SECRET_KEY@@@${secretKeyBase}`,
|
||||||
|
`MAILGUN_API_KEY@@@${mailgunApiKey}`,
|
||||||
|
`SENDGRID_API_KEY@@@${sendgridApiKey}`,
|
||||||
|
`DJANGO_SUPERUSER_PASSWORD@@@${defaultPassword}`,
|
||||||
|
emailSmtpUser && emailSmtpPassword && emailSmtpHost && emailSmtpPort && `EMAIL_URL@@@${encrypt(`smtp://${emailSmtpUser}:${decrypt(emailSmtpPassword)}@${emailSmtpHost}:${emailSmtpPort}`)} || ''`,
|
||||||
|
`DATABASE_URL@@@${encrypt(`postgres://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}`)}`,
|
||||||
|
`REDIS_URL@@@${encrypt(`redis://${id}-redis:6379`)}`
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`POSTGRES_USER@@@${postgresqlUser}`,
|
||||||
|
`POSTGRES_DB@@@${postgresqlDatabase}`,
|
||||||
|
`DEFAULT_FROM_EMAIL@@@${defaultEmailFrom}`,
|
||||||
|
`EMAIL_USE_TLS@@@${emailSmtpUseTls}`,
|
||||||
|
`EMAIL_USE_SSL@@@${emailSmtpUseSsl}`,
|
||||||
|
`EMAIL_BACKEND@@@${emailBackend}`,
|
||||||
|
`ENABLE_OPEN_USER_REGISTRATION@@@${enableOpenUserRegistration}`,
|
||||||
|
`DJANGO_SUPERUSER_EMAIL@@@${defaultEmail}`,
|
||||||
|
`DJANGO_SUPERUSER_USERNAME@@@${defaultUsername}`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
await prisma.service.update({ where: { id: service.id }, data: { type: 'glitchtip' } })
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { glitchTip: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function hasura(service: any, template: any) {
|
||||||
|
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, graphQLAdminPassword } = service.hasura
|
||||||
|
const { id } = service
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`HASURA_GRAPHQL_ADMIN_SECRET@@@${graphQLAdminPassword}`,
|
||||||
|
`HASURA_GRAPHQL_METADATA_DATABASE_URL@@@${encrypt(`postgres://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}`)}`,
|
||||||
|
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`POSTGRES_USER@@@${postgresqlUser}`,
|
||||||
|
`POSTGRES_DB@@@${postgresqlDatabase}`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { hasura: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function umami(service: any, template: any) {
|
||||||
|
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, umamiAdminPassword, hashSalt } = service.umami
|
||||||
|
const { id } = service
|
||||||
|
const secrets = [
|
||||||
|
`HASH_SALT@@@${hashSalt}`,
|
||||||
|
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
`ADMIN_PASSWORD@@@${umamiAdminPassword}`,
|
||||||
|
`DATABASE_URL@@@${encrypt(`postgres://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}`)}`,
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`DATABASE_TYPE@@@postgresql`,
|
||||||
|
`POSTGRES_USER@@@${postgresqlUser}`,
|
||||||
|
`POSTGRES_DB@@@${postgresqlDatabase}`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
await prisma.service.update({ where: { id: service.id }, data: { type: "umami-postgresql" } })
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { umami: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function meilisearch(service: any, template: any) {
|
||||||
|
const { masterKey } = service.meiliSearch
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`MEILI_MASTER_KEY@@@${masterKey}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
// await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { meiliSearch: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function ghost(service: any, template: any) {
|
||||||
|
const { defaultEmail, defaultPassword, mariadbUser, mariadbPassword, mariadbRootUser, mariadbRootUserPassword, mariadbDatabase } = service.ghost
|
||||||
|
const { fqdn } = service
|
||||||
|
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`GHOST_PASSWORD@@@${defaultPassword}`,
|
||||||
|
`MARIADB_PASSWORD@@@${mariadbPassword}`,
|
||||||
|
`MARIADB_ROOT_PASSWORD@@@${mariadbRootUserPassword}`,
|
||||||
|
`GHOST_DATABASE_PASSWORD@@@${mariadbPassword}`,
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`GHOST_EMAIL@@@${defaultEmail}`,
|
||||||
|
`GHOST_DATABASE_HOST@@@${service.id}-mariadb`,
|
||||||
|
`GHOST_DATABASE_USER@@@${mariadbUser}`,
|
||||||
|
`GHOST_DATABASE_NAME@@@${mariadbDatabase}`,
|
||||||
|
`GHOST_DATABASE_PORT_NUMBER@@@3306`,
|
||||||
|
`MARIADB_USER@@@${mariadbUser}`,
|
||||||
|
`MARIADB_DATABASE@@@${mariadbDatabase}`,
|
||||||
|
`MARIADB_ROOT_USER@@@${mariadbRootUser}`,
|
||||||
|
`GHOST_HOST@@@$$generate_domain`,
|
||||||
|
`url@@@$$generate_fqdn`,
|
||||||
|
`GHOST_ENABLE_HTTPS@@@${isHttps ? 'yes' : 'no'}`
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
await prisma.service.update({ where: { id: service.id }, data: { type: "ghost-mariadb" } })
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { ghost: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function wordpress(service: any, template: any) {
|
||||||
|
const { extraConfig, tablePrefix, ownMysql, mysqlHost, mysqlPort, mysqlUser, mysqlPassword, mysqlRootUser, mysqlRootUserPassword, mysqlDatabase, ftpEnabled, ftpUser, ftpPassword, ftpPublicPort, ftpHostKey, ftpHostKeyPrivate } = service.wordpress
|
||||||
|
|
||||||
|
let settings = []
|
||||||
|
let secrets = []
|
||||||
|
if (ownMysql) {
|
||||||
|
secrets = [
|
||||||
|
`WORDPRESS_DB_PASSWORD@@@${mysqlPassword}`,
|
||||||
|
ftpPassword && `COOLIFY_FTP_PASSWORD@@@${ftpPassword}`,
|
||||||
|
ftpHostKeyPrivate && `COOLIFY_FTP_HOST_KEY_PRIVATE@@@${ftpHostKeyPrivate}`,
|
||||||
|
ftpHostKey && `COOLIFY_FTP_HOST_KEY@@@${ftpHostKey}`,
|
||||||
|
]
|
||||||
|
settings = [
|
||||||
|
`WORDPRESS_CONFIG_EXTRA@@@${extraConfig}`,
|
||||||
|
`WORDPRESS_DB_HOST@@@${mysqlHost}`,
|
||||||
|
`WORDPRESS_DB_PORT@@@${mysqlPort}`,
|
||||||
|
`WORDPRESS_DB_USER@@@${mysqlUser}`,
|
||||||
|
`WORDPRESS_DB_NAME@@@${mysqlDatabase}`,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
secrets = [
|
||||||
|
`MYSQL_ROOT_PASSWORD@@@${mysqlRootUserPassword}`,
|
||||||
|
`MYSQL_PASSWORD@@@${mysqlPassword}`,
|
||||||
|
ftpPassword && `COOLIFY_FTP_PASSWORD@@@${ftpPassword}`,
|
||||||
|
ftpHostKeyPrivate && `COOLIFY_FTP_HOST_KEY_PRIVATE@@@${ftpHostKeyPrivate}`,
|
||||||
|
ftpHostKey && `COOLIFY_FTP_HOST_KEY@@@${ftpHostKey}`,
|
||||||
|
]
|
||||||
|
settings = [
|
||||||
|
`MYSQL_ROOT_USER@@@${mysqlRootUser}`,
|
||||||
|
`MYSQL_USER@@@${mysqlUser}`,
|
||||||
|
`MYSQL_DATABASE@@@${mysqlDatabase}`,
|
||||||
|
`MYSQL_HOST@@@${service.id}-mysql`,
|
||||||
|
`MYSQL_PORT@@@${mysqlPort}`,
|
||||||
|
`WORDPRESS_CONFIG_EXTRA@@@${extraConfig}`,
|
||||||
|
`WORDPRESS_TABLE_PREFIX@@@${tablePrefix}`,
|
||||||
|
`WORDPRESS_DB_HOST@@@${service.id}-mysql`,
|
||||||
|
`COOLIFY_OWN_DB@@@${ownMysql}`,
|
||||||
|
`COOLIFY_FTP_ENABLED@@@${ftpEnabled}`,
|
||||||
|
`COOLIFY_FTP_USER@@@${ftpUser}`,
|
||||||
|
`COOLIFY_FTP_PUBLIC_PORT@@@${ftpPublicPort}`,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
if (ownMysql) {
|
||||||
|
await prisma.service.update({ where: { id: service.id }, data: { type: "wordpress-only" } })
|
||||||
|
}
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { wordpress: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function vscodeserver(service: any, template: any) {
|
||||||
|
const { password } = service.vscodeserver
|
||||||
|
|
||||||
|
const secrets = [
|
||||||
|
`PASSWORD@@@${password}`,
|
||||||
|
]
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { vscodeserver: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function minio(service: any, template: any) {
|
||||||
|
const { rootUser, rootUserPassword, apiFqdn } = service.minio
|
||||||
|
const secrets = [
|
||||||
|
`MINIO_ROOT_PASSWORD@@@${rootUserPassword}`,
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`MINIO_ROOT_USER@@@${rootUser}`,
|
||||||
|
`MINIO_SERVER_URL@@@${apiFqdn}`,
|
||||||
|
`MINIO_BROWSER_REDIRECT_URL@@@$$generate_fqdn`,
|
||||||
|
`MINIO_DOMAIN@@@$$generate_domain`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { minio: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function fider(service: any, template: any) {
|
||||||
|
const { postgresqlUser, postgresqlPassword, postgresqlDatabase, jwtSecret, emailNoreply, emailMailgunApiKey, emailMailgunDomain, emailMailgunRegion, emailSmtpHost, emailSmtpPort, emailSmtpUser, emailSmtpPassword, emailSmtpEnableStartTls } = service.fider
|
||||||
|
const { id } = service
|
||||||
|
const secrets = [
|
||||||
|
`JWT_SECRET@@@${jwtSecret}`,
|
||||||
|
emailMailgunApiKey && `EMAIL_MAILGUN_API@@@${emailMailgunApiKey}`,
|
||||||
|
emailSmtpPassword && `EMAIL_SMTP_PASSWORD@@@${emailSmtpPassword}`,
|
||||||
|
`POSTGRES_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
`DATABASE_URL@@@${encrypt(`postgresql://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}?sslmode=disable`)}`
|
||||||
|
]
|
||||||
|
const settings = [
|
||||||
|
`BASE_URL@@@$$generate_fqdn`,
|
||||||
|
`EMAIL_NOREPLY@@@${emailNoreply || 'noreply@example.com'}`,
|
||||||
|
`EMAIL_MAILGUN_DOMAIN@@@${emailMailgunDomain || ''}`,
|
||||||
|
`EMAIL_MAILGUN_REGION@@@${emailMailgunRegion || ''}`,
|
||||||
|
`EMAIL_SMTP_HOST@@@${emailSmtpHost || ''}`,
|
||||||
|
`EMAIL_SMTP_PORT@@@${emailSmtpPort || 587}`,
|
||||||
|
`EMAIL_SMTP_USER@@@${emailSmtpUser || ''}`,
|
||||||
|
`EMAIL_SMTP_PASSWORD@@@${emailSmtpPassword || ''}`,
|
||||||
|
`EMAIL_SMTP_ENABLE_STARTTLS@@@${emailSmtpEnableStartTls || 'false'}`,
|
||||||
|
`POSTGRES_USER@@@${postgresqlUser}`,
|
||||||
|
`POSTGRES_DB@@@${postgresqlDatabase}`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { fider: { disconnect: true } } })
|
||||||
|
|
||||||
|
}
|
||||||
|
async function plausibleAnalytics(service: any, template: any) {
|
||||||
|
const { email, username, password, postgresqlUser, postgresqlPassword, postgresqlDatabase, secretKeyBase, scriptName } = service.plausibleAnalytics;
|
||||||
|
const { id } = service
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
`BASE_URL@@@$$generate_fqdn`,
|
||||||
|
`ADMIN_USER_EMAIL@@@${email}`,
|
||||||
|
`ADMIN_USER_NAME@@@${username}`,
|
||||||
|
`DISABLE_AUTH@@@false`,
|
||||||
|
`DISABLE_REGISTRATION@@@true`,
|
||||||
|
`POSTGRESQL_USERNAME@@@${postgresqlUser}`,
|
||||||
|
`POSTGRESQL_DATABASE@@@${postgresqlDatabase}`,
|
||||||
|
`SCRIPT_NAME@@@${scriptName}`,
|
||||||
|
]
|
||||||
|
const secrets = [
|
||||||
|
`ADMIN_USER_PWD@@@${password}`,
|
||||||
|
`SECRET_KEY_BASE@@@${secretKeyBase}`,
|
||||||
|
`POSTGRESQL_PASSWORD@@@${postgresqlPassword}`,
|
||||||
|
`DATABASE_URL@@@${encrypt(`postgres://${postgresqlUser}:${decrypt(postgresqlPassword)}@${id}-postgresql:5432/${postgresqlDatabase}`)}`,
|
||||||
|
]
|
||||||
|
await migrateSettings(settings, service, template);
|
||||||
|
await migrateSecrets(secrets, service);
|
||||||
|
|
||||||
|
// Disconnect old service data
|
||||||
|
// await prisma.service.update({ where: { id: service.id }, data: { plausibleAnalytics: { disconnect: true } } })
|
||||||
|
}
|
||||||
|
async function migrateSettings(settings: any[], service: any, template: any) {
|
||||||
|
for (const setting of settings) {
|
||||||
|
try {
|
||||||
|
if (!setting) continue;
|
||||||
|
let [name, value] = setting.split('@@@')
|
||||||
|
let minio = name
|
||||||
|
if (name === 'MINIO_SERVER_URL') {
|
||||||
|
name = 'coolify_fqdn_minio_console'
|
||||||
|
}
|
||||||
|
if (!value || value === 'null') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let variableName = template.variables.find((v: any) => v.name === name)?.id
|
||||||
|
if (!variableName) {
|
||||||
|
variableName = `$$config_${name.toLowerCase()}`
|
||||||
|
}
|
||||||
|
// console.log('Migrating setting', name, value, 'for service', service.id, ', service name:', service.name, 'variableName: ', variableName)
|
||||||
|
|
||||||
|
await prisma.serviceSetting.findFirst({ where: { name: minio, serviceId: service.id } }) || await prisma.serviceSetting.create({ data: { name: minio, value, variableName, service: { connect: { id: service.id } } } })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function migrateSecrets(secrets: any[], service: any) {
|
||||||
|
for (const secret of secrets) {
|
||||||
|
try {
|
||||||
|
if (!secret) continue;
|
||||||
|
let [name, value] = secret.split('@@@')
|
||||||
|
if (!value || value === 'null') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// console.log('Migrating secret', name, value, 'for service', service.id, ', service name:', service.name)
|
||||||
|
await prisma.serviceSecret.findFirst({ where: { name, serviceId: service.id } }) || await prisma.serviceSecret.create({ data: { name, value, service: { connect: { id: service.id } } } })
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function createVolumes(service: any, template: any) {
|
||||||
|
const volumes = [];
|
||||||
|
for (const s of Object.keys(template.services)) {
|
||||||
|
if (template.services[s].volumes && template.services[s].volumes.length > 0) {
|
||||||
|
for (const volume of template.services[s].volumes) {
|
||||||
|
let volumeName = volume.split(':')[0]
|
||||||
|
const volumePath = volume.split(':')[1]
|
||||||
|
let volumeService = s
|
||||||
|
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
|
||||||
|
let volumeId = volumeName.split('-')[0]
|
||||||
|
volumeName = volumeName.replace(volumeId, service.plausibleAnalytics.id)
|
||||||
|
}
|
||||||
|
volumes.push(`${volumeName}@@@${volumePath}@@@${volumeService}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const volume of volumes) {
|
||||||
|
const [volumeName, path, containerId] = volume.split('@@@')
|
||||||
|
// console.log('Creating volume', volumeName, path, containerId, 'for service', service.id, ', service name:', service.name)
|
||||||
|
await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: service.id } }) || await prisma.servicePersistentStorage.create({ data: { volumeName, path, containerId, predefined: true, service: { connect: { id: service.id } } } })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
import { base64Encode, encrypt, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
|
import {
|
||||||
|
base64Encode,
|
||||||
|
decrypt,
|
||||||
|
encrypt,
|
||||||
|
executeCommand,
|
||||||
|
generateTimestamp,
|
||||||
|
getDomain,
|
||||||
|
isARM,
|
||||||
|
isDev,
|
||||||
|
prisma,
|
||||||
|
version
|
||||||
|
} from '../common';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { day } from "../dayjs";
|
import { day } from '../dayjs';
|
||||||
|
|
||||||
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
||||||
const nodeBased = [
|
const nodeBased = [
|
||||||
@@ -17,7 +28,10 @@ const nodeBased = [
|
|||||||
'nextjs'
|
'nextjs'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function setDefaultBaseImage(buildPack: string | null, deploymentType: string | null = null) {
|
export function setDefaultBaseImage(
|
||||||
|
buildPack: string | null,
|
||||||
|
deploymentType: string | null = null
|
||||||
|
) {
|
||||||
const nodeVersions = [
|
const nodeVersions = [
|
||||||
{
|
{
|
||||||
value: 'node:lts',
|
value: 'node:lts',
|
||||||
@@ -52,6 +66,14 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
{
|
{
|
||||||
value: 'webdevops/apache:alpine',
|
value: 'webdevops/apache:alpine',
|
||||||
label: 'webdevops/apache:alpine'
|
label: 'webdevops/apache:alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'nginx:alpine',
|
||||||
|
label: 'nginx:alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'httpd:alpine',
|
||||||
|
label: 'httpd:alpine (Apache)'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const rustVersions = [
|
const rustVersions = [
|
||||||
@@ -214,8 +236,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
label: 'webdevops/php-apache:7.1-alpine'
|
label: 'webdevops/php-apache:7.1-alpine'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'webdevops/php-nginx:7.1-alpine',
|
value: 'php:8.1-fpm',
|
||||||
label: 'webdevops/php-nginx:7.1-alpine'
|
label: 'php:8.1-fpm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'php:8.0-fpm',
|
||||||
|
label: 'php:8.0-fpm'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'php:8.1-fpm-alpine',
|
||||||
|
label: 'php:8.1-fpm-alpine'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'php:8.0-fpm-alpine',
|
||||||
|
label: 'php:8.0-fpm-alpine'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const pythonVersions = [
|
const pythonVersions = [
|
||||||
@@ -296,8 +330,8 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
{
|
{
|
||||||
value: 'heroku/builder-classic:22',
|
value: 'heroku/builder-classic:22',
|
||||||
label: 'heroku/builder-classic:22'
|
label: 'heroku/builder-classic:22'
|
||||||
},
|
}
|
||||||
]
|
];
|
||||||
let payload: any = {
|
let payload: any = {
|
||||||
baseImage: null,
|
baseImage: null,
|
||||||
baseBuildImage: null,
|
baseBuildImage: null,
|
||||||
@@ -306,8 +340,10 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
};
|
};
|
||||||
if (nodeBased.includes(buildPack)) {
|
if (nodeBased.includes(buildPack)) {
|
||||||
if (deploymentType === 'static') {
|
if (deploymentType === 'static') {
|
||||||
payload.baseImage = 'webdevops/nginx:alpine';
|
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||||
payload.baseImages = staticVersions;
|
payload.baseImages = isARM(process.arch)
|
||||||
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
} else {
|
} else {
|
||||||
@@ -318,8 +354,10 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (staticApps.includes(buildPack)) {
|
if (staticApps.includes(buildPack)) {
|
||||||
payload.baseImage = 'webdevops/nginx:alpine';
|
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||||
payload.baseImages = staticVersions;
|
payload.baseImages = isARM(process.arch)
|
||||||
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
@@ -337,12 +375,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
payload.baseImage = 'denoland/deno:latest';
|
payload.baseImage = 'denoland/deno:latest';
|
||||||
}
|
}
|
||||||
if (buildPack === 'php') {
|
if (buildPack === 'php') {
|
||||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
payload.baseImage = isARM(process.arch)
|
||||||
payload.baseImages = phpVersions;
|
? 'php:8.1-fpm-alpine'
|
||||||
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
|
payload.baseImages = isARM(process.arch)
|
||||||
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: phpVersions;
|
||||||
}
|
}
|
||||||
if (buildPack === 'laravel') {
|
if (buildPack === 'laravel') {
|
||||||
payload.baseImage = 'webdevops/php-apache:8.2-alpine';
|
payload.baseImage = isARM(process.arch)
|
||||||
payload.baseImages = phpVersions;
|
? 'php:8.1-fpm-alpine'
|
||||||
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
|
payload.baseImages = isARM(process.arch)
|
||||||
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: phpVersions;
|
||||||
payload.baseBuildImage = 'node:18';
|
payload.baseBuildImage = 'node:18';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
@@ -363,6 +409,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
|
dockerComposeFileLocation,
|
||||||
denoMainFile
|
denoMainFile
|
||||||
} = data;
|
} = data;
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
@@ -384,7 +431,8 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||||
if (baseDirectory) {
|
if (baseDirectory) {
|
||||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/') baseDirectory = baseDirectory.slice(0, -1);
|
if (baseDirectory.endsWith('/') && baseDirectory !== '/')
|
||||||
|
baseDirectory = baseDirectory.slice(0, -1);
|
||||||
}
|
}
|
||||||
if (dockerFileLocation) {
|
if (dockerFileLocation) {
|
||||||
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
||||||
@@ -392,6 +440,14 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
} else {
|
} else {
|
||||||
dockerFileLocation = '/Dockerfile';
|
dockerFileLocation = '/Dockerfile';
|
||||||
}
|
}
|
||||||
|
if (dockerComposeFileLocation) {
|
||||||
|
if (!dockerComposeFileLocation.startsWith('/'))
|
||||||
|
dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||||
|
if (dockerComposeFileLocation.endsWith('/'))
|
||||||
|
dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||||
|
} else {
|
||||||
|
dockerComposeFileLocation = '/Dockerfile';
|
||||||
|
}
|
||||||
if (!denoMainFile) {
|
if (!denoMainFile) {
|
||||||
denoMainFile = 'main.ts';
|
denoMainFile = 'main.ts';
|
||||||
}
|
}
|
||||||
@@ -405,6 +461,7 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
|
dockerComposeFileLocation,
|
||||||
denoMainFile
|
denoMainFile
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -451,7 +508,6 @@ export const scanningTemplates = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const saveBuildLog = async ({
|
export const saveBuildLog = async ({
|
||||||
line,
|
line,
|
||||||
buildId,
|
buildId,
|
||||||
@@ -461,16 +517,28 @@ export const saveBuildLog = async ({
|
|||||||
buildId: string;
|
buildId: string;
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}): Promise<any> => {
|
}): Promise<any> => {
|
||||||
const { default: got } = await import('got')
|
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
||||||
|
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
||||||
|
const { default: got } = await import('got');
|
||||||
|
if (typeof line === 'object' && line) {
|
||||||
|
if (line.shortMessage) {
|
||||||
|
line = line.shortMessage + '\n' + line.stderr;
|
||||||
|
} else {
|
||||||
|
line = JSON.stringify(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
if (line && typeof line === 'string' && line.includes('ghs_')) {
|
||||||
const regex = /ghs_.*@/g;
|
const regex = /ghs_.*@/g;
|
||||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||||
}
|
}
|
||||||
const addTimestamp = `[${generateTimestamp()}] ${line}`;
|
const addTimestamp = `[${generateTimestamp()}] ${line}`;
|
||||||
const fluentBitUrl = isDev ? 'http://localhost:24224' : 'http://coolify-fluentbit:24224';
|
const fluentBitUrl = isDev
|
||||||
|
? process.env.COOLIFY_CONTAINER_DEV === 'true'
|
||||||
|
? 'http://coolify-fluentbit:24224'
|
||||||
|
: 'http://localhost:24224'
|
||||||
|
: 'http://coolify-fluentbit:24224';
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev && !process.env.COOLIFY_CONTAINER_DEV) {
|
||||||
console.debug(`[${applicationId}] ${addTimestamp}`);
|
console.debug(`[${applicationId}] ${addTimestamp}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -478,16 +546,17 @@ export const saveBuildLog = async ({
|
|||||||
json: {
|
json: {
|
||||||
line: encrypt(line)
|
line: encrypt(line)
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isDev) return
|
|
||||||
return await prisma.buildLog.create({
|
return await prisma.buildLog.create({
|
||||||
data: {
|
data: {
|
||||||
line: addTimestamp, buildId, time: Number(day().valueOf()), applicationId
|
line: addTimestamp,
|
||||||
|
buildId,
|
||||||
|
time: Number(day().valueOf()),
|
||||||
|
applicationId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function copyBaseConfigurationFiles(
|
export async function copyBaseConfigurationFiles(
|
||||||
@@ -559,6 +628,7 @@ export async function copyBaseConfigurationFiles(
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// TODO: Add more configuration files for other buildpacks, like apache2, etc.
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
@@ -572,6 +642,29 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
||||||
|
if (!username || !password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decryptedPassword = decrypt(password);
|
||||||
|
const location = `${workdir}/.docker`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(`${workdir}/.docker`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
const payload = JSON.stringify({
|
||||||
|
auths: {
|
||||||
|
[url]: {
|
||||||
|
auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await fs.writeFile(`${location}/config.json`, payload);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
export async function buildImage({
|
export async function buildImage({
|
||||||
applicationId,
|
applicationId,
|
||||||
tag,
|
tag,
|
||||||
@@ -580,34 +673,52 @@ export async function buildImage({
|
|||||||
dockerId,
|
dockerId,
|
||||||
isCache = false,
|
isCache = false,
|
||||||
debug = false,
|
debug = false,
|
||||||
dockerFileLocation = '/Dockerfile'
|
dockerFileLocation = '/Dockerfile',
|
||||||
|
commit,
|
||||||
|
forceRebuild = false
|
||||||
}) {
|
}) {
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image...`, buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||||
}
|
}
|
||||||
if (!debug) {
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`;
|
||||||
await saveBuildLog({
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`;
|
||||||
line: `Debug turned off. To see more details, allow it in the features tab.`,
|
let location = null;
|
||||||
buildId,
|
|
||||||
applicationId
|
const { dockerRegistry } = await prisma.application.findUnique({
|
||||||
});
|
where: { id: applicationId },
|
||||||
|
select: { dockerRegistry: true }
|
||||||
|
});
|
||||||
|
if (dockerRegistry) {
|
||||||
|
const { url, username, password } = dockerRegistry;
|
||||||
|
location = await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
await executeCommand({
|
||||||
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
|
stream: true,
|
||||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
debug,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
dockerId,
|
||||||
|
command: `docker ${location ? `--config ${location}` : ''} build ${
|
||||||
|
forceRebuild ? '--no-cache' : ''
|
||||||
|
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status } = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
if (status === 'canceled') {
|
if (status === 'canceled') {
|
||||||
throw new Error('Deployment canceled.')
|
throw new Error('Canceled.');
|
||||||
}
|
|
||||||
if (isCache) {
|
|
||||||
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
|
|
||||||
} else {
|
|
||||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
|
||||||
|
return [
|
||||||
|
'coolify.managed=true',
|
||||||
|
`coolify.version=${version}`,
|
||||||
|
`coolify.applicationId=${applicationId}`,
|
||||||
|
`coolify.type=standalone-application`
|
||||||
|
];
|
||||||
|
}
|
||||||
export function makeLabelForStandaloneApplication({
|
export function makeLabelForStandaloneApplication({
|
||||||
applicationId,
|
applicationId,
|
||||||
fqdn,
|
fqdn,
|
||||||
@@ -634,7 +745,9 @@ export function makeLabelForStandaloneApplication({
|
|||||||
return [
|
return [
|
||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
`coolify.version=${version}`,
|
`coolify.version=${version}`,
|
||||||
|
`coolify.applicationId=${applicationId}`,
|
||||||
`coolify.type=standalone-application`,
|
`coolify.type=standalone-application`,
|
||||||
|
`coolify.name=${name}`,
|
||||||
`coolify.configuration=${base64Encode(
|
`coolify.configuration=${base64Encode(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
applicationId,
|
applicationId,
|
||||||
@@ -677,15 +790,15 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,15 +828,15 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
|
|||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -737,11 +850,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||||
const {
|
const { applicationId, workdir, buildId } = data;
|
||||||
applicationId,
|
|
||||||
workdir,
|
|
||||||
buildId,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||||
@@ -758,4 +867,4 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
|||||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||||
await buildImage({ ...data, isCache: true });
|
await buildImage({ ...data, isCache: true });
|
||||||
}
|
}
|
||||||
|
|||||||
126
apps/api/src/lib/buildPacks/compose.ts
Normal file
126
apps/api/src/lib/buildPacks/compose.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { defaultComposeConfiguration, executeCommand } from '../common';
|
||||||
|
import { saveBuildLog } from './common';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
|
export default async function (data) {
|
||||||
|
let {
|
||||||
|
applicationId,
|
||||||
|
debug,
|
||||||
|
buildId,
|
||||||
|
dockerId,
|
||||||
|
network,
|
||||||
|
volumes,
|
||||||
|
labels,
|
||||||
|
workdir,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
dockerComposeFileLocation
|
||||||
|
} = data;
|
||||||
|
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||||
|
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||||
|
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||||
|
if (!dockerComposeYaml.services) {
|
||||||
|
throw 'No Services found in docker-compose file.';
|
||||||
|
}
|
||||||
|
const envs = [];
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
||||||
|
if (isSecretFound.length > 0) {
|
||||||
|
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
||||||
|
} else {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
let envFound = false;
|
||||||
|
try {
|
||||||
|
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
const composeVolumes = [];
|
||||||
|
if (volumes.length > 0) {
|
||||||
|
for (const volume of volumes) {
|
||||||
|
let [v, path] = volume.split(':');
|
||||||
|
composeVolumes[v] = {
|
||||||
|
name: v
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let networks = {};
|
||||||
|
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||||
|
value['container_name'] = `${applicationId}-${key}`;
|
||||||
|
value['env_file'] = envFound ? [`${workdir}/.env`] : [];
|
||||||
|
value['labels'] = labels;
|
||||||
|
// TODO: If we support separated volume for each service, we need to add it here
|
||||||
|
if (value['volumes']?.length > 0) {
|
||||||
|
value['volumes'] = value['volumes'].map((volume) => {
|
||||||
|
let [v, path, permission] = volume.split(':');
|
||||||
|
if (!path) {
|
||||||
|
path = v;
|
||||||
|
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||||
|
} else {
|
||||||
|
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||||
|
}
|
||||||
|
composeVolumes[v] = {
|
||||||
|
name: v
|
||||||
|
};
|
||||||
|
return `${v}:${path}${permission ? ':' + permission : ''}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (volumes.length > 0) {
|
||||||
|
for (const volume of volumes) {
|
||||||
|
value['volumes'].push(volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dockerComposeConfiguration[key].port) {
|
||||||
|
value['expose'] = [dockerComposeConfiguration[key].port];
|
||||||
|
}
|
||||||
|
if (value['networks']?.length > 0) {
|
||||||
|
value['networks'].forEach((network) => {
|
||||||
|
networks[network] = {
|
||||||
|
name: network
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
value['networks'] = [...(value['networks'] || ''), network];
|
||||||
|
dockerComposeYaml.services[key] = {
|
||||||
|
...dockerComposeYaml.services[key],
|
||||||
|
restart: defaultComposeConfiguration(network).restart,
|
||||||
|
deploy: defaultComposeConfiguration(network).deploy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (Object.keys(composeVolumes).length > 0) {
|
||||||
|
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
||||||
|
}
|
||||||
|
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||||
|
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||||
|
await executeCommand({
|
||||||
|
debug,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
dockerId,
|
||||||
|
command: `docker compose --project-directory ${workdir} pull`
|
||||||
|
});
|
||||||
|
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
||||||
|
await executeCommand({
|
||||||
|
debug,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
dockerId,
|
||||||
|
command: `docker compose --project-directory ${workdir} build --progress plain`
|
||||||
|
});
|
||||||
|
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
||||||
|
}
|
||||||
@@ -29,13 +29,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||||
Dockerfile.push(`ENV NO_COLOR true`);
|
Dockerfile.push(`ENV NO_COLOR true`);
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD deno run ${denoOptions ? denoOptions.split(' ') : ''} ${denoMainFile}`);
|
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,16 @@ import { promises as fs } from 'fs';
|
|||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
let {
|
let { workdir, buildId, baseDirectory, secrets, pullmergeRequestId, dockerFileLocation } = data;
|
||||||
applicationId,
|
|
||||||
debug,
|
|
||||||
tag,
|
|
||||||
workdir,
|
|
||||||
buildId,
|
|
||||||
baseDirectory,
|
|
||||||
secrets,
|
|
||||||
pullmergeRequestId,
|
|
||||||
dockerFileLocation
|
|
||||||
} = data
|
|
||||||
const file = `${workdir}${baseDirectory}${dockerFileLocation}`;
|
const file = `${workdir}${baseDirectory}${dockerFileLocation}`;
|
||||||
data.workdir = `${workdir}${baseDirectory}`;
|
data.workdir = `${workdir}${baseDirectory}`;
|
||||||
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8')
|
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8');
|
||||||
const Dockerfile: Array<string> = DockerfileRaw
|
const Dockerfile: Array<string> = DockerfileRaw.toString().trim().split('\n');
|
||||||
.toString()
|
Dockerfile.forEach((line, index) => {
|
||||||
.trim()
|
if (line.startsWith('FROM')) {
|
||||||
.split('\n');
|
Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
}
|
||||||
|
});
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
@@ -28,18 +19,15 @@ export default async function (data) {
|
|||||||
(pullmergeRequestId && secret.isPRMRSecret) ||
|
(pullmergeRequestId && secret.isPRMRSecret) ||
|
||||||
(!pullmergeRequestId && !secret.isPRMRSecret)
|
(!pullmergeRequestId && !secret.isPRMRSecret)
|
||||||
) {
|
) {
|
||||||
Dockerfile.unshift(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
|
|
||||||
Dockerfile.forEach((line, index) => {
|
Dockerfile.forEach((line, index) => {
|
||||||
if (line.startsWith('FROM')) {
|
if (line.startsWith('FROM')) {
|
||||||
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}`);
|
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
await fs.writeFile(`${workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
|
||||||
await buildImage(data);
|
await buildImage(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { executeDockerCmd, prisma } from "../common"
|
import { executeCommand } from "../common"
|
||||||
import { saveBuildLog } from "./common";
|
import { saveBuildLog } from "./common";
|
||||||
|
|
||||||
export default async function (data: any): Promise<void> {
|
export default async function (data: any): Promise<void> {
|
||||||
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory } = data
|
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data
|
||||||
try {
|
try {
|
||||||
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
|
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
|
buildId,
|
||||||
debug,
|
debug,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder heroku/buildpacks:20`
|
command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder ${baseImage}`
|
||||||
})
|
})
|
||||||
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import python from './python';
|
|||||||
import deno from './deno';
|
import deno from './deno';
|
||||||
import laravel from './laravel';
|
import laravel from './laravel';
|
||||||
import heroku from './heroku';
|
import heroku from './heroku';
|
||||||
|
import compose from './compose'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
node,
|
node,
|
||||||
@@ -35,5 +36,6 @@ export {
|
|||||||
python,
|
python,
|
||||||
deno,
|
deno,
|
||||||
laravel,
|
laravel,
|
||||||
heroku
|
heroku,
|
||||||
|
compose
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import TOML from '@iarna/toml';
|
import TOML from '@iarna/toml';
|
||||||
import { asyncExecShell } from '../common';
|
import { executeCommand } from '../common';
|
||||||
import { buildCacheImageWithCargo, buildImage } from './common';
|
import { buildCacheImageWithCargo, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image, name): Promise<void> => {
|
const createDockerfile = async (data, image, name): Promise<void> => {
|
||||||
@@ -28,7 +28,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
|||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
try {
|
try {
|
||||||
const { workdir, baseImage, baseBuildImage } = data;
|
const { workdir, baseImage, baseBuildImage } = data;
|
||||||
const { stdout: cargoToml } = await asyncExecShell(`cat ${workdir}/Cargo.toml`);
|
const { stdout: cargoToml } = await executeCommand({ command: `cat ${workdir}/Cargo.toml` });
|
||||||
const parsedToml: any = TOML.parse(cargoToml);
|
const parsedToml: any = TOML.parse(cargoToml);
|
||||||
const name = parsedToml.package.name;
|
const name = parsedToml.package.name;
|
||||||
await buildCacheImageWithCargo(data, baseBuildImage);
|
await buildCacheImageWithCargo(data, baseBuildImage);
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
if (baseImage?.includes('httpd')) {
|
||||||
|
Dockerfile.push('WORKDIR /usr/local/apache2/htdocs/');
|
||||||
|
} else {
|
||||||
|
Dockerfile.push('WORKDIR /app');
|
||||||
|
}
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
@@ -26,13 +30,13 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import { executeDockerCmd } from './common';
|
import { executeCommand } from './common';
|
||||||
|
|
||||||
export function formatLabelsOnDocker(data) {
|
export function formatLabelsOnDocker(data) {
|
||||||
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
|
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
|
||||||
@@ -16,7 +16,7 @@ export function formatLabelsOnDocker(data) {
|
|||||||
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
|
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
|
||||||
let containerFound = false;
|
let containerFound = false;
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({
|
const { stdout } = await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command:
|
command:
|
||||||
`docker inspect --format '{{json .State}}' ${container}`
|
`docker inspect --format '{{json .State}}' ${container}`
|
||||||
@@ -28,27 +28,26 @@ export async function checkContainer({ dockerId, container, remove = false }: {
|
|||||||
const isRestarting = status === 'restarting'
|
const isRestarting = status === 'restarting'
|
||||||
const isExited = status === 'exited'
|
const isExited = status === 'exited'
|
||||||
if (status === 'created') {
|
if (status === 'created') {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command:
|
command:
|
||||||
`docker rm ${container}`
|
`docker rm ${container}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (remove && status === 'exited') {
|
if (remove && status === 'exited') {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command:
|
command:
|
||||||
`docker rm ${container}`
|
`docker rm ${container}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
found: containerFound,
|
found: containerFound,
|
||||||
status: {
|
status: {
|
||||||
isRunning,
|
isRunning,
|
||||||
isRestarting,
|
isRestarting,
|
||||||
isExited
|
isExited
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -63,7 +62,7 @@ export async function checkContainer({ dockerId, container, remove = false }: {
|
|||||||
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
|
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
|
||||||
let isExited = false;
|
let isExited = false;
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
|
const { stdout } = await executeCommand({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
|
||||||
if (stdout.trim() === 'exited') {
|
if (stdout.trim() === 'exited') {
|
||||||
isExited = true;
|
isExited = true;
|
||||||
}
|
}
|
||||||
@@ -82,10 +81,13 @@ export async function removeContainer({
|
|||||||
dockerId: string;
|
dockerId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
const { stdout } = await executeCommand({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
await executeCommand({ dockerId, command: `docker rm ${id}` })
|
||||||
|
}
|
||||||
|
if (JSON.parse(stdout).Status === 'exited') {
|
||||||
|
await executeCommand({ dockerId, command: `docker rm ${id}` })
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import { saveBuildLog } from '../buildPacks/common';
|
import { saveBuildLog } from '../buildPacks/common';
|
||||||
import { asyncExecShell, decrypt, prisma } from '../common';
|
import { decrypt, executeCommand, prisma } from '../common';
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
applicationId,
|
applicationId,
|
||||||
@@ -9,6 +9,7 @@ export default async function ({
|
|||||||
githubAppId,
|
githubAppId,
|
||||||
repository,
|
repository,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
gitCommitHash,
|
||||||
htmlUrl,
|
htmlUrl,
|
||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
@@ -20,6 +21,7 @@ export default async function ({
|
|||||||
githubAppId: string;
|
githubAppId: string;
|
||||||
repository: string;
|
repository: string;
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
|
gitCommitHash?: string;
|
||||||
htmlUrl: string;
|
htmlUrl: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
buildId: string;
|
buildId: string;
|
||||||
@@ -28,16 +30,24 @@ export default async function ({
|
|||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got')
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '');
|
||||||
await saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
|
||||||
if (forPublic) {
|
if (forPublic) {
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
line: `Cloning ${repository}:${branch}...`,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
await asyncExecShell(
|
if (gitCommitHash) {
|
||||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
await saveBuildLog({
|
||||||
);
|
line: `Checking out ${gitCommitHash} commit...`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await executeCommand({
|
||||||
|
command:
|
||||||
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||||
@@ -62,17 +72,23 @@ export default async function ({
|
|||||||
})
|
})
|
||||||
.json();
|
.json();
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
line: `Cloning ${repository}:${branch}...`,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
await asyncExecShell(
|
if (gitCommitHash) {
|
||||||
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git submodule update --init --recursive && git lfs pull && cd .. `
|
await saveBuildLog({
|
||||||
);
|
line: `Checking out ${gitCommitHash} commit...`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await executeCommand({
|
||||||
|
command:
|
||||||
|
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
|
||||||
|
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { saveBuildLog } from "../buildPacks/common";
|
import { saveBuildLog } from "../buildPacks/common";
|
||||||
import { asyncExecShell } from "../common";
|
import { executeCommand } from "../common";
|
||||||
|
|
||||||
export default async function ({
|
export default async function ({
|
||||||
applicationId,
|
applicationId,
|
||||||
workdir,
|
workdir,
|
||||||
repodir,
|
repodir,
|
||||||
htmlUrl,
|
htmlUrl,
|
||||||
|
gitCommitHash,
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
buildId,
|
buildId,
|
||||||
@@ -20,34 +21,43 @@ export default async function ({
|
|||||||
branch: string;
|
branch: string;
|
||||||
buildId: string;
|
buildId: string;
|
||||||
repodir: string;
|
repodir: string;
|
||||||
|
gitCommitHash: string;
|
||||||
privateSshKey: string;
|
privateSshKey: string;
|
||||||
customPort: number;
|
customPort: number;
|
||||||
forPublic: boolean;
|
forPublic: boolean;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||||
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
|
|
||||||
|
|
||||||
if (!forPublic) {
|
if (!forPublic) {
|
||||||
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
|
await executeCommand({ command: `echo '${privateSshKey}' > ${repodir}/id.rsa`, shell: true });
|
||||||
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
|
await executeCommand({ command: `chmod 600 ${repodir}/id.rsa` });
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveBuildLog({
|
await saveBuildLog({
|
||||||
line: `Cloning ${repository}:${branch} branch.`,
|
line: `Cloning ${repository}:${branch}...`,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId
|
applicationId
|
||||||
});
|
});
|
||||||
|
if (gitCommitHash) {
|
||||||
|
await saveBuildLog({
|
||||||
|
line: `Checking out ${gitCommitHash} commit...`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
|
}
|
||||||
if (forPublic) {
|
if (forPublic) {
|
||||||
await asyncExecShell(
|
await executeCommand({
|
||||||
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
command:
|
||||||
|
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await asyncExecShell(
|
await executeCommand({
|
||||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
|
command:
|
||||||
|
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
|
const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
|
||||||
return commit.replace('\n', '');
|
return commit.replace('\n', '');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,16 @@ Bree.extend(TSBree);
|
|||||||
|
|
||||||
const options: any = {
|
const options: any = {
|
||||||
defaultExtension: 'js',
|
defaultExtension: 'js',
|
||||||
logger: new Cabin(),
|
logger: false,
|
||||||
// logger: false,
|
// logger: false,
|
||||||
workerMessageHandler: async ({ name, message }) => {
|
// workerMessageHandler: async ({ name, message }) => {
|
||||||
if (name === 'deployApplication' && message?.deploying) {
|
// if (name === 'deployApplication' && message?.deploying) {
|
||||||
if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) {
|
// if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) {
|
||||||
scheduler.workers.get('deployApplication').postMessage('cancel')
|
// scheduler.workers.get('deployApplication').postMessage('cancel')
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
jobs: [
|
jobs: [
|
||||||
{ name: 'infrastructure' },
|
|
||||||
{ name: 'deployApplication' },
|
{ name: 'deployApplication' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,47 @@
|
|||||||
import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common";
|
import { isARM, isDev } from './common';
|
||||||
|
import fs from 'fs/promises';
|
||||||
export async function defaultServiceConfigurations({ id, teamId }) {
|
export async function getTemplates() {
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const templatePath = isDev ? './templates.json' : '/app/templates.json';
|
||||||
const { destinationDockerId, destinationDocker, type, serviceSecret } = service;
|
const open = await fs.open(templatePath, 'r');
|
||||||
|
try {
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
let data = await open.readFile({ encoding: 'utf-8' });
|
||||||
const port = getServiceMainPort(type);
|
let jsonData = JSON.parse(data);
|
||||||
|
if (isARM(process.arch)) {
|
||||||
const { workdir } = await createDirectories({ repository: type, buildId: id });
|
jsonData = jsonData.filter((d) => d.arch !== 'amd64');
|
||||||
|
}
|
||||||
const image = getServiceImage(type);
|
return jsonData;
|
||||||
let secrets = [];
|
} catch (error) {
|
||||||
if (serviceSecret.length > 0) {
|
return [];
|
||||||
serviceSecret.forEach((secret) => {
|
} finally {
|
||||||
secrets.push(`${secret.name}=${secret.value}`);
|
await open?.close();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
return { ...service, network, port, workdir, image, secrets }
|
const compareSemanticVersions = (a: string, b: string) => {
|
||||||
}
|
const a1 = a.split('.');
|
||||||
|
const b1 = b.split('.');
|
||||||
|
const len = Math.min(a1.length, b1.length);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const a2 = +a1[i] || 0;
|
||||||
|
const b2 = +b1[i] || 0;
|
||||||
|
if (a2 !== b2) {
|
||||||
|
return a2 > b2 ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b1.length - a1.length;
|
||||||
|
};
|
||||||
|
export async function getTags(type: string) {
|
||||||
|
try {
|
||||||
|
if (type) {
|
||||||
|
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||||
|
const data = await fs.readFile(tagsPath, 'utf8');
|
||||||
|
let tags = JSON.parse(data);
|
||||||
|
if (tags) {
|
||||||
|
tags = tags.find((tag: any) => tag.name.includes(type));
|
||||||
|
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,367 +1,9 @@
|
|||||||
|
|
||||||
import cuid from 'cuid';
|
import { decrypt, prisma } from '../common';
|
||||||
import { encrypt, generatePassword, prisma } from '../common';
|
|
||||||
|
|
||||||
export const includeServices: any = {
|
|
||||||
destinationDocker: true,
|
|
||||||
persistentStorage: true,
|
|
||||||
serviceSecret: true,
|
|
||||||
minio: true,
|
|
||||||
plausibleAnalytics: true,
|
|
||||||
vscodeserver: true,
|
|
||||||
wordpress: true,
|
|
||||||
ghost: true,
|
|
||||||
meiliSearch: true,
|
|
||||||
umami: true,
|
|
||||||
hasura: true,
|
|
||||||
fider: true,
|
|
||||||
moodle: true,
|
|
||||||
appwrite: true,
|
|
||||||
glitchTip: true,
|
|
||||||
searxng: true,
|
|
||||||
weblate: true,
|
|
||||||
taiga: true,
|
|
||||||
};
|
|
||||||
export async function configureServiceType({
|
|
||||||
id,
|
|
||||||
type
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
if (type === 'plausibleanalytics') {
|
|
||||||
const password = encrypt(generatePassword({}));
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'plausibleanalytics';
|
|
||||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
|
||||||
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
plausibleAnalytics: {
|
|
||||||
create: {
|
|
||||||
postgresqlDatabase,
|
|
||||||
postgresqlUser,
|
|
||||||
postgresqlPassword,
|
|
||||||
password,
|
|
||||||
secretKeyBase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'nocodb') {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: { type }
|
|
||||||
});
|
|
||||||
} else if (type === 'minio') {
|
|
||||||
const rootUser = cuid();
|
|
||||||
const rootUserPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
|
||||||
});
|
|
||||||
} else if (type === 'vscodeserver') {
|
|
||||||
const password = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: { type, vscodeserver: { create: { password } } }
|
|
||||||
});
|
|
||||||
} else if (type === 'wordpress') {
|
|
||||||
const mysqlUser = cuid();
|
|
||||||
const mysqlPassword = encrypt(generatePassword({}));
|
|
||||||
const mysqlRootUser = cuid();
|
|
||||||
const mysqlRootUserPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'vaultwarden') {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'languagetool') {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'n8n') {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'uptimekuma') {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'ghost') {
|
|
||||||
const defaultEmail = `${cuid()}@example.com`;
|
|
||||||
const defaultPassword = encrypt(generatePassword({}));
|
|
||||||
const mariadbUser = cuid();
|
|
||||||
const mariadbPassword = encrypt(generatePassword({}));
|
|
||||||
const mariadbRootUser = cuid();
|
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
|
||||||
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
ghost: {
|
|
||||||
create: {
|
|
||||||
defaultEmail,
|
|
||||||
defaultPassword,
|
|
||||||
mariadbUser,
|
|
||||||
mariadbPassword,
|
|
||||||
mariadbRootUser,
|
|
||||||
mariadbRootUserPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'meilisearch') {
|
|
||||||
const masterKey = encrypt(generatePassword({ length: 32 }));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
meiliSearch: { create: { masterKey } }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'umami') {
|
|
||||||
const umamiAdminPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'umami';
|
|
||||||
const hashSalt = encrypt(generatePassword({ length: 64 }));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
umami: {
|
|
||||||
create: {
|
|
||||||
umamiAdminPassword,
|
|
||||||
postgresqlDatabase,
|
|
||||||
postgresqlPassword,
|
|
||||||
postgresqlUser,
|
|
||||||
hashSalt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'hasura') {
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'hasura';
|
|
||||||
const graphQLAdminPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
hasura: {
|
|
||||||
create: {
|
|
||||||
postgresqlDatabase,
|
|
||||||
postgresqlPassword,
|
|
||||||
postgresqlUser,
|
|
||||||
graphQLAdminPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'fider') {
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'fider';
|
|
||||||
const jwtSecret = encrypt(generatePassword({ length: 64, symbols: true }));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
fider: {
|
|
||||||
create: {
|
|
||||||
postgresqlDatabase,
|
|
||||||
postgresqlPassword,
|
|
||||||
postgresqlUser,
|
|
||||||
jwtSecret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'moodle') {
|
|
||||||
const defaultUsername = cuid();
|
|
||||||
const defaultPassword = encrypt(generatePassword({}));
|
|
||||||
const defaultEmail = `${cuid()} @example.com`;
|
|
||||||
const mariadbUser = cuid();
|
|
||||||
const mariadbPassword = encrypt(generatePassword({}));
|
|
||||||
const mariadbDatabase = 'moodle_db';
|
|
||||||
const mariadbRootUser = cuid();
|
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
moodle: {
|
|
||||||
create: {
|
|
||||||
defaultUsername,
|
|
||||||
defaultPassword,
|
|
||||||
defaultEmail,
|
|
||||||
mariadbUser,
|
|
||||||
mariadbPassword,
|
|
||||||
mariadbDatabase,
|
|
||||||
mariadbRootUser,
|
|
||||||
mariadbRootUserPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'appwrite') {
|
|
||||||
const opensslKeyV1 = encrypt(generatePassword({}));
|
|
||||||
const executorSecret = encrypt(generatePassword({}));
|
|
||||||
const redisPassword = encrypt(generatePassword({}));
|
|
||||||
const mariadbHost = `${id}-mariadb`
|
|
||||||
const mariadbUser = cuid();
|
|
||||||
const mariadbPassword = encrypt(generatePassword({}));
|
|
||||||
const mariadbDatabase = 'appwrite';
|
|
||||||
const mariadbRootUser = cuid();
|
|
||||||
const mariadbRootUserPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
appwrite: {
|
|
||||||
create: {
|
|
||||||
opensslKeyV1,
|
|
||||||
executorSecret,
|
|
||||||
redisPassword,
|
|
||||||
mariadbHost,
|
|
||||||
mariadbUser,
|
|
||||||
mariadbPassword,
|
|
||||||
mariadbDatabase,
|
|
||||||
mariadbRootUser,
|
|
||||||
mariadbRootUserPassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'glitchTip') {
|
|
||||||
const defaultUsername = cuid();
|
|
||||||
const defaultEmail = `${defaultUsername}@example.com`;
|
|
||||||
const defaultPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'glitchTip';
|
|
||||||
const secretKeyBase = encrypt(generatePassword({ length: 64 }));
|
|
||||||
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
glitchTip: {
|
|
||||||
create: {
|
|
||||||
postgresqlDatabase,
|
|
||||||
postgresqlUser,
|
|
||||||
postgresqlPassword,
|
|
||||||
secretKeyBase,
|
|
||||||
defaultEmail,
|
|
||||||
defaultUsername,
|
|
||||||
defaultPassword,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'searxng') {
|
|
||||||
const secretKey = encrypt(generatePassword({ length: 32, isHex: true }))
|
|
||||||
const redisPassword = encrypt(generatePassword({}));
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
searxng: {
|
|
||||||
create: {
|
|
||||||
secretKey,
|
|
||||||
redisPassword,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'weblate') {
|
|
||||||
const adminPassword = encrypt(generatePassword({}))
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'weblate';
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
weblate: {
|
|
||||||
create: {
|
|
||||||
adminPassword,
|
|
||||||
postgresqlHost: `${id}-postgresql`,
|
|
||||||
postgresqlPort: 5432,
|
|
||||||
postgresqlUser,
|
|
||||||
postgresqlPassword,
|
|
||||||
postgresqlDatabase,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (type === 'taiga') {
|
|
||||||
const secretKey = encrypt(generatePassword({}))
|
|
||||||
const erlangSecret = encrypt(generatePassword({}))
|
|
||||||
const rabbitMQUser = cuid();
|
|
||||||
const djangoAdminUser = cuid();
|
|
||||||
const djangoAdminPassword = encrypt(generatePassword({}))
|
|
||||||
const rabbitMQPassword = encrypt(generatePassword({}))
|
|
||||||
const postgresqlUser = cuid();
|
|
||||||
const postgresqlPassword = encrypt(generatePassword({}));
|
|
||||||
const postgresqlDatabase = 'taiga';
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type,
|
|
||||||
taiga: {
|
|
||||||
create: {
|
|
||||||
secretKey,
|
|
||||||
erlangSecret,
|
|
||||||
djangoAdminUser,
|
|
||||||
djangoAdminPassword,
|
|
||||||
rabbitMQUser,
|
|
||||||
rabbitMQPassword,
|
|
||||||
postgresqlHost: `${id}-postgresql`,
|
|
||||||
postgresqlPort: 5432,
|
|
||||||
postgresqlUser,
|
|
||||||
postgresqlPassword,
|
|
||||||
postgresqlDatabase,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await prisma.service.update({
|
|
||||||
where: { id },
|
|
||||||
data: {
|
|
||||||
type
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeService({ id }: { id: string }): Promise<void> {
|
export async function removeService({ id }: { id: string }): Promise<void> {
|
||||||
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
|
||||||
|
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
await prisma.fider.deleteMany({ where: { serviceId: id } });
|
||||||
@@ -380,4 +22,18 @@ export async function removeService({ id }: { id: string }): Promise<void> {
|
|||||||
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
await prisma.taiga.deleteMany({ where: { serviceId: id } });
|
||||||
|
|
||||||
await prisma.service.delete({ where: { id } });
|
await prisma.service.delete({ where: { id } });
|
||||||
|
}
|
||||||
|
export async function verifyAndDecryptServiceSecrets(id: string) {
|
||||||
|
const secrets = await prisma.serviceSecret.findMany({ where: { serviceId: id } })
|
||||||
|
let decryptedSecrets = secrets.map(secret => {
|
||||||
|
const { name, value } = secret
|
||||||
|
if (value) {
|
||||||
|
let rawValue = decrypt(value)
|
||||||
|
rawValue = rawValue.replaceAll(/\$/gi, '$$$')
|
||||||
|
return { name, value: rawValue }
|
||||||
|
}
|
||||||
|
return { name, value }
|
||||||
|
|
||||||
|
})
|
||||||
|
return decryptedSecrets
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,258 +0,0 @@
|
|||||||
/*
|
|
||||||
Example of a supported version:
|
|
||||||
{
|
|
||||||
// Name used to identify the service internally
|
|
||||||
name: 'umami',
|
|
||||||
// Fancier name to show to the user
|
|
||||||
fancyName: 'Umami',
|
|
||||||
// Docker base image for the service
|
|
||||||
baseImage: 'ghcr.io/mikecao/umami',
|
|
||||||
// Optional: If there is any dependent image, you should list it here
|
|
||||||
images: [],
|
|
||||||
// Usable tags
|
|
||||||
versions: ['postgresql-latest'],
|
|
||||||
// Which tag is the recommended
|
|
||||||
recommendedVersion: 'postgresql-latest',
|
|
||||||
// Application's default port, Umami listens on 3000
|
|
||||||
ports: {
|
|
||||||
main: 3000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
export const supportedServiceTypesAndVersions = [
|
|
||||||
{
|
|
||||||
name: 'plausibleanalytics',
|
|
||||||
fancyName: 'Plausible Analytics',
|
|
||||||
baseImage: 'plausible/analytics',
|
|
||||||
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
|
|
||||||
versions: ['latest', 'stable'],
|
|
||||||
recommendedVersion: 'stable',
|
|
||||||
ports: {
|
|
||||||
main: 8000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nocodb',
|
|
||||||
fancyName: 'NocoDB',
|
|
||||||
baseImage: 'nocodb/nocodb',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minio',
|
|
||||||
fancyName: 'MinIO',
|
|
||||||
baseImage: 'minio/minio',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 9001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vscodeserver',
|
|
||||||
fancyName: 'VSCode Server',
|
|
||||||
baseImage: 'codercom/code-server',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wordpress',
|
|
||||||
fancyName: 'Wordpress',
|
|
||||||
baseImage: 'wordpress',
|
|
||||||
images: ['bitnami/mysql:5.7'],
|
|
||||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vaultwarden',
|
|
||||||
fancyName: 'Vaultwarden',
|
|
||||||
baseImage: 'vaultwarden/server',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'languagetool',
|
|
||||||
fancyName: 'LanguageTool',
|
|
||||||
baseImage: 'silviof/docker-languagetool',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8010
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'n8n',
|
|
||||||
fancyName: 'n8n',
|
|
||||||
baseImage: 'n8nio/n8n',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 5678
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'uptimekuma',
|
|
||||||
fancyName: 'Uptime Kuma',
|
|
||||||
baseImage: 'louislam/uptime-kuma',
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 3001
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ghost',
|
|
||||||
fancyName: 'Ghost',
|
|
||||||
baseImage: 'bitnami/ghost',
|
|
||||||
images: ['bitnami/mariadb'],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 2368
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'meilisearch',
|
|
||||||
fancyName: 'Meilisearch',
|
|
||||||
baseImage: 'getmeili/meilisearch',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 7700
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'umami',
|
|
||||||
fancyName: 'Umami',
|
|
||||||
baseImage: 'ghcr.io/umami-software/umami',
|
|
||||||
images: ['postgres:12-alpine'],
|
|
||||||
versions: ['postgresql-latest'],
|
|
||||||
recommendedVersion: 'postgresql-latest',
|
|
||||||
ports: {
|
|
||||||
main: 3000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'hasura',
|
|
||||||
fancyName: 'Hasura',
|
|
||||||
baseImage: 'hasura/graphql-engine',
|
|
||||||
images: ['postgres:12-alpine'],
|
|
||||||
versions: ['latest', 'v2.10.0', 'v2.5.1'],
|
|
||||||
recommendedVersion: 'v2.10.0',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'fider',
|
|
||||||
fancyName: 'Fider',
|
|
||||||
baseImage: 'getfider/fider',
|
|
||||||
images: ['postgres:12-alpine'],
|
|
||||||
versions: ['stable'],
|
|
||||||
recommendedVersion: 'stable',
|
|
||||||
ports: {
|
|
||||||
main: 3000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'appwrite',
|
|
||||||
fancyName: 'Appwrite',
|
|
||||||
baseImage: 'appwrite/appwrite',
|
|
||||||
images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'],
|
|
||||||
versions: ['latest', '1.0', '0.15.3'],
|
|
||||||
recommendedVersion: '1.0',
|
|
||||||
ports: {
|
|
||||||
main: 80
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// name: 'moodle',
|
|
||||||
// fancyName: 'Moodle',
|
|
||||||
// baseImage: 'bitnami/moodle',
|
|
||||||
// images: [],
|
|
||||||
// versions: ['latest', 'v4.0.2'],
|
|
||||||
// recommendedVersion: 'latest',
|
|
||||||
// ports: {
|
|
||||||
// main: 8080
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
{
|
|
||||||
name: 'glitchTip',
|
|
||||||
fancyName: 'GlitchTip',
|
|
||||||
baseImage: 'glitchtip/glitchtip',
|
|
||||||
images: ['postgres:14-alpine', 'redis:7-alpine'],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'searxng',
|
|
||||||
fancyName: 'SearXNG',
|
|
||||||
baseImage: 'searxng/searxng',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'weblate',
|
|
||||||
fancyName: 'Weblate',
|
|
||||||
baseImage: 'weblate/weblate',
|
|
||||||
images: ['postgres:14-alpine', 'redis:6-alpine'],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// name: 'taiga',
|
|
||||||
// fancyName: 'Taiga',
|
|
||||||
// baseImage: 'taigaio/taiga-front',
|
|
||||||
// images: ['postgres:12.3', 'rabbitmq:3.8-management-alpine', 'taigaio/taiga-back', 'taigaio/taiga-events', 'taigaio/taiga-protected'],
|
|
||||||
// versions: ['latest'],
|
|
||||||
// recommendedVersion: 'latest',
|
|
||||||
// ports: {
|
|
||||||
// main: 80
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
name: 'grafana',
|
|
||||||
fancyName: 'Grafana',
|
|
||||||
baseImage: 'grafana/grafana',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest', '9.1.3', '9.1.2', '9.0.8', '8.3.11', '8.4.11', '8.5.11'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 3000
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'trilium',
|
|
||||||
fancyName: 'Trilium Notes',
|
|
||||||
baseImage: 'zadam/trilium',
|
|
||||||
images: [],
|
|
||||||
versions: ['latest'],
|
|
||||||
recommendedVersion: 'latest',
|
|
||||||
ports: {
|
|
||||||
main: 8080
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
29
apps/api/src/realtime/index.ts
Normal file
29
apps/api/src/realtime/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
export default async (fastify) => {
|
||||||
|
fastify.io.use((socket, next) => {
|
||||||
|
const { token } = socket.handshake.auth;
|
||||||
|
if (token && fastify.jwt.verify(token)) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
return next(new Error("unauthorized event"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fastify.io.on('connection', (socket: any) => {
|
||||||
|
const { token } = socket.handshake.auth;
|
||||||
|
const { teamId } = fastify.jwt.decode(token);
|
||||||
|
socket.join(teamId);
|
||||||
|
// console.info('Socket connected!', socket.id)
|
||||||
|
// console.info('Socket joined team!', teamId)
|
||||||
|
// socket.on('message', (message) => {
|
||||||
|
// console.log(message)
|
||||||
|
// })
|
||||||
|
// socket.on('error', (err) => {
|
||||||
|
// console.log(err)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
// fastify.io.on("error", (err) => {
|
||||||
|
// if (err && err.message === "unauthorized event") {
|
||||||
|
// fastify.io.disconnect();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import crypto from 'node:crypto'
|
import crypto from 'node:crypto'
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import axios from 'axios';
|
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import csv from 'csvtojson';
|
import csv from 'csvtojson';
|
||||||
|
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
import { makeLabelForStandaloneApplication, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
import { saveDockerRegistryCredentials, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
|
||||||
import { checkDomainsIsValidInDNS, checkDoubleBranch, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
|
||||||
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
|
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
|
||||||
|
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds } from './types';
|
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds, RestartApplication } from './types';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
function filterObject(obj, callback) {
|
function filterObject(obj, callback) {
|
||||||
@@ -79,7 +78,7 @@ export async function cleanupUnconfiguredApplications(request: FastifyRequest<an
|
|||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) {
|
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) {
|
||||||
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
if (application?.destinationDockerId && application.destinationDocker?.network) {
|
||||||
const { stdout: containers } = await executeDockerCmd({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: application.destinationDocker.id,
|
dockerId: application.destinationDocker.id,
|
||||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
|
||||||
})
|
})
|
||||||
@@ -110,23 +109,64 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
let isRunning = false;
|
let payload = []
|
||||||
let isExited = false;
|
|
||||||
let isRestarting = false;
|
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
const status = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
|
if (application.buildPack === 'compose') {
|
||||||
if (status?.found) {
|
const { stdout: containers } = await executeCommand({
|
||||||
isRunning = status.status.isRunning;
|
dockerId: application.destinationDocker.id,
|
||||||
isExited = status.status.isExited;
|
command:
|
||||||
isRestarting = status.status.isRestarting
|
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||||
|
});
|
||||||
|
const containersArray = containers.trim().split('\n');
|
||||||
|
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||||
|
for (const container of containersArray) {
|
||||||
|
let isRunning = false;
|
||||||
|
let isExited = false;
|
||||||
|
let isRestarting = false;
|
||||||
|
const containerObj = JSON.parse(container);
|
||||||
|
const status = containerObj.State
|
||||||
|
if (status === 'running') {
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
if (status === 'exited') {
|
||||||
|
isExited = true;
|
||||||
|
}
|
||||||
|
if (status === 'restarting') {
|
||||||
|
isRestarting = true;
|
||||||
|
}
|
||||||
|
payload.push({
|
||||||
|
name: containerObj.Names,
|
||||||
|
status: {
|
||||||
|
isRunning,
|
||||||
|
isExited,
|
||||||
|
isRestarting
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let isRunning = false;
|
||||||
|
let isExited = false;
|
||||||
|
let isRestarting = false;
|
||||||
|
const status = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
|
||||||
|
if (status?.found) {
|
||||||
|
isRunning = status.status.isRunning;
|
||||||
|
isExited = status.status.isExited;
|
||||||
|
isRestarting = status.status.isRestarting
|
||||||
|
payload.push({
|
||||||
|
name: id,
|
||||||
|
status: {
|
||||||
|
isRunning,
|
||||||
|
isExited,
|
||||||
|
isRestarting
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return payload
|
||||||
isRunning,
|
|
||||||
isRestarting,
|
|
||||||
isExited,
|
|
||||||
};
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
@@ -201,7 +241,8 @@ export async function getApplicationFromDB(id: string, teamId: string) {
|
|||||||
secrets: true,
|
secrets: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
connectedDatabase: true,
|
connectedDatabase: true,
|
||||||
previewApplication: true
|
previewApplication: true,
|
||||||
|
dockerRegistry: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!application) {
|
if (!application) {
|
||||||
@@ -240,7 +281,7 @@ export async function getApplicationFromDBWebhook(projectId: number, branch: str
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (applications.length === 0) {
|
if (applications.length === 0) {
|
||||||
throw { status: 500, message: 'Application not configured.' }
|
throw { status: 500, message: 'Application not configured.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
applications = applications.map((application: any) => {
|
applications = applications.map((application: any) => {
|
||||||
application = decryptApplication(application);
|
application = decryptApplication(application);
|
||||||
@@ -262,8 +303,8 @@ export async function getApplicationFromDBWebhook(projectId: number, branch: str
|
|||||||
|
|
||||||
return applications;
|
return applications;
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message, type }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message, type })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function saveApplication(request: FastifyRequest<SaveApplication>, reply: FastifyReply) {
|
export async function saveApplication(request: FastifyRequest<SaveApplication>, reply: FastifyReply) {
|
||||||
@@ -286,16 +327,21 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
denoMainFile,
|
denoMainFile,
|
||||||
denoOptions,
|
denoOptions,
|
||||||
|
gitCommitHash,
|
||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
baseDatabaseBranch
|
baseDatabaseBranch,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
simpleDockerfile,
|
||||||
|
dockerRegistryImageName
|
||||||
} = request.body
|
} = request.body
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
exposePort = Number(exposePort);
|
exposePort = Number(exposePort);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
||||||
if (denoOptions) denoOptions = denoOptions.trim();
|
if (denoOptions) denoOptions = denoOptions.trim();
|
||||||
@@ -308,6 +354,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
publishDirectory,
|
publishDirectory,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
dockerFileLocation,
|
dockerFileLocation,
|
||||||
|
dockerComposeFileLocation,
|
||||||
denoMainFile
|
denoMainFile
|
||||||
});
|
});
|
||||||
if (baseDatabaseBranch) {
|
if (baseDatabaseBranch) {
|
||||||
@@ -322,8 +369,14 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
pythonVariable,
|
pythonVariable,
|
||||||
denoOptions,
|
denoOptions,
|
||||||
baseImage,
|
baseImage,
|
||||||
|
gitCommitHash,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
simpleDockerfile,
|
||||||
|
dockerRegistryImageName,
|
||||||
...defaultConfiguration,
|
...defaultConfiguration,
|
||||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||||
}
|
}
|
||||||
@@ -337,11 +390,17 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
|
|||||||
exposePort,
|
exposePort,
|
||||||
pythonWSGI,
|
pythonWSGI,
|
||||||
pythonModule,
|
pythonModule,
|
||||||
|
gitCommitHash,
|
||||||
pythonVariable,
|
pythonVariable,
|
||||||
denoOptions,
|
denoOptions,
|
||||||
baseImage,
|
baseImage,
|
||||||
baseBuildImage,
|
baseBuildImage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
|
dockerComposeFile,
|
||||||
|
dockerComposeFileLocation,
|
||||||
|
dockerComposeConfiguration,
|
||||||
|
simpleDockerfile,
|
||||||
|
dockerRegistryImageName,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -390,16 +449,17 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function restartApplication(request: FastifyRequest<RestartApplication>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
const { imageId = null } = request.body
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
let application: any = await getApplicationFromDB(id, teamId);
|
let application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
const { id: dockerId, network } = application.destinationDocker;
|
const { id: dockerId, network } = application.destinationDocker;
|
||||||
const { secrets, pullmergeRequestId, port, repository, persistentStorage, id: applicationId, buildPack, exposePort } = application;
|
const { dockerRegistry, secrets, pullmergeRequestId, port, repository, persistentStorage, id: applicationId, buildPack, exposePort } = application;
|
||||||
|
let location = null;
|
||||||
const envs = [
|
const envs = [
|
||||||
`PORT=${port}`
|
`PORT=${port}`
|
||||||
];
|
];
|
||||||
@@ -408,13 +468,13 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -422,28 +482,48 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
|
|||||||
const { workdir } = await createDirectories({ repository, buildId });
|
const { workdir } = await createDirectories({ repository, buildId });
|
||||||
const labels = []
|
const labels = []
|
||||||
let image = null
|
let image = null
|
||||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
if (imageId) {
|
||||||
const containersArray = container.trim().split('\n');
|
image = imageId
|
||||||
for (const container of containersArray) {
|
} else {
|
||||||
const containerObj = formatLabelsOnDocker(container);
|
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
|
||||||
image = containerObj[0].Image
|
const containersArray = container.trim().split('\n');
|
||||||
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
for (const container of containersArray) {
|
||||||
if (key.startsWith('coolify')) {
|
const containerObj = formatLabelsOnDocker(container);
|
||||||
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
image = containerObj[0].Image
|
||||||
}
|
Object.keys(containerObj[0].Labels).forEach(function (key) {
|
||||||
})
|
if (key.startsWith('coolify')) {
|
||||||
|
labels.push(`${key}=${containerObj[0].Labels[key]}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let imageFound = false;
|
if (dockerRegistry) {
|
||||||
|
const { url, username, password } = dockerRegistry
|
||||||
|
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageFoundLocally = false;
|
||||||
try {
|
try {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker image inspect ${image}`
|
command: `docker image inspect ${image}`
|
||||||
})
|
})
|
||||||
imageFound = true;
|
imageFoundLocally = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
if (!imageFound) {
|
let imageFoundRemotely = false;
|
||||||
|
try {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker ${location ? `--config ${location}` : ''} pull ${image}`
|
||||||
|
})
|
||||||
|
imageFoundRemotely = true;
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageFoundLocally && !imageFoundRemotely) {
|
||||||
throw { status: 500, message: 'Image not found, cannot restart application.' }
|
throw { status: 500, message: 'Image not found, cannot restart application.' }
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
@@ -489,9 +569,14 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
|
|||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
};
|
};
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
|
try {
|
||||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
|
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
await executeCommand({ dockerId, command: `docker rm ${id}` })
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
await executeCommand({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Application cannot be restarted.' }
|
throw { status: 500, message: 'Application cannot be restarted.' }
|
||||||
@@ -506,6 +591,21 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
|
|||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
const { id: dockerId } = application.destinationDocker;
|
const { id: dockerId } = application.destinationDocker;
|
||||||
|
if (application.buildPack === 'compose') {
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: application.destinationDocker.id,
|
||||||
|
command:
|
||||||
|
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
|
||||||
|
});
|
||||||
|
const containersArray = containers.trim().split('\n');
|
||||||
|
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||||
|
for (const container of containersArray) {
|
||||||
|
const containerObj = JSON.parse(container);
|
||||||
|
await removeContainer({ id: containerObj.ID, dockerId: application.destinationDocker.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
const { found } = await checkContainer({ dockerId, container: id });
|
const { found } = await checkContainer({ dockerId, container: id });
|
||||||
if (found) {
|
if (found) {
|
||||||
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
await removeContainer({ id, dockerId: application.destinationDocker.id });
|
||||||
@@ -527,7 +627,7 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
|
|||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true }
|
||||||
});
|
});
|
||||||
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
||||||
const { stdout: containers } = await executeDockerCmd({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: application.destinationDocker.id,
|
dockerId: application.destinationDocker.id,
|
||||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||||
})
|
})
|
||||||
@@ -613,6 +713,65 @@ export async function getUsage(request) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function getDockerImages(request) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const teamId = request.user?.teamId;
|
||||||
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
let imagesAvailables = [];
|
||||||
|
try {
|
||||||
|
const { stdout } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache`, shell: true });
|
||||||
|
const { stdout: runningImage } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` });
|
||||||
|
const images = stdout.trim().split('\n');
|
||||||
|
|
||||||
|
for (const image of images) {
|
||||||
|
const [repository, tag, createdAt] = image.split('#');
|
||||||
|
if (tag.includes('-')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const [year, time] = createdAt.split(' ');
|
||||||
|
imagesAvailables.push({
|
||||||
|
repository,
|
||||||
|
tag,
|
||||||
|
createdAt: day(year + time).unix()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesAvailables = imagesAvailables.sort((a, b) => b.tag - a.tag);
|
||||||
|
|
||||||
|
return {
|
||||||
|
imagesAvailables,
|
||||||
|
runningImage
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
imagesAvailables,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsageByContainer(request) {
|
||||||
|
try {
|
||||||
|
const { id, containerId } = request.params
|
||||||
|
const teamId = request.user?.teamId;
|
||||||
|
let usage = {};
|
||||||
|
|
||||||
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
|
if (application.destinationDockerId) {
|
||||||
|
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.id, containerId)]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
usage
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function deployApplication(request: FastifyRequest<DeployApplication>) {
|
export async function deployApplication(request: FastifyRequest<DeployApplication>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
@@ -637,22 +796,37 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
await prisma.application.update({ where: { id }, data: { configHash } });
|
await prisma.application.update({ where: { id }, data: { configHash } });
|
||||||
}
|
}
|
||||||
await prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
|
await prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
|
||||||
await prisma.build.create({
|
if (application.gitSourceId) {
|
||||||
data: {
|
await prisma.build.create({
|
||||||
id: buildId,
|
data: {
|
||||||
applicationId: id,
|
id: buildId,
|
||||||
sourceBranch: branch,
|
applicationId: id,
|
||||||
branch: application.branch,
|
sourceBranch: branch,
|
||||||
pullmergeRequestId: pullmergeRequestId?.toString(),
|
branch: application.branch,
|
||||||
forceRebuild,
|
pullmergeRequestId: pullmergeRequestId?.toString(),
|
||||||
destinationDockerId: application.destinationDocker?.id,
|
forceRebuild,
|
||||||
gitSourceId: application.gitSource?.id,
|
destinationDockerId: application.destinationDocker?.id,
|
||||||
githubAppId: application.gitSource?.githubApp?.id,
|
gitSourceId: application.gitSource?.id,
|
||||||
gitlabAppId: application.gitSource?.gitlabApp?.id,
|
githubAppId: application.gitSource?.githubApp?.id,
|
||||||
status: 'queued',
|
gitlabAppId: application.gitSource?.gitlabApp?.id,
|
||||||
type: pullmergeRequestId ? application.gitSource?.githubApp?.id ? 'manual_pr' : 'manual_mr' : 'manual'
|
status: 'queued',
|
||||||
}
|
type: pullmergeRequestId ? application.gitSource?.githubApp?.id ? 'manual_pr' : 'manual_mr' : 'manual'
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await prisma.build.create({
|
||||||
|
data: {
|
||||||
|
id: buildId,
|
||||||
|
applicationId: id,
|
||||||
|
branch: 'latest',
|
||||||
|
forceRebuild,
|
||||||
|
destinationDockerId: application.destinationDocker?.id,
|
||||||
|
status: 'queued',
|
||||||
|
type: 'manual'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buildId
|
buildId
|
||||||
};
|
};
|
||||||
@@ -667,20 +841,28 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
|
|||||||
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { gitSourceId, forPublic, type } = request.body
|
const { gitSourceId, forPublic, type, simpleDockerfile } = request.body
|
||||||
if (forPublic) {
|
if (forPublic) {
|
||||||
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
|
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { gitSource: { connect: { id: publicGit.id } } }
|
data: { gitSource: { connect: { id: publicGit.id } } }
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
|
if (simpleDockerfile) {
|
||||||
|
await prisma.application.update({
|
||||||
|
where: { id },
|
||||||
|
data: { simpleDockerfile, settings: { update: { autodeploy: false } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (gitSourceId) {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -689,6 +871,7 @@ export async function saveApplicationSource(request: FastifyRequest<SaveApplicat
|
|||||||
|
|
||||||
export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
@@ -700,13 +883,13 @@ export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: Fas
|
|||||||
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
const githubToken = jsonwebtoken.sign(payload, application.gitSource.githubApp.privateKey, {
|
||||||
algorithm: 'RS256'
|
algorithm: 'RS256'
|
||||||
});
|
});
|
||||||
const { data } = await axios.post(`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`, {}, {
|
const { token } = await got.post(`${application.gitSource.apiUrl}/app/installations/${application.gitSource.githubApp.installationId}/access_tokens`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${githubToken}`
|
'Authorization': `Bearer ${githubToken}`,
|
||||||
}
|
}
|
||||||
})
|
}).json()
|
||||||
return reply.code(201).send({
|
return reply.code(201).send({
|
||||||
token: data.token
|
token
|
||||||
})
|
})
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -737,7 +920,7 @@ export async function saveRepository(request, reply) {
|
|||||||
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
let { repository, branch, projectId, autodeploy, webhookToken, isPublicRepository = false } = request.body
|
||||||
|
|
||||||
repository = repository.toLowerCase();
|
repository = repository.toLowerCase();
|
||||||
branch = branch.toLowerCase();
|
|
||||||
projectId = Number(projectId);
|
projectId = Number(projectId);
|
||||||
if (webhookToken) {
|
if (webhookToken) {
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
@@ -782,11 +965,11 @@ export async function getBuildPack(request) {
|
|||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
return {
|
return {
|
||||||
type: application.gitSource.type,
|
type: application.gitSource?.type || 'dockerRegistry',
|
||||||
projectId: application.projectId,
|
projectId: application.projectId,
|
||||||
repository: application.repository,
|
repository: application.repository,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
apiUrl: application.gitSource.apiUrl,
|
apiUrl: application.gitSource?.apiUrl || null,
|
||||||
isPublicRepository: application.settings.isPublicRepository
|
isPublicRepository: application.settings.isPublicRepository
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -794,6 +977,16 @@ export async function getBuildPack(request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveRegistry(request, reply) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params
|
||||||
|
const { registryId } = request.body
|
||||||
|
await prisma.application.update({ where: { id }, data: { dockerRegistry: { connect: { id: registryId } } } });
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function saveBuildPack(request, reply) {
|
export async function saveBuildPack(request, reply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
@@ -888,6 +1081,10 @@ export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: Fas
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { name, value, isBuildSecret = false } = request.body
|
const { name, value, isBuildSecret = false } = request.body
|
||||||
|
const found = await prisma.secret.findMany({ where: { applicationId: id, name } })
|
||||||
|
if (found.length > 0) {
|
||||||
|
throw ({ message: 'Secret already exists.' })
|
||||||
|
}
|
||||||
await prisma.secret.create({
|
await prisma.secret.create({
|
||||||
data: { name, value: encrypt(value.trim()), isBuildSecret, isPRMRSecret: false, application: { connect: { id } } }
|
data: { name, value: encrypt(value.trim()), isBuildSecret, isPRMRSecret: false, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
@@ -972,13 +1169,13 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
|
|||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||||
if (isSecretFound.length > 0) {
|
if (isSecretFound.length > 0) {
|
||||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
||||||
} else {
|
} else {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!secret.isPRMRSecret) {
|
if (!secret.isPRMRSecret) {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}='${secret.value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -986,7 +1183,7 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
|
|||||||
const { workdir } = await createDirectories({ repository, buildId });
|
const { workdir } = await createDirectories({ repository, buildId });
|
||||||
const labels = []
|
const labels = []
|
||||||
let image = null
|
let image = null
|
||||||
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}-${pullmergeRequestId}' --format '{{json .}}'` })
|
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}-${pullmergeRequestId}' --format '{{json .}}'` })
|
||||||
const containersArray = container.trim().split('\n');
|
const containersArray = container.trim().split('\n');
|
||||||
for (const container of containersArray) {
|
for (const container of containersArray) {
|
||||||
const containerObj = formatLabelsOnDocker(container);
|
const containerObj = formatLabelsOnDocker(container);
|
||||||
@@ -999,7 +1196,7 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
|
|||||||
}
|
}
|
||||||
let imageFound = false;
|
let imageFound = false;
|
||||||
try {
|
try {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker image inspect ${image}`
|
command: `docker image inspect ${image}`
|
||||||
})
|
})
|
||||||
@@ -1053,9 +1250,9 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
|
|||||||
volumes: Object.assign({}, ...composeVolumes)
|
volumes: Object.assign({}, ...composeVolumes)
|
||||||
};
|
};
|
||||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||||
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}-${pullmergeRequestId}` })
|
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}-${pullmergeRequestId}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker rm ${id}-${pullmergeRequestId}` })
|
await executeCommand({ dockerId, command: `docker rm ${id}-${pullmergeRequestId}` })
|
||||||
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
await executeCommand({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Application cannot be restarted.' }
|
throw { status: 500, message: 'Application cannot be restarted.' }
|
||||||
@@ -1096,7 +1293,7 @@ export async function loadPreviews(request: FastifyRequest<OnlyId>) {
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const application = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } });
|
const application = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } });
|
||||||
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` })
|
const { stdout } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` })
|
||||||
if (stdout === '') {
|
if (stdout === '') {
|
||||||
throw { status: 500, message: 'No previews found.' }
|
throw { status: 500, message: 'No previews found.' }
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1356,7 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
|
|||||||
|
|
||||||
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
|
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id, containerId } = request.params;
|
||||||
let { since = 0 } = request.query
|
let { since = 0 } = request.query
|
||||||
if (since !== 0) {
|
if (since !== 0) {
|
||||||
since = day(since).unix();
|
since = day(since).unix();
|
||||||
@@ -1170,10 +1367,8 @@ export async function getApplicationLogs(request: FastifyRequest<GetApplicationL
|
|||||||
});
|
});
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
try {
|
try {
|
||||||
// const found = await checkContainer({ dockerId, container: id })
|
|
||||||
// if (found) {
|
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` })
|
||||||
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const logs = stripLogsStderr.concat(stripLogsStdout)
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
@@ -1181,7 +1376,10 @@ export async function getApplicationLogs(request: FastifyRequest<GetApplicationL
|
|||||||
return { logs: sortedLogs }
|
return { logs: sortedLogs }
|
||||||
// }
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { statusCode } = error;
|
const { statusCode, stderr } = error;
|
||||||
|
if (stderr.startsWith('Error: No such container')) {
|
||||||
|
return { logs: [], noContainer: true }
|
||||||
|
}
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
return {
|
return {
|
||||||
logs: []
|
logs: []
|
||||||
@@ -1361,19 +1559,19 @@ export async function createdBranchDatabase(database: any, baseDatabaseBranch: s
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (type === 'postgresql') {
|
if (type === 'postgresql') {
|
||||||
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
|
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
|
||||||
})
|
})
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
|
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
|
||||||
})
|
})
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
|
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
|
||||||
})
|
})
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
|
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
|
||||||
})
|
})
|
||||||
@@ -1392,12 +1590,12 @@ export async function removeBranchDatabase(database: any, pullmergeRequestId: st
|
|||||||
if (type === 'postgresql') {
|
if (type === 'postgresql') {
|
||||||
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
const decryptedRootUserPassword = decrypt(rootUserPassword);
|
||||||
// Terminate all connections to the database
|
// Terminate all connections to the database
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
|
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
|
||||||
})
|
})
|
||||||
|
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
|
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getDockerImages, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
|
||||||
|
|
||||||
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartApplication, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
@@ -21,7 +21,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
|
||||||
|
|
||||||
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
fastify.post<RestartApplication>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
|
||||||
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
|
||||||
|
|
||||||
@@ -45,11 +45,14 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/status', async (request) => await getPreviewStatus(request));
|
fastify.get<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/status', async (request) => await getPreviewStatus(request));
|
||||||
fastify.post<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/restart', async (request, reply) => await restartPreview(request, reply));
|
fastify.post<RestartPreviewApplication>('/:id/previews/:pullmergeRequestId/restart', async (request, reply) => await restartPreview(request, reply));
|
||||||
|
|
||||||
fastify.get<GetApplicationLogs>('/:id/logs', async (request) => await getApplicationLogs(request));
|
fastify.get<GetApplicationLogs>('/:id/logs/:containerId', async (request) => await getApplicationLogs(request));
|
||||||
fastify.get<GetBuilds>('/:id/logs/build', async (request) => await getBuilds(request));
|
fastify.get<GetBuilds>('/:id/logs/build', async (request) => await getBuilds(request));
|
||||||
fastify.get<GetBuildIdLogs>('/:id/logs/build/:buildId', async (request) => await getBuildIdLogs(request));
|
fastify.get<GetBuildIdLogs>('/:id/logs/build/:buildId', async (request) => await getBuildIdLogs(request));
|
||||||
|
|
||||||
fastify.get('/:id/usage', async (request) => await getUsage(request))
|
fastify.get('/:id/usage', async (request) => await getUsage(request))
|
||||||
|
fastify.get('/:id/usage/:containerId', async (request) => await getUsageByContainer(request))
|
||||||
|
|
||||||
|
fastify.get('/:id/images', async (request) => await getDockerImages(request))
|
||||||
|
|
||||||
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
|
||||||
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
|
||||||
@@ -62,6 +65,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
|
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
|
||||||
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
|
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
|
||||||
|
|
||||||
|
fastify.post('/:id/configuration/registry', async (request, reply) => await saveRegistry(request, reply));
|
||||||
|
|
||||||
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
|
fastify.post('/:id/configuration/database', async (request, reply) => await saveConnectedDatabase(request, reply));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
|
fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
|
||||||
|
|||||||
@@ -19,9 +19,15 @@ export interface SaveApplication extends OnlyId {
|
|||||||
denoMainFile: string,
|
denoMainFile: string,
|
||||||
denoOptions: string,
|
denoOptions: string,
|
||||||
baseImage: string,
|
baseImage: string,
|
||||||
|
gitCommitHash: string,
|
||||||
baseBuildImage: string,
|
baseBuildImage: string,
|
||||||
deploymentType: string,
|
deploymentType: string,
|
||||||
baseDatabaseBranch: string
|
baseDatabaseBranch: string,
|
||||||
|
dockerComposeFile: string,
|
||||||
|
dockerComposeFileLocation: string,
|
||||||
|
dockerComposeConfiguration: string,
|
||||||
|
simpleDockerfile: string,
|
||||||
|
dockerRegistryImageName: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
@@ -52,7 +58,7 @@ export interface GetImages {
|
|||||||
Body: { buildPack: string, deploymentType: string }
|
Body: { buildPack: string, deploymentType: string }
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSource extends OnlyId {
|
export interface SaveApplicationSource extends OnlyId {
|
||||||
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string }
|
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string }
|
||||||
}
|
}
|
||||||
export interface CheckRepository extends OnlyId {
|
export interface CheckRepository extends OnlyId {
|
||||||
Querystring: { repository: string, branch: string }
|
Querystring: { repository: string, branch: string }
|
||||||
@@ -84,7 +90,11 @@ export interface DeleteStorage extends OnlyId {
|
|||||||
path: string,
|
path: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface GetApplicationLogs extends OnlyId {
|
export interface GetApplicationLogs {
|
||||||
|
Params: {
|
||||||
|
id: string,
|
||||||
|
containerId: string
|
||||||
|
}
|
||||||
Querystring: {
|
Querystring: {
|
||||||
since: number,
|
since: number,
|
||||||
}
|
}
|
||||||
@@ -133,4 +143,12 @@ export interface RestartPreviewApplication {
|
|||||||
id: string,
|
id: string,
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
export interface RestartApplication {
|
||||||
|
Params: {
|
||||||
|
id: string,
|
||||||
|
},
|
||||||
|
Body: {
|
||||||
|
imageId: string | null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { errorHandler, listSettings, version } from '../../../../lib/common';
|
import { errorHandler, isARM, listSettings, version } from '../../../../lib/common';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get('/', async () => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
const settings = await listSettings()
|
try {
|
||||||
try {
|
await request.jwtVerify();
|
||||||
return {
|
} catch (error) {
|
||||||
ipv4: settings.ipv4,
|
return;
|
||||||
ipv6: settings.ipv6,
|
}
|
||||||
version,
|
});
|
||||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
fastify.get('/', async (request) => {
|
||||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
const teamId = request.user?.teamId;
|
||||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
const settings = await listSettings();
|
||||||
}
|
try {
|
||||||
} catch ({ status, message }) {
|
return {
|
||||||
return errorHandler({ status, message })
|
ipv4: teamId ? settings.ipv4 : null,
|
||||||
}
|
ipv6: teamId ? settings.ipv6 : null,
|
||||||
});
|
version,
|
||||||
|
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||||
|
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||||
|
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||||
|
isARM: isARM(process.arch)
|
||||||
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { FastifyRequest } from 'fastify';
|
|||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
|
import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
|
||||||
import { day } from '../../../../lib/dayjs';
|
import { day } from '../../../../lib/dayjs';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
@@ -89,7 +89,7 @@ export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
const { destinationDockerId, destinationDocker } = database;
|
const { destinationDockerId, destinationDocker } = database;
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` })
|
const { stdout } = await executeCommand({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` })
|
||||||
|
|
||||||
if (JSON.parse(stdout).Running) {
|
if (JSON.parse(stdout).Running) {
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
@@ -208,7 +208,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
if (type && version) {
|
if (type && version) {
|
||||||
const baseImage = getDatabaseImage(type, arch);
|
const baseImage = getDatabaseImage(type, arch);
|
||||||
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reply.code(201).send({})
|
return reply.code(201).send({})
|
||||||
@@ -298,7 +298,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
};
|
};
|
||||||
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
const composeFileDestination = `${workdir}/docker-compose.yaml`;
|
||||||
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
|
||||||
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
|
await executeCommand({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
|
||||||
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>)
|
|||||||
// const found = await checkContainer({ dockerId, container: id })
|
// const found = await checkContainer({ dockerId, container: id })
|
||||||
// if (found) {
|
// if (found) {
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
||||||
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const logs = stripLogsStderr.concat(stripLogsStdout)
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import sshConfig from 'ssh-config'
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
|
|
||||||
import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||||
import { checkContainer } from '../../../../lib/docker';
|
import { checkContainer } from '../../../../lib/docker';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
@@ -79,9 +79,9 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
|||||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
if (engine) {
|
if (engine) {
|
||||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
|
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
|
||||||
if (stdout === '') {
|
if (stdout === '') {
|
||||||
await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network create --attachable ${network}`);
|
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
||||||
}
|
}
|
||||||
await prisma.destinationDocker.create({
|
await prisma.destinationDocker.create({
|
||||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||||
@@ -103,7 +103,7 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
|||||||
return reply.code(201).send({ id: destination.id });
|
return reply.code(201).send({ id: destination.id });
|
||||||
} else {
|
} else {
|
||||||
const destination = await prisma.destinationDocker.create({
|
const destination = await prisma.destinationDocker.create({
|
||||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort }
|
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort: Number(remotePort) }
|
||||||
});
|
});
|
||||||
return reply.code(201).send({ id: destination.id })
|
return reply.code(201).send({ id: destination.id })
|
||||||
}
|
}
|
||||||
@@ -122,13 +122,13 @@ export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
|||||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||||
if (isCoolifyProxyUsed) {
|
if (isCoolifyProxyUsed) {
|
||||||
if (engine || remoteVerified) {
|
if (engine || remoteVerified) {
|
||||||
const { stdout: found } = await executeDockerCmd({
|
const { stdout: found } = await executeCommand({
|
||||||
dockerId: id,
|
dockerId: id,
|
||||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||||
})
|
})
|
||||||
if (found) {
|
if (found) {
|
||||||
await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
|
await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
|
||||||
await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` })
|
await executeCommand({ dockerId: id, command: `docker network rm ${network}` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,25 +202,65 @@ export async function assignSSHKey(request: FastifyRequest) {
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||||
|
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||||
|
const daemonJson = `daemon-${id}.json`
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id });
|
||||||
await createRemoteEngineConfiguration(id);
|
} catch (error) {
|
||||||
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
|
||||||
const host = `ssh://${remoteUser}@${remoteIpAddress}`
|
}
|
||||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
|
||||||
if (!stdout) {
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
|
||||||
}
|
|
||||||
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`);
|
|
||||||
if (!coolifyNetwork) {
|
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
|
|
||||||
}
|
|
||||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
|
||||||
return reply.code(201).send()
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id });
|
||||||
|
} catch (error) {
|
||||||
|
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||||
|
let isUpdated = false;
|
||||||
|
let daemonJsonParsed = {
|
||||||
|
"live-restore": true,
|
||||||
|
"features": {
|
||||||
|
"buildkit": true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||||
|
daemonJsonParsed = JSON.parse(daemonJson);
|
||||||
|
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||||
|
isUpdated = true;
|
||||||
|
daemonJsonParsed['live-restore'] = true
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!daemonJsonParsed?.features?.buildkit) {
|
||||||
|
isUpdated = true;
|
||||||
|
daemonJsonParsed.features = {
|
||||||
|
buildkit: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
isUpdated = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (isUpdated) {
|
||||||
|
await executeCommand({ shell: true, command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}` })
|
||||||
|
await executeCommand({ dockerId: id, command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json` });
|
||||||
|
await executeCommand({ command: `rm /tmp/${daemonJson}` })
|
||||||
|
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
|
||||||
|
}
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Error while verifying remote docker engine')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
|
const { id } = request.params;
|
||||||
|
try {
|
||||||
|
await verifyRemoteDockerEngineFn(id);
|
||||||
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import axios from "axios";
|
|
||||||
import { compareVersions } from "compare-versions";
|
import { compareVersions } from "compare-versions";
|
||||||
import cuid from "cuid";
|
import cuid from "cuid";
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from "bcryptjs";
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
import {
|
import {
|
||||||
asyncExecShell,
|
|
||||||
asyncSleep,
|
asyncSleep,
|
||||||
cleanupDockerStorage,
|
cleanupDockerStorage,
|
||||||
errorHandler,
|
errorHandler,
|
||||||
@@ -12,8 +12,9 @@ import {
|
|||||||
prisma,
|
prisma,
|
||||||
uniqueName,
|
uniqueName,
|
||||||
version,
|
version,
|
||||||
|
sentryDSN,
|
||||||
|
executeCommand,
|
||||||
} from "../../../lib/common";
|
} from "../../../lib/common";
|
||||||
import { supportedServiceTypesAndVersions } from "../../../lib/services/supportedVersions";
|
|
||||||
import { scheduler } from "../../../lib/scheduler";
|
import { scheduler } from "../../../lib/scheduler";
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import type { Login, Update } from ".";
|
import type { Login, Update } from ".";
|
||||||
@@ -24,6 +25,35 @@ export async function hashPassword(password: string): Promise<string> {
|
|||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function backup(request: FastifyRequest) {
|
||||||
|
try {
|
||||||
|
const { backupData } = request.params;
|
||||||
|
let std = null;
|
||||||
|
const [id, backupType, type, zipped, storage] = backupData.split(':')
|
||||||
|
console.log(id, backupType, type, zipped, storage)
|
||||||
|
const database = await prisma.database.findUnique({ where: { id } })
|
||||||
|
if (database) {
|
||||||
|
// await executeDockerCmd({
|
||||||
|
// dockerId: database.destinationDockerId,
|
||||||
|
// command: `docker pull coollabsio/backup:latest`,
|
||||||
|
// })
|
||||||
|
std = await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
if (std.stdout) {
|
||||||
|
return std.stdout;
|
||||||
|
}
|
||||||
|
if (std.stderr) {
|
||||||
|
return std.stderr;
|
||||||
|
}
|
||||||
|
return 'nope';
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function cleanupManually(request: FastifyRequest) {
|
export async function cleanupManually(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const { serverId } = request.body;
|
const { serverId } = request.body;
|
||||||
@@ -36,16 +66,59 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function refreshTags() {
|
||||||
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
|
try {
|
||||||
|
if (isDev) {
|
||||||
|
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||||
|
await fs.writeFile('./tags.json', tags)
|
||||||
|
} else {
|
||||||
|
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||||
|
await fs.writeFile('/app/tags.json', tags)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function refreshTemplates() {
|
||||||
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
|
try {
|
||||||
|
if (isDev) {
|
||||||
|
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||||
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
||||||
|
} else {
|
||||||
|
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||||
|
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function checkUpdate(request: FastifyRequest) {
|
export async function checkUpdate(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
const isStaging =
|
const isStaging =
|
||||||
request.hostname === "staging.coolify.io" ||
|
request.hostname === "staging.coolify.io" ||
|
||||||
request.hostname === "arm.coolify.io";
|
request.hostname === "arm.coolify.io";
|
||||||
const currentVersion = version;
|
const currentVersion = version;
|
||||||
const { data: versions } = await axios.get(
|
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||||
`https://get.coollabs.io/versions.json?appId=${process.env["COOLIFY_APP_ID"]}&version=${currentVersion}`
|
searchParams: {
|
||||||
);
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
const latestVersion = versions["coolify"].main.version;
|
version: currentVersion
|
||||||
|
}
|
||||||
|
}).json()
|
||||||
|
const latestVersion = coolify.main.version;
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
if (isStaging) {
|
if (isStaging) {
|
||||||
return {
|
return {
|
||||||
@@ -67,14 +140,10 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
try {
|
try {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
|
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||||
await asyncExecShell(`env | grep COOLIFY > .env`);
|
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
||||||
await asyncExecShell(
|
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
|
||||||
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` });
|
||||||
);
|
|
||||||
await asyncExecShell(
|
|
||||||
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
|
||||||
);
|
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
await asyncSleep(2000);
|
await asyncSleep(2000);
|
||||||
@@ -103,7 +172,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (teamId === "0") {
|
if (teamId === "0") {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
asyncExecShell(`docker restart coolify`);
|
await executeCommand({ command: `docker restart coolify` });
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
@@ -146,7 +215,7 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
|
|
||||||
let foundUnconfiguredApplication = false;
|
let foundUnconfiguredApplication = false;
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) {
|
if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
|
||||||
foundUnconfiguredApplication = true
|
foundUnconfiguredApplication = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,9 +424,9 @@ export async function getCurrentUser(
|
|||||||
}
|
}
|
||||||
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
|
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
|
||||||
return {
|
return {
|
||||||
settings: await prisma.setting.findFirst(),
|
settings: await prisma.setting.findUnique({ where: { id: "0" } }),
|
||||||
|
sentryDSN,
|
||||||
pendingInvitations,
|
pendingInvitations,
|
||||||
supportedServiceTypesAndVersions,
|
|
||||||
token,
|
token,
|
||||||
...request.user,
|
...request.user,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify } from './handlers';
|
import { checkUpdate, login, showDashboard, update, resetQueue, getCurrentUser, cleanupManually, restartCoolify, backup } from './handlers';
|
||||||
import { GetCurrentUser } from './types';
|
import { GetCurrentUser } from './types';
|
||||||
import pump from 'pump'
|
|
||||||
import fs from 'fs'
|
|
||||||
import { asyncExecShell, encrypt, errorHandler, prisma } from '../../../lib/common';
|
|
||||||
|
|
||||||
export interface Update {
|
export interface Update {
|
||||||
Body: { latestVersion: string }
|
Body: { latestVersion: string }
|
||||||
@@ -55,6 +52,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post('/internal/cleanup', {
|
fastify.post('/internal/cleanup', {
|
||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async (request) => await cleanupManually(request));
|
}, async (request) => await cleanupManually(request));
|
||||||
|
|
||||||
|
// fastify.get('/internal/backup/:backupData', {
|
||||||
|
// onRequest: [fastify.authenticate]
|
||||||
|
// }, async (request) => await backup(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import { errorHandler, executeDockerCmd, prisma, createRemoteEngineConfiguration, executeSSHCmd } from '../../../../lib/common';
|
import { errorHandler, prisma, executeCommand } from '../../../../lib/common';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import osu from 'node-os-utils';
|
import osu from 'node-os-utils';
|
||||||
|
|
||||||
@@ -8,7 +8,16 @@ export async function listServers(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const servers = await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } }}, distinct: ['remoteIpAddress', 'engine'] })
|
let servers = await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, distinct: ['remoteIpAddress', 'engine'] })
|
||||||
|
servers = servers.filter((server) => {
|
||||||
|
if (server.remoteEngine) {
|
||||||
|
if (server.remoteVerified) {
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
servers
|
servers
|
||||||
}
|
}
|
||||||
@@ -62,10 +71,10 @@ export async function showUsage(request: FastifyRequest) {
|
|||||||
let { remoteEngine } = request.query
|
let { remoteEngine } = request.query
|
||||||
remoteEngine = remoteEngine === 'true' ? true : false
|
remoteEngine = remoteEngine === 'true' ? true : false
|
||||||
if (remoteEngine) {
|
if (remoteEngine) {
|
||||||
const { stdout: stats } = await executeSSHCmd({ dockerId: id, command: `vmstat -s` })
|
const { stdout: stats } = await executeCommand({ sshCommand: true, dockerId: id, command: `vmstat -s` })
|
||||||
const { stdout: disks } = await executeSSHCmd({ dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
|
const { stdout: disks } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
|
||||||
const { stdout: cpus } = await executeSSHCmd({ dockerId: id, command: `nproc --all` })
|
const { stdout: cpus } = await executeCommand({ sshCommand: true, dockerId: id, command: `nproc --all` })
|
||||||
const { stdout: cpuUsage } = await executeSSHCmd({ dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
|
const { stdout: cpuUsage } = await executeCommand({ sshCommand: true, shell: true, dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
|
||||||
const parsed: any = parseFromText(stats)
|
const parsed: any = parseFromText(stats)
|
||||||
return {
|
return {
|
||||||
usage: {
|
usage: {
|
||||||
@@ -78,7 +87,7 @@ export async function showUsage(request: FastifyRequest) {
|
|||||||
freeMemPercentage: (parsed.totalMemoryKB - parsed.usedMemoryKB) / parsed.totalMemoryKB * 100
|
freeMemPercentage: (parsed.totalMemoryKB - parsed.usedMemoryKB) / parsed.totalMemoryKB * 100
|
||||||
},
|
},
|
||||||
cpu: {
|
cpu: {
|
||||||
load: [0,0,0],
|
load: [0, 0, 0],
|
||||||
usage: cpuUsage,
|
usage: cpuUsage,
|
||||||
count: cpus
|
count: cpus
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings } from '../../../../lib/common';
|
import bcrypt from 'bcryptjs';
|
||||||
import { day } from '../../../../lib/dayjs';
|
|
||||||
import { checkContainer, isContainerExited } from '../../../../lib/docker';
|
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import { prisma, uniqueName, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken, executeCommand } from '../../../../lib/common';
|
||||||
|
import { day } from '../../../../lib/dayjs';
|
||||||
|
import { checkContainer, } from '../../../../lib/docker';
|
||||||
|
import { removeService } from '../../../../lib/services/common';
|
||||||
|
import { getTags, getTemplates } from '../../../../lib/services';
|
||||||
|
|
||||||
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types';
|
||||||
import { supportedServiceTypesAndVersions } from '../../../../lib/services/supportedVersions';
|
import type { OnlyId } from '../../../../types';
|
||||||
import { configureServiceType, removeService } from '../../../../lib/services/common';
|
|
||||||
|
|
||||||
export async function listServices(request: FastifyRequest) {
|
export async function listServices(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -46,14 +48,19 @@ export async function cleanupUnconfiguredServices(request: FastifyRequest) {
|
|||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
if (!service.fqdn) {
|
if (!service.fqdn) {
|
||||||
if (service.destinationDockerId) {
|
if (service.destinationDockerId) {
|
||||||
await executeDockerCmd({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: service.destinationDockerId,
|
dockerId: service.destinationDockerId,
|
||||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
|
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}`
|
||||||
})
|
|
||||||
await executeDockerCmd({
|
|
||||||
dockerId: service.destinationDockerId,
|
|
||||||
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
|
|
||||||
})
|
})
|
||||||
|
if (containers) {
|
||||||
|
const containerArray = containers.split('\n');
|
||||||
|
if (containerArray.length > 0) {
|
||||||
|
for (const container of containerArray) {
|
||||||
|
await executeCommand({ dockerId: service.destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||||
|
await executeCommand({ dockerId: service.destinationDockerId, command: `docker rm --force ${container}` })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await removeService({ id: service.id });
|
await removeService({ id: service.id });
|
||||||
}
|
}
|
||||||
@@ -67,30 +74,210 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
|
||||||
let isRunning = false;
|
|
||||||
let isExited = false
|
|
||||||
let isRestarting = false;
|
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const { destinationDockerId, settings } = service;
|
const { destinationDockerId, settings } = service;
|
||||||
|
let payload = {}
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
const status = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
|
const { stdout: containers } = await executeCommand({
|
||||||
if (status?.found) {
|
dockerId: service.destinationDocker.id,
|
||||||
isRunning = status.status.isRunning;
|
command:
|
||||||
isExited = status.status.isExited;
|
`docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
|
||||||
isRestarting = status.status.isRestarting
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containersArray = containers.trim().split('\n');
|
||||||
|
if (containersArray.length > 0 && containersArray[0] !== '') {
|
||||||
|
const templates = await getTemplates();
|
||||||
|
let template = templates.find(t => t.type === service.type);
|
||||||
|
const templateStr = JSON.stringify(template)
|
||||||
|
if (templateStr) {
|
||||||
|
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
|
||||||
|
}
|
||||||
|
for (const container of containersArray) {
|
||||||
|
let isRunning = false;
|
||||||
|
let isExited = false;
|
||||||
|
let isRestarting = false;
|
||||||
|
let isExcluded = false;
|
||||||
|
const containerObj = JSON.parse(container);
|
||||||
|
const exclude = template?.services[containerObj.Names]?.exclude;
|
||||||
|
if (exclude) {
|
||||||
|
payload[containerObj.Names] = {
|
||||||
|
status: {
|
||||||
|
isExcluded: true,
|
||||||
|
isRunning: false,
|
||||||
|
isExited: false,
|
||||||
|
isRestarting: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = containerObj.State
|
||||||
|
if (status === 'running') {
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
if (status === 'exited') {
|
||||||
|
isExited = true;
|
||||||
|
}
|
||||||
|
if (status === 'restarting') {
|
||||||
|
isRestarting = true;
|
||||||
|
}
|
||||||
|
payload[containerObj.Names] = {
|
||||||
|
status: {
|
||||||
|
isExcluded,
|
||||||
|
isRunning,
|
||||||
|
isExited,
|
||||||
|
isRestarting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return {
|
return payload
|
||||||
isRunning,
|
|
||||||
isExited,
|
|
||||||
settings
|
|
||||||
}
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) {
|
||||||
|
const templates = await getTemplates()
|
||||||
|
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||||
|
let parsedTemplate = {}
|
||||||
|
if (foundTemplate) {
|
||||||
|
if (!isDeploy) {
|
||||||
|
for (const [key, value] of Object.entries(foundTemplate.services)) {
|
||||||
|
const realKey = key.replace('$$id', service.id)
|
||||||
|
let name = value.name
|
||||||
|
if (!name) {
|
||||||
|
if (Object.keys(foundTemplate.services).length === 1) {
|
||||||
|
name = foundTemplate.name || service.name.toLowerCase()
|
||||||
|
} else {
|
||||||
|
if (key === '$$id') {
|
||||||
|
name = foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase()
|
||||||
|
} else {
|
||||||
|
name = key.replaceAll('$$id-', '') || service.name.toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedTemplate[realKey] = {
|
||||||
|
value,
|
||||||
|
name,
|
||||||
|
documentation: value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io',
|
||||||
|
image: value.image,
|
||||||
|
files: value?.files,
|
||||||
|
environment: [],
|
||||||
|
fqdns: [],
|
||||||
|
hostPorts: [],
|
||||||
|
proxy: {}
|
||||||
|
}
|
||||||
|
if (value.environment?.length > 0) {
|
||||||
|
for (const env of value.environment) {
|
||||||
|
let [envKey, ...envValue] = env.split('=')
|
||||||
|
envValue = envValue.join("=")
|
||||||
|
let variable = null
|
||||||
|
if (foundTemplate?.variables) {
|
||||||
|
variable = foundTemplate?.variables.find(v => v.name === envKey) || foundTemplate?.variables.find(v => v.id === envValue)
|
||||||
|
}
|
||||||
|
if (variable) {
|
||||||
|
const id = variable.id.replaceAll('$$', '')
|
||||||
|
const label = variable?.label
|
||||||
|
const description = variable?.description
|
||||||
|
const defaultValue = variable?.defaultValue
|
||||||
|
const main = variable?.main || '$$id'
|
||||||
|
const type = variable?.type || 'input'
|
||||||
|
const placeholder = variable?.placeholder || ''
|
||||||
|
const readOnly = variable?.readOnly || false
|
||||||
|
const required = variable?.required || false
|
||||||
|
if (envValue.startsWith('$$config') || variable?.showOnConfiguration) {
|
||||||
|
if (envValue.startsWith('$$config_coolify')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parsedTemplate[realKey].environment.push(
|
||||||
|
{ id, name: envKey, value: envValue, main, label, description, defaultValue, type, placeholder, required, readOnly }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value?.proxy && value.proxy.length > 0) {
|
||||||
|
for (const proxyValue of value.proxy) {
|
||||||
|
if (proxyValue.domain) {
|
||||||
|
const variable = foundTemplate?.variables.find(v => v.id === proxyValue.domain)
|
||||||
|
if (variable) {
|
||||||
|
const { id, name, label, description, defaultValue, required = false } = variable
|
||||||
|
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } })
|
||||||
|
parsedTemplate[realKey].fqdns.push(
|
||||||
|
{ id, name, value: found?.value || '', label, description, defaultValue, required }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (proxyValue.hostPort) {
|
||||||
|
const variable = foundTemplate?.variables.find(v => v.id === proxyValue.hostPort)
|
||||||
|
if (variable) {
|
||||||
|
const { id, name, label, description, defaultValue, required = false } = variable
|
||||||
|
const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.hostPort } })
|
||||||
|
parsedTemplate[realKey].hostPorts.push(
|
||||||
|
{ id, name, value: found?.value || '', label, description, defaultValue, required }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parsedTemplate = foundTemplate
|
||||||
|
}
|
||||||
|
let strParsedTemplate = JSON.stringify(parsedTemplate)
|
||||||
|
|
||||||
|
// replace $$id and $$workdir
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id)
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll('$$core_version', service.version || foundTemplate.defaultVersion)
|
||||||
|
|
||||||
|
// replace $$workdir
|
||||||
|
if (workdir) {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace $$config
|
||||||
|
if (service.serviceSetting.length > 0) {
|
||||||
|
for (const setting of service.serviceSetting) {
|
||||||
|
const { value, variableName } = setting
|
||||||
|
const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi')
|
||||||
|
if (value === '$$generate_fqdn') {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"')
|
||||||
|
} else if (value === '$$generate_fqdn_slash') {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"')
|
||||||
|
} else if (value === '$$generate_domain') {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"')
|
||||||
|
} else if (service.destinationDocker?.network && value === '$$generate_network') {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network + '"')
|
||||||
|
} else {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace $$secret
|
||||||
|
if (service.serviceSecret.length > 0) {
|
||||||
|
for (const secret of service.serviceSecret) {
|
||||||
|
let { name, value } = secret
|
||||||
|
name = name.toLowerCase()
|
||||||
|
const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}\"`, 'gi')
|
||||||
|
const regex = new RegExp(`\\$\\$secret_${name}\"`, 'gi')
|
||||||
|
if (value) {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10) + '"')
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"") + '"')
|
||||||
|
} else {
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '' + '"')
|
||||||
|
strParsedTemplate = strParsedTemplate.replaceAll(regex, '' + '"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedTemplate = JSON.parse(strParsedTemplate)
|
||||||
|
}
|
||||||
|
return parsedTemplate
|
||||||
|
}
|
||||||
|
|
||||||
export async function getService(request: FastifyRequest<OnlyId>) {
|
export async function getService(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
@@ -100,9 +287,17 @@ export async function getService(request: FastifyRequest<OnlyId>) {
|
|||||||
if (!service) {
|
if (!service) {
|
||||||
throw { status: 404, message: 'Service not found.' }
|
throw { status: 404, message: 'Service not found.' }
|
||||||
}
|
}
|
||||||
|
let template = {}
|
||||||
|
let tags = []
|
||||||
|
if (service.type) {
|
||||||
|
template = await parseAndFindServiceTemplates(service)
|
||||||
|
tags = await getTags(service.type)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
settings: await listSettings(),
|
settings: await listSettings(),
|
||||||
service
|
service,
|
||||||
|
template,
|
||||||
|
tags
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -111,7 +306,7 @@ export async function getService(request: FastifyRequest<OnlyId>) {
|
|||||||
export async function getServiceType(request: FastifyRequest) {
|
export async function getServiceType(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
types: supportedServiceTypesAndVersions
|
services: await getTemplates()
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -121,25 +316,83 @@ export async function saveServiceType(request: FastifyRequest<SaveServiceType>,
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { type } = request.body;
|
const { type } = request.body;
|
||||||
await configureServiceType({ id, type });
|
const templates = await getTemplates()
|
||||||
return reply.code(201).send()
|
let foundTemplate = templates.find(t => fixType(t.type) === fixType(type))
|
||||||
} catch ({ status, message }) {
|
if (foundTemplate) {
|
||||||
return errorHandler({ status, message })
|
foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id))
|
||||||
}
|
if (foundTemplate.variables) {
|
||||||
}
|
if (foundTemplate.variables.length > 0) {
|
||||||
export async function getServiceVersions(request: FastifyRequest<OnlyId>) {
|
for (const variable of foundTemplate.variables) {
|
||||||
try {
|
const { defaultValue } = variable;
|
||||||
const teamId = request.user.teamId;
|
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||||
const { id } = request.params;
|
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||||
const { type } = await getServiceFromDB({ id, teamId });
|
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||||
return {
|
variable.value = generatePassword({ length });
|
||||||
type,
|
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||||
versions: supportedServiceTypesAndVersions.find((name) => name.name === type).versions
|
variable.value = generatePassword({ length, isHex: true });
|
||||||
|
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||||
|
variable.value = cuid();
|
||||||
|
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
||||||
|
variable.value = generateToken()
|
||||||
|
} else {
|
||||||
|
variable.value = variable.defaultValue || '';
|
||||||
|
}
|
||||||
|
const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id))
|
||||||
|
if (foundVariableSomewhereElse) {
|
||||||
|
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const variable of foundTemplate.variables) {
|
||||||
|
if (variable.id.startsWith('$$secret_')) {
|
||||||
|
const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.serviceSecret.create({
|
||||||
|
data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (variable.id.startsWith('$$config_')) {
|
||||||
|
const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.serviceSetting.create({
|
||||||
|
data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const service of Object.keys(foundTemplate.services)) {
|
||||||
|
if (foundTemplate.services[service].volumes) {
|
||||||
|
for (const volume of foundTemplate.services[service].volumes) {
|
||||||
|
const [volumeName, path] = volume.split(':')
|
||||||
|
if (!volumeName.startsWith('/')) {
|
||||||
|
const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } })
|
||||||
|
if (!found) {
|
||||||
|
await prisma.servicePersistentStorage.create({
|
||||||
|
data: { volumeName, path, containerId: service, predefined: true, service: { connect: { id } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.defaultVersion, templateVersion: foundTemplate.templateVersion } })
|
||||||
|
|
||||||
|
if (type.startsWith('wordpress')) {
|
||||||
|
await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } })
|
||||||
|
}
|
||||||
|
return reply.code(201).send()
|
||||||
|
} else {
|
||||||
|
throw { status: 404, message: 'Service type not found.' }
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveServiceVersion(request: FastifyRequest<SaveServiceVersion>, reply: FastifyReply) {
|
export async function saveServiceVersion(request: FastifyRequest<SaveServiceVersion>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
@@ -186,7 +439,7 @@ export async function getServiceUsage(request: FastifyRequest<OnlyId>) {
|
|||||||
}
|
}
|
||||||
export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id, containerId } = request.params;
|
||||||
let { since = 0 } = request.query
|
let { since = 0 } = request.query
|
||||||
if (since !== 0) {
|
if (since !== 0) {
|
||||||
since = day(since).unix();
|
since = day(since).unix();
|
||||||
@@ -197,10 +450,8 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
|||||||
});
|
});
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
try {
|
try {
|
||||||
// const found = await checkContainer({ dockerId, container: id })
|
|
||||||
// if (found) {
|
|
||||||
const { default: ansi } = await import('strip-ansi')
|
const { default: ansi } = await import('strip-ansi')
|
||||||
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
|
const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` })
|
||||||
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
|
||||||
const logs = stripLogsStderr.concat(stripLogsStdout)
|
const logs = stripLogsStderr.concat(stripLogsStdout)
|
||||||
@@ -208,7 +459,10 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
|||||||
return { logs: sortedLogs }
|
return { logs: sortedLogs }
|
||||||
// }
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { statusCode } = error;
|
const { statusCode, stderr } = error;
|
||||||
|
if (stderr.startsWith('Error: No such container')) {
|
||||||
|
return { logs: [], noContainer: true }
|
||||||
|
}
|
||||||
if (statusCode === 404) {
|
if (statusCode === 404) {
|
||||||
return {
|
return {
|
||||||
logs: []
|
logs: []
|
||||||
@@ -258,26 +512,22 @@ export async function checkServiceDomain(request: FastifyRequest<CheckServiceDom
|
|||||||
export async function checkService(request: FastifyRequest<CheckService>) {
|
export async function checkService(request: FastifyRequest<CheckService>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { fqdn, exposePort, forceSave, otherFqdns, dualCerts } = request.body;
|
let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body;
|
||||||
|
|
||||||
|
const domainsList = await prisma.serviceSetting.findMany({ where: { variableName: { startsWith: '$$config_coolify_fqdn' } } })
|
||||||
|
|
||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
|
|
||||||
if (exposePort) exposePort = Number(exposePort);
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
|
const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
|
||||||
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
|
||||||
|
|
||||||
let found = await isDomainConfigured({ id, fqdn, remoteIpAddress });
|
let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||||
}
|
}
|
||||||
if (otherFqdns && otherFqdns.length > 0) {
|
if (domainsList.find(d => getDomain(d.value) === getDomain(fqdn))) {
|
||||||
for (const ofqdn of otherFqdns) {
|
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||||
found = await isDomainConfigured({ id, fqdn: ofqdn, remoteIpAddress });
|
|
||||||
if (found) {
|
|
||||||
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
||||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||||
@@ -293,20 +543,33 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
|||||||
export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
|
export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { name, fqdn, exposePort, type } = request.body;
|
let { name, fqdn, exposePort, type, serviceSetting, version } = request.body;
|
||||||
|
|
||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
if (exposePort) exposePort = Number(exposePort);
|
if (exposePort) exposePort = Number(exposePort);
|
||||||
|
|
||||||
type = fixType(type)
|
type = fixType(type)
|
||||||
const update = saveUpdateableFields(type, request.body[type])
|
|
||||||
const data = {
|
const data = {
|
||||||
fqdn,
|
fqdn,
|
||||||
name,
|
name,
|
||||||
exposePort,
|
exposePort,
|
||||||
|
version,
|
||||||
}
|
}
|
||||||
if (Object.keys(update).length > 0) {
|
const templates = await getTemplates()
|
||||||
data[type] = { update: update }
|
const service = await prisma.service.findUnique({ where: { id } })
|
||||||
|
const foundTemplate = templates.find(t => fixType(t.type) === fixType(service.type))
|
||||||
|
for (const setting of serviceSetting) {
|
||||||
|
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting
|
||||||
|
if (value) {
|
||||||
|
if (changed) {
|
||||||
|
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } })
|
||||||
|
}
|
||||||
|
if (isNew) {
|
||||||
|
if (!variableName) {
|
||||||
|
variableName = foundTemplate?.variables.find(v => v.name === name).id
|
||||||
|
}
|
||||||
|
await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await prisma.service.update({
|
await prisma.service.update({
|
||||||
where: { id }, data
|
where: { id }, data
|
||||||
@@ -320,11 +583,19 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
|||||||
export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
|
export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
let secrets = await prisma.serviceSecret.findMany({
|
let secrets = await prisma.serviceSecret.findMany({
|
||||||
where: { serviceId: id },
|
where: { serviceId: id },
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
|
const templates = await getTemplates()
|
||||||
|
const foundTemplate = templates.find(t => fixType(t.type) === service.type)
|
||||||
secrets = secrets.map((secret) => {
|
secrets = secrets.map((secret) => {
|
||||||
|
const foundVariable = foundTemplate?.variables.find(v => v.name === secret.name) || null
|
||||||
|
if (foundVariable) {
|
||||||
|
secret.readOnly = foundVariable.readOnly
|
||||||
|
}
|
||||||
secret.value = decrypt(secret.value);
|
secret.value = decrypt(secret.value);
|
||||||
return secret;
|
return secret;
|
||||||
});
|
});
|
||||||
@@ -341,7 +612,6 @@ export async function saveServiceSecret(request: FastifyRequest<SaveServiceSecre
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
let { name, value, isNew } = request.body
|
let { name, value, isNew } = request.body
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
|
||||||
if (found) {
|
if (found) {
|
||||||
@@ -400,16 +670,21 @@ export async function getServiceStorages(request: FastifyRequest<OnlyId>) {
|
|||||||
export async function saveServiceStorage(request: FastifyRequest<SaveServiceStorage>, reply: FastifyReply) {
|
export async function saveServiceStorage(request: FastifyRequest<SaveServiceStorage>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { path, newStorage, storageId } = request.body
|
const { path, isNewStorage, storageId, containerId } = request.body
|
||||||
|
|
||||||
if (newStorage) {
|
if (isNewStorage) {
|
||||||
|
const volumeName = `${id}-custom${path.replace(/\//gi, '-')}`
|
||||||
|
const found = await prisma.servicePersistentStorage.findFirst({ where: { path, containerId } });
|
||||||
|
if (found) {
|
||||||
|
throw { status: 500, message: 'Persistent storage already exists for this container and path.' }
|
||||||
|
}
|
||||||
await prisma.servicePersistentStorage.create({
|
await prisma.servicePersistentStorage.create({
|
||||||
data: { path, service: { connect: { id } } }
|
data: { path, volumeName, containerId, service: { connect: { id } } }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.servicePersistentStorage.update({
|
await prisma.servicePersistentStorage.update({
|
||||||
where: { id: storageId },
|
where: { id: storageId },
|
||||||
data: { path }
|
data: { path, containerId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
@@ -420,9 +695,8 @@ export async function saveServiceStorage(request: FastifyRequest<SaveServiceStor
|
|||||||
|
|
||||||
export async function deleteServiceStorage(request: FastifyRequest<DeleteServiceStorage>) {
|
export async function deleteServiceStorage(request: FastifyRequest<DeleteServiceStorage>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { storageId } = request.body
|
||||||
const { path } = request.body
|
await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } });
|
||||||
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id, path } });
|
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -478,14 +752,17 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
|
|||||||
const {
|
const {
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
|
serviceSecret
|
||||||
} = await getServiceFromDB({ id, teamId });
|
} = await getServiceFromDB({ id, teamId });
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
await executeDockerCmd({
|
const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL');
|
||||||
dockerId: destinationDocker.id,
|
if (databaseUrl) {
|
||||||
command: `docker exec ${id}-postgresql psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"`
|
await executeCommand({
|
||||||
})
|
dockerId: destinationDocker.id,
|
||||||
return await reply.code(201).send()
|
command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"`
|
||||||
|
})
|
||||||
|
return await reply.code(201).send()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Could not activate users.' }
|
throw { status: 500, message: 'Could not activate users.' }
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -501,9 +778,10 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
|
|||||||
destinationDocker,
|
destinationDocker,
|
||||||
} = await getServiceFromDB({ id, teamId });
|
} = await getServiceFromDB({ id, teamId });
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`
|
command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`,
|
||||||
|
shell: true
|
||||||
})
|
})
|
||||||
return await reply.code(201).send()
|
return await reply.code(201).send()
|
||||||
}
|
}
|
||||||
@@ -543,36 +821,42 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
if (user) ftpUser = user;
|
if (user) ftpUser = user;
|
||||||
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
if (savedPassword) ftpPassword = decrypt(savedPassword);
|
||||||
|
|
||||||
const { stdout: password } = await asyncExecShell(
|
// TODO: rewrite these to usable without shell
|
||||||
`echo ${ftpPassword} | openssl passwd -1 -stdin`
|
const { stdout: password } = await executeCommand({
|
||||||
|
command:
|
||||||
|
`echo ${ftpPassword} | openssl passwd -1 -stdin`,
|
||||||
|
shell: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
try {
|
try {
|
||||||
await fs.stat(hostkeyDir);
|
await fs.stat(hostkeyDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await asyncExecShell(`mkdir -p ${hostkeyDir}`);
|
await executeCommand({ command: `mkdir -p ${hostkeyDir}` });
|
||||||
}
|
}
|
||||||
if (!ftpHostKey) {
|
if (!ftpHostKey) {
|
||||||
await asyncExecShell(
|
await executeCommand({
|
||||||
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
command:
|
||||||
|
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`);
|
const { stdout: ftpHostKey } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.ed25519` });
|
||||||
await prisma.wordpress.update({
|
await prisma.wordpress.update({
|
||||||
where: { serviceId: id },
|
where: { serviceId: id },
|
||||||
data: { ftpHostKey: encrypt(ftpHostKey) }
|
data: { ftpHostKey: encrypt(ftpHostKey) }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`);
|
await executeCommand({ command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, shell: true });
|
||||||
}
|
}
|
||||||
if (!ftpHostKeyPrivate) {
|
if (!ftpHostKeyPrivate) {
|
||||||
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`);
|
await executeCommand({ command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` });
|
||||||
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`);
|
const { stdout: ftpHostKeyPrivate } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.rsa` });
|
||||||
await prisma.wordpress.update({
|
await prisma.wordpress.update({
|
||||||
where: { serviceId: id },
|
where: { serviceId: id },
|
||||||
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`);
|
await executeCommand({ command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, shell: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.wordpress.update({
|
await prisma.wordpress.update({
|
||||||
@@ -587,9 +871,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
try {
|
try {
|
||||||
const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
|
const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`,
|
||||||
|
shell: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
@@ -633,9 +918,9 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
`${hostkeyDir}/${id}.sh`,
|
`${hostkeyDir}/${id}.sh`,
|
||||||
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/`
|
`#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/`
|
||||||
);
|
);
|
||||||
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
|
await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` });
|
||||||
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
|
||||||
})
|
})
|
||||||
@@ -652,9 +937,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
data: { ftpPublicPort: null }
|
data: { ftpPublicPort: null }
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await executeDockerCmd({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
|
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`,
|
||||||
|
shell: true
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -668,8 +954,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
|
|||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await asyncExecShell(
|
await executeCommand({
|
||||||
`rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
command:
|
||||||
|
`rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
getServiceStorages,
|
getServiceStorages,
|
||||||
getServiceType,
|
getServiceType,
|
||||||
getServiceUsage,
|
getServiceUsage,
|
||||||
getServiceVersions,
|
|
||||||
listServices,
|
listServices,
|
||||||
newService,
|
newService,
|
||||||
saveService,
|
saveService,
|
||||||
@@ -64,16 +63,15 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get('/:id/configuration/type', async (request) => await getServiceType(request));
|
fastify.get('/:id/configuration/type', async (request) => await getServiceType(request));
|
||||||
fastify.post<SaveServiceType>('/:id/configuration/type', async (request, reply) => await saveServiceType(request, reply));
|
fastify.post<SaveServiceType>('/:id/configuration/type', async (request, reply) => await saveServiceType(request, reply));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id/configuration/version', async (request) => await getServiceVersions(request));
|
|
||||||
fastify.post<SaveServiceVersion>('/:id/configuration/version', async (request, reply) => await saveServiceVersion(request, reply));
|
fastify.post<SaveServiceVersion>('/:id/configuration/version', async (request, reply) => await saveServiceVersion(request, reply));
|
||||||
|
|
||||||
fastify.post<SaveServiceDestination>('/:id/configuration/destination', async (request, reply) => await saveServiceDestination(request, reply));
|
fastify.post<SaveServiceDestination>('/:id/configuration/destination', async (request, reply) => await saveServiceDestination(request, reply));
|
||||||
|
|
||||||
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
|
fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
|
||||||
fastify.get<GetServiceLogs>('/:id/logs', async (request) => await getServiceLogs(request));
|
fastify.get<GetServiceLogs>('/:id/logs/:containerId', async (request) => await getServiceLogs(request));
|
||||||
|
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
|
fastify.post<ServiceStartStop>('/:id/start', async (request) => await startService(request, fastify));
|
||||||
fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
|
fastify.post<ServiceStartStop>('/:id/stop', async (request) => await stopService(request));
|
||||||
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
fastify.post<ServiceStartStop & SetWordpressSettings & SetGlitchTipSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
|
||||||
|
|
||||||
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ export interface SaveServiceDestination extends OnlyId {
|
|||||||
destinationId: string
|
destinationId: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface GetServiceLogs extends OnlyId {
|
export interface GetServiceLogs{
|
||||||
|
Params: {
|
||||||
|
id: string,
|
||||||
|
containerId: string
|
||||||
|
},
|
||||||
Querystring: {
|
Querystring: {
|
||||||
since: number
|
since: number,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface SaveServiceSettings extends OnlyId {
|
export interface SaveServiceSettings extends OnlyId {
|
||||||
@@ -36,7 +40,7 @@ export interface CheckService extends OnlyId {
|
|||||||
forceSave: boolean,
|
forceSave: boolean,
|
||||||
dualCerts: boolean,
|
dualCerts: boolean,
|
||||||
exposePort: number,
|
exposePort: number,
|
||||||
otherFqdns: Array<string>
|
otherFqdn: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface SaveService extends OnlyId {
|
export interface SaveService extends OnlyId {
|
||||||
@@ -44,6 +48,8 @@ export interface SaveService extends OnlyId {
|
|||||||
name: string,
|
name: string,
|
||||||
fqdn: string,
|
fqdn: string,
|
||||||
exposePort: number,
|
exposePort: number,
|
||||||
|
version: string,
|
||||||
|
serviceSetting: any
|
||||||
type: string
|
type: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,14 +68,15 @@ export interface DeleteServiceSecret extends OnlyId {
|
|||||||
export interface SaveServiceStorage extends OnlyId {
|
export interface SaveServiceStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
path: string,
|
||||||
newStorage: string,
|
containerId: string,
|
||||||
storageId: string,
|
storageId: string,
|
||||||
|
isNewStorage: boolean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeleteServiceStorage extends OnlyId {
|
export interface DeleteServiceStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
storageId: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface ServiceStartStop {
|
export interface ServiceStartStop {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
import { X509Certificate } from 'node:crypto';
|
import { X509Certificate } from 'node:crypto';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { asyncExecShell, checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
|
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
|
||||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||||
|
|
||||||
|
|
||||||
export async function listAllSettings(request: FastifyRequest) {
|
export async function listAllSettings(request: FastifyRequest) {
|
||||||
@@ -11,6 +11,13 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||||
|
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } })
|
||||||
|
registries = registries.map((registry) => {
|
||||||
|
if (registry.password) {
|
||||||
|
registry.password = decrypt(registry.password)
|
||||||
|
}
|
||||||
|
return registry
|
||||||
|
})
|
||||||
const unencryptedKeys = []
|
const unencryptedKeys = []
|
||||||
if (sshKeys.length > 0) {
|
if (sshKeys.length > 0) {
|
||||||
for (const key of sshKeys) {
|
for (const key of sshKeys) {
|
||||||
@@ -27,7 +34,8 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
return {
|
return {
|
||||||
settings,
|
settings,
|
||||||
certificates: cns,
|
certificates: cns,
|
||||||
sshKeys: unencryptedKeys
|
sshKeys: unencryptedKeys,
|
||||||
|
registries
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -35,7 +43,10 @@ export async function listAllSettings(request: FastifyRequest) {
|
|||||||
}
|
}
|
||||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const {
|
let {
|
||||||
|
previewSeparator,
|
||||||
|
numberOfDockerImagesKeptLocally,
|
||||||
|
doNotTrack,
|
||||||
fqdn,
|
fqdn,
|
||||||
isAPIDebuggingEnabled,
|
isAPIDebuggingEnabled,
|
||||||
isRegistrationEnabled,
|
isRegistrationEnabled,
|
||||||
@@ -44,19 +55,48 @@ export async function saveSettings(request: FastifyRequest<SaveSettings>, reply:
|
|||||||
maxPort,
|
maxPort,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
isDNSCheckEnabled,
|
isDNSCheckEnabled,
|
||||||
DNSServers
|
DNSServers,
|
||||||
|
proxyDefaultRedirect
|
||||||
} = request.body
|
} = request.body
|
||||||
const { id } = await listSettings();
|
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||||
|
if (numberOfDockerImagesKeptLocally) {
|
||||||
|
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally)
|
||||||
|
}
|
||||||
|
if (previewSeparator == '') {
|
||||||
|
previewSeparator = '.'
|
||||||
|
}
|
||||||
|
if (SetPreviewSeparator != previewSeparator) {
|
||||||
|
const applications = await prisma.application.findMany({ where: { previewApplication: { some: { id: { not: undefined } } } }, include: { previewApplication: true } })
|
||||||
|
for (const application of applications) {
|
||||||
|
for (const preview of application.previewApplication) {
|
||||||
|
const { protocol } = new URL(preview.customDomain)
|
||||||
|
const { pullmergeRequestId } = preview
|
||||||
|
const { fqdn } = application
|
||||||
|
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}`
|
||||||
|
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
data: { previewSeparator, numberOfDockerImagesKeptLocally, doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
||||||
});
|
});
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
}
|
}
|
||||||
|
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||||
if (minPort && maxPort) {
|
if (minPort && maxPort) {
|
||||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||||
}
|
}
|
||||||
|
if (doNotTrack === false) {
|
||||||
|
// Sentry.init({
|
||||||
|
// dsn: sentryDSN,
|
||||||
|
// environment: isDev ? 'development' : 'production',
|
||||||
|
// release: version
|
||||||
|
// });
|
||||||
|
// console.log('Sentry initialized')
|
||||||
|
}
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -89,9 +129,9 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
|||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
const found = await isDomainConfigured({ id, fqdn });
|
const found = await isDomainConfigured({ id, fqdn });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw "Domain already configured";
|
throw { message: "Domain already configured" };
|
||||||
}
|
}
|
||||||
if (isDNSCheckEnabled && !forceSave) {
|
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||||
const hostname = request.hostname.split(':')[0]
|
const hostname = request.hostname.split(':')[0]
|
||||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||||
}
|
}
|
||||||
@@ -129,8 +169,9 @@ export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: Fas
|
|||||||
}
|
}
|
||||||
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await prisma.sshKey.delete({ where: { id } })
|
await prisma.sshKey.deleteMany({ where: { id, teamId } })
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
@@ -139,9 +180,54 @@ export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply:
|
|||||||
|
|
||||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await asyncExecShell(`docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`)
|
await executeCommand({ command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`, shell: true })
|
||||||
await prisma.certificate.delete({ where: { id } })
|
await prisma.certificate.deleteMany({ where: { id, teamId } })
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id, username, password } = request.body;
|
||||||
|
|
||||||
|
let encryptedPassword = ''
|
||||||
|
if (password) encryptedPassword = encrypt(password)
|
||||||
|
|
||||||
|
if (teamId === '0') {
|
||||||
|
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } })
|
||||||
|
} else {
|
||||||
|
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } })
|
||||||
|
}
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { name, url, username, password } = request.body;
|
||||||
|
|
||||||
|
let encryptedPassword = ''
|
||||||
|
if (password) encryptedPassword = encrypt(password)
|
||||||
|
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
|
||||||
|
|
||||||
|
return reply.code(201).send()
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id } = request.body;
|
||||||
|
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
|
||||||
|
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { FastifyPluginAsync } from 'fastify';
|
|||||||
import { X509Certificate } from 'node:crypto';
|
import { X509Certificate } from 'node:crypto';
|
||||||
|
|
||||||
import { encrypt, errorHandler, prisma } from '../../../../lib/common';
|
import { encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||||
import { checkDNS, checkDomain, deleteCertificates, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
|
import { addDockerRegistry, checkDNS, checkDomain, deleteCertificates, deleteDockerRegistry, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey, setDockerRegistry } from './handlers';
|
||||||
import { CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey } from './types';
|
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||||
|
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
@@ -20,6 +20,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
|
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
|
||||||
fastify.delete<OnlyIdInBody>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
|
fastify.delete<OnlyIdInBody>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
|
||||||
|
|
||||||
|
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
|
||||||
|
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
|
||||||
|
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
|
||||||
|
|
||||||
fastify.post('/upload', async (request) => {
|
fastify.post('/upload', async (request) => {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
@@ -53,7 +57,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
fastify.delete<OnlyIdInBody>('/certificate', async (request, reply) => await deleteCertificates(request, reply))
|
fastify.delete<OnlyIdInBody>('/certificate', async (request, reply) => await deleteCertificates(request, reply))
|
||||||
// fastify.get('/certificates', async (request) => await getCertificates(request))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import { OnlyId } from "../../../../types"
|
|||||||
|
|
||||||
export interface SaveSettings {
|
export interface SaveSettings {
|
||||||
Body: {
|
Body: {
|
||||||
|
previewSeparator: string,
|
||||||
|
numberOfDockerImagesKeptLocally: number,
|
||||||
|
doNotTrack: boolean,
|
||||||
fqdn: string,
|
fqdn: string,
|
||||||
isAPIDebuggingEnabled: boolean,
|
isAPIDebuggingEnabled: boolean,
|
||||||
isRegistrationEnabled: boolean,
|
isRegistrationEnabled: boolean,
|
||||||
@@ -10,7 +13,8 @@ export interface SaveSettings {
|
|||||||
maxPort: number,
|
maxPort: number,
|
||||||
isAutoUpdateEnabled: boolean,
|
isAutoUpdateEnabled: boolean,
|
||||||
isDNSCheckEnabled: boolean,
|
isDNSCheckEnabled: boolean,
|
||||||
DNSServers: string
|
DNSServers: string,
|
||||||
|
proxyDefaultRedirect: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface DeleteDomain {
|
export interface DeleteDomain {
|
||||||
@@ -20,30 +24,46 @@ export interface DeleteDomain {
|
|||||||
}
|
}
|
||||||
export interface CheckDomain extends OnlyId {
|
export interface CheckDomain extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
fqdn: string,
|
fqdn: string,
|
||||||
forceSave: boolean,
|
forceSave: boolean,
|
||||||
dualCerts: boolean,
|
dualCerts: boolean,
|
||||||
isDNSCheckEnabled: boolean,
|
isDNSCheckEnabled: boolean,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface CheckDNS {
|
export interface CheckDNS {
|
||||||
Params: {
|
Params: {
|
||||||
domain: string,
|
domain: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface SaveSSHKey {
|
export interface SaveSSHKey {
|
||||||
Body: {
|
Body: {
|
||||||
privateKey: string,
|
privateKey: string,
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface DeleteSSHKey {
|
export interface DeleteSSHKey {
|
||||||
Body: {
|
Body: {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface OnlyIdInBody {
|
export interface OnlyIdInBody {
|
||||||
Body: {
|
Body: {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetDefaultRegistry {
|
||||||
|
Body: {
|
||||||
|
id: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface AddDefaultRegistry {
|
||||||
|
Body: {
|
||||||
|
url: string
|
||||||
|
name: string
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -37,9 +37,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
|
|
||||||
const settings = await prisma.setting.findFirst({});
|
const settings = await prisma.setting.findFirst({});
|
||||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
|
||||||
|
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import axios from "axios";
|
|
||||||
import cuid from "cuid";
|
import cuid from "cuid";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { encrypt, errorHandler, getDomain, getUIUrl, isDev, prisma } from "../../../lib/common";
|
import { encrypt, errorHandler, getDomain, getUIUrl, isDev, prisma } from "../../../lib/common";
|
||||||
@@ -32,13 +31,14 @@ export async function installGithub(request: FastifyRequest<InstallGithub>, repl
|
|||||||
}
|
}
|
||||||
export async function configureGitHubApp(request, reply) {
|
export async function configureGitHubApp(request, reply) {
|
||||||
try {
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
const { code, state } = request.query;
|
const { code, state } = request.query;
|
||||||
const { apiUrl } = await prisma.gitSource.findFirst({
|
const { apiUrl } = await prisma.gitSource.findFirst({
|
||||||
where: { id: state },
|
where: { id: state },
|
||||||
include: { githubApp: true, gitlabApp: true }
|
include: { githubApp: true, gitlabApp: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data }: any = await axios.post(`${apiUrl}/app-manifests/${code}/conversions`);
|
const data: any = await got.post(`${apiUrl}/app-manifests/${code}/conversions`).json()
|
||||||
const { id, client_id, slug, client_secret, pem, webhook_secret } = data
|
const { id, client_id, slug, client_secret, pem, webhook_secret } = data
|
||||||
|
|
||||||
const encryptedClientSecret = encrypt(client_secret);
|
const encryptedClientSecret = encrypt(client_secret);
|
||||||
@@ -71,7 +71,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
const githubEvent = request.headers['x-github-event']?.toString().toLowerCase();
|
||||||
const githubSignature = request.headers['x-hub-signature-256']?.toString().toLowerCase();
|
const githubSignature = request.headers['x-hub-signature-256']?.toString().toLowerCase();
|
||||||
if (!allowedGithubEvents.includes(githubEvent)) {
|
if (!allowedGithubEvents.includes(githubEvent)) {
|
||||||
throw { status: 500, message: 'Event not allowed.' }
|
throw { status: 500, message: 'Event not allowed.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
if (githubEvent === 'ping') {
|
if (githubEvent === 'ping') {
|
||||||
return { pong: 'cool' }
|
return { pong: 'cool' }
|
||||||
@@ -89,9 +89,10 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
branch = body.pull_request.base.ref
|
branch = body.pull_request.base.ref
|
||||||
}
|
}
|
||||||
if (!projectId || !branch) {
|
if (!projectId || !branch) {
|
||||||
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
|
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!', type: 'webhook' }
|
||||||
}
|
}
|
||||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
const applicationsFound = await getApplicationFromDBWebhook(projectId, branch);
|
||||||
|
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||||
if (applicationsFound && applicationsFound.length > 0) {
|
if (applicationsFound && applicationsFound.length > 0) {
|
||||||
for (const application of applicationsFound) {
|
for (const application of applicationsFound) {
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
@@ -106,7 +107,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
const checksum = Buffer.from(githubSignature, 'utf8');
|
const checksum = Buffer.from(githubSignature, 'utf8');
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
if (checksum.length !== digest.length || !crypto.timingSafeEqual(digest, checksum)) {
|
||||||
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?' }
|
throw { status: 500, message: 'SHA256 checksum failed. Are you doing something fishy?', type: 'webhook' }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
const sourceBranch = body.pull_request.head.ref
|
const sourceBranch = body.pull_request.head.ref
|
||||||
const sourceRepository = body.pull_request.head.repo.full_name
|
const sourceRepository = body.pull_request.head.repo.full_name
|
||||||
if (!allowedActions.includes(pullmergeRequestAction)) {
|
if (!allowedActions.includes(pullmergeRequestAction)) {
|
||||||
throw { status: 500, message: 'Action not allowed.' }
|
throw { status: 500, message: 'Action not allowed.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (application.settings.previews) {
|
if (application.settings.previews) {
|
||||||
@@ -168,7 +169,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
throw { status: 500, message: 'Application not running.' }
|
throw { status: 500, message: 'Application not running.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -192,7 +193,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
data: {
|
data: {
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
sourceBranch,
|
sourceBranch,
|
||||||
customDomain: `${protocol}${pullmergeRequestId}.${getDomain(application.fqdn)}`,
|
customDomain: `${protocol}${pullmergeRequestId}${settings.previewSeparator}${getDomain(application.fqdn)}`,
|
||||||
application: { connect: { id: application.id } }
|
application: { connect: { id: application.id } }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -257,8 +258,8 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message, type }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message, type })
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import axios from "axios";
|
|
||||||
import cuid from "cuid";
|
import cuid from "cuid";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||||
@@ -10,6 +9,7 @@ import type { ConfigureGitLabApp, GitLabEvents } from "./types";
|
|||||||
|
|
||||||
export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLabApp>, reply: FastifyReply) {
|
export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLabApp>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
|
const { default: got } = await import('got')
|
||||||
const { code, state } = request.query;
|
const { code, state } = request.query;
|
||||||
const { fqdn } = await listSettings();
|
const { fqdn } = await listSettings();
|
||||||
const { gitSource: { gitlabApp: { appId, appSecret }, htmlUrl } }: any = await getApplicationFromDB(state, undefined);
|
const { gitSource: { gitlabApp: { appId, appSecret }, htmlUrl } }: any = await getApplicationFromDB(state, undefined);
|
||||||
@@ -19,19 +19,21 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
|
|||||||
if (isDev) {
|
if (isDev) {
|
||||||
domain = getAPIUrl();
|
domain = getAPIUrl();
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams({
|
|
||||||
client_id: appId,
|
const { access_token } = await got.post(`${htmlUrl}/oauth/token`, {
|
||||||
client_secret: appSecret,
|
searchParams: {
|
||||||
code,
|
client_id: appId,
|
||||||
state,
|
client_secret: appSecret,
|
||||||
grant_type: 'authorization_code',
|
code,
|
||||||
redirect_uri: `${domain}/webhooks/gitlab`
|
state,
|
||||||
});
|
grant_type: 'authorization_code',
|
||||||
const { data } = await axios.post(`${htmlUrl}/oauth/token`, params)
|
redirect_uri: `${domain}/webhooks/gitlab`
|
||||||
|
}
|
||||||
|
}).json()
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${data.access_token}`)
|
return reply.redirect(`${getUIUrl()}/webhooks/success?token=${access_token}`)
|
||||||
}
|
}
|
||||||
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
|
return reply.redirect(`/webhooks/success?token=${access_token}`)
|
||||||
} catch ({ status, message, ...other }) {
|
} catch ({ status, message, ...other }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message })
|
||||||
}
|
}
|
||||||
@@ -42,8 +44,9 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
|
||||||
const webhookToken = request.headers['x-gitlab-token'];
|
const webhookToken = request.headers['x-gitlab-token'];
|
||||||
if (!webhookToken && !isDev) {
|
if (!webhookToken && !isDev) {
|
||||||
throw { status: 500, message: 'Invalid webhookToken.' }
|
throw { status: 500, message: 'Invalid webhookToken.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
|
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||||
if (objectKind === 'push') {
|
if (objectKind === 'push') {
|
||||||
const projectId = Number(project_id);
|
const projectId = Number(project_id);
|
||||||
const branch = ref.split('/')[2];
|
const branch = ref.split('/')[2];
|
||||||
@@ -93,10 +96,10 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
const pullmergeRequestId = request.body.object_attributes.iid.toString();
|
const pullmergeRequestId = request.body.object_attributes.iid.toString();
|
||||||
const projectId = Number(id);
|
const projectId = Number(id);
|
||||||
if (!allowedActions.includes(action)) {
|
if (!allowedActions.includes(action)) {
|
||||||
throw { status: 500, message: 'Action not allowed.' }
|
throw { status: 500, message: 'Action not allowed.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
if (isDraft) {
|
if (isDraft) {
|
||||||
throw { status: 500, message: 'Draft MR, do nothing.' }
|
throw { status: 500, message: 'Draft MR, do nothing.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
const applicationsFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
const applicationsFound = await getApplicationFromDBWebhook(projectId, targetBranch);
|
||||||
if (applicationsFound && applicationsFound.length > 0) {
|
if (applicationsFound && applicationsFound.length > 0) {
|
||||||
@@ -111,11 +114,11 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
throw { status: 500, message: 'Application not running.' }
|
throw { status: 500, message: 'Application not running.', type: 'webhook' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isDev && application.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
if (!isDev && application.gitSource.gitlabApp.webhookToken !== webhookToken) {
|
||||||
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!' }
|
throw { status: 500, message: 'Invalid webhookToken. Are you doing something nasty?!', type: 'webhook' }
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
action === 'opened' ||
|
action === 'opened' ||
|
||||||
@@ -138,7 +141,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
data: {
|
data: {
|
||||||
pullmergeRequestId,
|
pullmergeRequestId,
|
||||||
sourceBranch,
|
sourceBranch,
|
||||||
customDomain: `${protocol}${pullmergeRequestId}.${getDomain(application.fqdn)}`,
|
customDomain: `${protocol}${pullmergeRequestId}${settings.previewSeparator}${getDomain(application.fqdn)}`,
|
||||||
application: { connect: { id: application.id } }
|
application: { connect: { id: application.id } }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -186,7 +189,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message, type }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message, type })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,12 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { OnlyId } from '../../../types';
|
import { OnlyId } from '../../../types';
|
||||||
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
|
import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||||
import { TraefikOtherConfiguration } from './types';
|
import { OtherProxyConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
|
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||||
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
|
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||||
|
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
||||||
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface TraefikOtherConfiguration {
|
export interface OtherProxyConfiguration {
|
||||||
Querystring: {
|
Querystring: {
|
||||||
id: string,
|
id: string,
|
||||||
privatePort: number,
|
privatePort: number,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface OnlyId {
|
export interface OnlyId {
|
||||||
Params: { id: string },
|
Params: { id?: string },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
apps/api/tags.json
Normal file
1
apps/api/tags.json
Normal file
File diff suppressed because one or more lines are too long
1
apps/api/templates.json
Normal file
1
apps/api/templates.json
Normal file
File diff suppressed because one or more lines are too long
2
apps/backup/.dockerignore
Normal file
2
apps/backup/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
backup/*
|
||||||
27
apps/backup/Dockerfile
Normal file
27
apps/backup/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
ARG PNPM_VERSION=7.17.1
|
||||||
|
|
||||||
|
FROM node:18-slim as build
|
||||||
|
WORKDIR /app
|
||||||
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
|
||||||
|
COPY ./package*.json .
|
||||||
|
RUN pnpm install -p
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Production build
|
||||||
|
FROM node:18-slim
|
||||||
|
ARG DOCKER_VERSION=20.10.18
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt update && apt -y install curl
|
||||||
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/docker-$DOCKER_VERSION -o /usr/bin/docker
|
||||||
|
RUN chmod +x /usr/bin/docker
|
||||||
|
COPY --from=minio/mc:latest /usr/bin/mc /usr/bin/mc
|
||||||
|
COPY --from=build /app/ .
|
||||||
|
|
||||||
|
ENV CHECKPOINT_DISABLE=1
|
||||||
|
CMD node /app/src/index.mjs
|
||||||
0
apps/backup/backups/.gitkeep
Normal file
0
apps/backup/backups/.gitkeep
Normal file
24
apps/backup/package.json
Normal file
24
apps/backup/package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "backup",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "Andras Bacsai",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "NODE_ENV=production node src/index.mjs",
|
||||||
|
"dev": "pnpm cleanup && NODE_ENV=development node src/index.mjs",
|
||||||
|
"build": "docker build -t backup .",
|
||||||
|
"test": "pnpm build && docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock -v /root/devel/coolify/apps/backup/backups:/app/backups -e CONTAINERS_TO_BACKUP='clatmhc6000008lvb5a5tnvsk:database:mysql:local' backup",
|
||||||
|
"cleanup": "rm -rf backups/*"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.222.0",
|
||||||
|
"@aws-sdk/lib-storage": "^3.222.0",
|
||||||
|
"cuid": "2.1.8",
|
||||||
|
"dotenv": "16.0.3",
|
||||||
|
"zx": "7.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
126
apps/backup/src/index.mjs
Normal file
126
apps/backup/src/index.mjs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
import 'zx/globals';
|
||||||
|
import cuid from 'cuid';
|
||||||
|
import { S3, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
|
$.verbose = !!isDev
|
||||||
|
|
||||||
|
if (!process.env.CONTAINERS_TO_BACKUP && !isDev) {
|
||||||
|
console.log(chalk.red(`No containers to backup!`))
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
const mysqlGzipLocal = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:local';
|
||||||
|
const mysqlRawLocal = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:raw:local';
|
||||||
|
const postgresqlGzipLocal = 'clb6c15yi00008lpuezop7cy0:database:postgresql:gzip:local';
|
||||||
|
const postgresqlRawLocal = 'clb6c15yi00008lpuezop7cy0:database:postgresql:raw:local';
|
||||||
|
|
||||||
|
const minio = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:minio|http|min.arm.coolify.io|backups|<access_key>|<secret_key>';
|
||||||
|
const digitalOcean = 'clb6c9ue4000a8lputdd5g1cl:database:mysql:gzip:do|https|fra1.digitaloceanspaces.com|backups|<access_key>|<secret_key>';
|
||||||
|
|
||||||
|
const devContainers = [mysqlGzipLocal, mysqlRawLocal, postgresqlGzipLocal, postgresqlRawLocal]
|
||||||
|
|
||||||
|
const containers = isDev
|
||||||
|
? devContainers
|
||||||
|
: process.env.CONTAINERS_TO_BACKUP.split(',')
|
||||||
|
|
||||||
|
const backup = async (container) => {
|
||||||
|
const id = cuid()
|
||||||
|
const [name, backupType, type, zipped, storage] = container.split(':')
|
||||||
|
const directory = `backups`;
|
||||||
|
const filename = zipped === 'raw'
|
||||||
|
? `${name}-${type}-${backupType}-${new Date().getTime()}.sql`
|
||||||
|
: `${name}-${type}-${backupType}-${new Date().getTime()}.tgz`
|
||||||
|
const backup = `${directory}/${filename}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await $`docker inspect ${name.split(' ')[0]}`.quiet()
|
||||||
|
if (backupType === 'database') {
|
||||||
|
if (type === 'mysql') {
|
||||||
|
console.log(chalk.blue(`Backing up ${name}:${type}...`))
|
||||||
|
const { stdout: rootPassword } = await $`docker exec ${name} printenv MYSQL_ROOT_PASSWORD`.quiet()
|
||||||
|
if (zipped === 'raw') {
|
||||||
|
await $`docker exec ${name} sh -c "exec mysqldump --all-databases -uroot -p${rootPassword.trim()}" > ${backup}`
|
||||||
|
} else if (zipped === 'gzip') {
|
||||||
|
await $`docker exec ${name} sh -c "exec mysqldump --all-databases -uroot -p${rootPassword.trim()}" | gzip > ${backup}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'postgresql') {
|
||||||
|
console.log(chalk.blue(`Backing up ${name}:${type}...`))
|
||||||
|
const { stdout: userPassword } = await $`docker exec ${name} printenv POSTGRES_PASSWORD`
|
||||||
|
const { stdout: user } = await $`docker exec ${name} printenv POSTGRES_USER`
|
||||||
|
if (zipped === 'raw') {
|
||||||
|
await $`docker exec ${name} sh -c "exec pg_dumpall -c -U${user.trim()}" -W${userPassword.trim()}> ${backup}`
|
||||||
|
} else if (zipped === 'gzip') {
|
||||||
|
await $`docker exec ${name} sh -c "exec pg_dumpall -c -U${user.trim()}" -W${userPassword.trim()} | gzip > ${backup}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [storageType, ...storageArgs] = storage.split('|')
|
||||||
|
if (storageType !== 'local') {
|
||||||
|
let s3Protocol, s3Url, s3Bucket, s3Key, s3Secret = null
|
||||||
|
if (storageArgs.length > 0) {
|
||||||
|
[s3Protocol, s3Url, s3Bucket, s3Key, s3Secret] = storageArgs
|
||||||
|
}
|
||||||
|
if (storageType === 'minio') {
|
||||||
|
if (!s3Protocol || !s3Url || !s3Bucket || !s3Key || !s3Secret) {
|
||||||
|
console.log(chalk.red(`Invalid storage arguments for ${name}:${type}!`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await $`mc alias set ${id} ${s3Protocol}://${s3Url} ${s3Key} ${s3Secret}`
|
||||||
|
await $`mc stat ${id}`
|
||||||
|
await $`mc cp ${backup} ${id}/${s3Bucket}`
|
||||||
|
await $`rm ${backup}`
|
||||||
|
await $`mc alias rm ${id}`
|
||||||
|
} else if (storageType === 'do') {
|
||||||
|
if (!s3Protocol || !s3Url || !s3Bucket || !s3Key || !s3Secret) {
|
||||||
|
console.log(chalk.red(`Invalid storage arguments for ${name}:${type}!`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log({ s3Protocol, s3Url, s3Bucket, s3Key, s3Secret })
|
||||||
|
console.log(chalk.blue(`Uploading ${name}:${type} to DigitalOcean Spaces...`))
|
||||||
|
const readstream = fs.createReadStream(backup)
|
||||||
|
const bucketParams = {
|
||||||
|
Bucket: s3Bucket,
|
||||||
|
Key: filename,
|
||||||
|
Body: readstream
|
||||||
|
};
|
||||||
|
const s3Client = new S3({
|
||||||
|
forcePathStyle: false,
|
||||||
|
endpoint: `${s3Protocol}://${s3Url}`,
|
||||||
|
region: "us-east-1",
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: s3Key,
|
||||||
|
secretAccessKey: s3Secret
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const data = await s3Client.send(new PutObjectCommand(bucketParams));
|
||||||
|
console.log(chalk.green("Successfully uploaded backup: " +
|
||||||
|
bucketParams.Bucket +
|
||||||
|
"/" +
|
||||||
|
bucketParams.Key
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Error", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(chalk.green(`Backup of ${name}:${type} complete!`))
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red(`Backup of ${name}:${type} failed!`))
|
||||||
|
console.log(chalk.red(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const promises = []
|
||||||
|
for (const container of containers) {
|
||||||
|
// await backup(container);
|
||||||
|
promises.push(backup(container))
|
||||||
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
13
apps/client/.eslintignore
Normal file
13
apps/client/.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Ignore files for PNPM, NPM and YARN
|
||||||
|
pnpm-lock.yaml
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
20
apps/client/.eslintrc.cjs
Normal file
20
apps/client/.eslintrc.cjs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||||
|
plugins: ['svelte3', '@typescript-eslint'],
|
||||||
|
ignorePatterns: ['*.cjs'],
|
||||||
|
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||||
|
settings: {
|
||||||
|
'svelte3/typescript': () => require('typescript')
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 2020
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2017: true,
|
||||||
|
node: true
|
||||||
|
}
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user