mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 20:49:28 +00:00
Compare commits
378 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80a519b80 | ||
|
|
2fa7ffc931 | ||
|
|
4abec14a21 | ||
|
|
18d0623011 | ||
|
|
aa634c78d1 | ||
|
|
a2d4373104 | ||
|
|
702e16d643 | ||
|
|
3b25c8f96b | ||
|
|
1c8c567791 | ||
|
|
807a3c9d66 | ||
|
|
2abd7bd7bb | ||
|
|
343957ab8b | ||
|
|
49261308f7 | ||
|
|
d037409237 | ||
|
|
338cbf62a1 | ||
|
|
4c51bffc7b | ||
|
|
fd98ba8812 | ||
|
|
930251e9c8 | ||
|
|
7cd441266a | ||
|
|
990fb8ec15 | ||
|
|
3fe982b2f4 | ||
|
|
9dd874e959 | ||
|
|
b91368223b | ||
|
|
139670372b | ||
|
|
1c0769ad75 | ||
|
|
e6cbcf98cb | ||
|
|
64b0481055 | ||
|
|
ce15161926 | ||
|
|
4003d4d894 | ||
|
|
6e011025a7 | ||
|
|
6c0544adb2 | ||
|
|
8e4f7c9065 | ||
|
|
e71f890b54 | ||
|
|
4dc35dea97 | ||
|
|
b63dfb4bcd | ||
|
|
b2ffd9183b | ||
|
|
5cb0bcfd9b | ||
|
|
1fbcfcaf74 | ||
|
|
3ba44a1e23 | ||
|
|
de4efbb555 | ||
|
|
6f443680f3 | ||
|
|
23b22e5ca8 | ||
|
|
1bba747ce5 | ||
|
|
9ebfc6646e | ||
|
|
055ff6dbbd | ||
|
|
6430e7b288 | ||
|
|
87b0050161 | ||
|
|
369d8b408d | ||
|
|
505abc592c | ||
|
|
c9df812258 | ||
|
|
0bfcf6b66f | ||
|
|
67853acabd | ||
|
|
79c98657b1 | ||
|
|
d1be7e44af | ||
|
|
33b853b981 | ||
|
|
e6063fb93a | ||
|
|
f30f23af59 | ||
|
|
d4798a3b22 | ||
|
|
eefc2a3d0e | ||
|
|
d14ca724e9 | ||
|
|
7b05aaffc3 | ||
|
|
f3beb5d8db | ||
|
|
e86b916415 | ||
|
|
e14cc6f2f0 | ||
|
|
8c1eb94401 | ||
|
|
29fa421945 | ||
|
|
7cfe98d988 | ||
|
|
e2314c350b | ||
|
|
3713b33578 | ||
|
|
e007a773fd | ||
|
|
e2821118eb | ||
|
|
4c8e73ac86 | ||
|
|
cb980fb814 | ||
|
|
41c84e3642 | ||
|
|
2bad98424f | ||
|
|
bc6b1e2dea | ||
|
|
911c15d1be | ||
|
|
f79d570870 | ||
|
|
7fffa9fba5 | ||
|
|
cbd634fb99 | ||
|
|
7ae7436d4f | ||
|
|
641bada100 | ||
|
|
3416d8d88e | ||
|
|
0bb503368b | ||
|
|
ac3a77c3c7 | ||
|
|
79b4178d76 | ||
|
|
42a61296d7 | ||
|
|
e8088e2a70 | ||
|
|
c4d39aced2 | ||
|
|
b40a5adeb0 | ||
|
|
558a900620 | ||
|
|
6b5e5a504d | ||
|
|
e44dca2464 | ||
|
|
e1f84b277a | ||
|
|
2518f46b08 | ||
|
|
01e18a9496 | ||
|
|
564ca709d3 | ||
|
|
a54a36ae18 | ||
|
|
43603b0961 | ||
|
|
96cd99f904 | ||
|
|
3438d10e25 | ||
|
|
022ccb42a1 | ||
|
|
e6d72e9f87 | ||
|
|
06e8a6af23 | ||
|
|
ac188d137a | ||
|
|
cae466745a | ||
|
|
d61f16dab0 | ||
|
|
02ba277a86 | ||
|
|
470ff49a02 | ||
|
|
04d741581d | ||
|
|
038f210148 | ||
|
|
2adad3a7bd | ||
|
|
05fb26a49b | ||
|
|
1c237affb4 | ||
|
|
3e81d7e9cb | ||
|
|
edb66620c1 | ||
|
|
04f7e8e777 | ||
|
|
eee201013c | ||
|
|
1190cb4ea1 | ||
|
|
507100ea0b | ||
|
|
9b13912b6d | ||
|
|
ee65deebfd | ||
|
|
ba9fa442d1 | ||
|
|
87da27f9bf | ||
|
|
b5bc5fe2c6 | ||
|
|
d2329360d0 | ||
|
|
7ece0ae10a | ||
|
|
f931b47eb8 | ||
|
|
7f7eb12ded | ||
|
|
c0940f7a19 | ||
|
|
9dfde11e35 | ||
|
|
6f15cc2dbc | ||
|
|
120308638f | ||
|
|
1d04ef99bb | ||
|
|
9b00d177ef | ||
|
|
884524c448 | ||
|
|
3ae1e7e87d | ||
|
|
81f885311d | ||
|
|
d9362f09d8 | ||
|
|
906d181d1b | ||
|
|
44b8812a7b | ||
|
|
3308c45e88 | ||
|
|
e530ecf9f9 | ||
|
|
51b5edb04f | ||
|
|
f0d89f850e | ||
|
|
b777e08542 | ||
|
|
2e485df530 | ||
|
|
3c37d22a6e | ||
|
|
08ab7a504a | ||
|
|
06563ef921 | ||
|
|
34f6210bc0 | ||
|
|
0bbde0c605 | ||
|
|
a8f24fd1b7 | ||
|
|
c3e0237696 | ||
|
|
bb6925920f | ||
|
|
63ec2a33ae | ||
|
|
c89a959fe8 | ||
|
|
150b50e0ba | ||
|
|
4ef824f665 | ||
|
|
5a56cca0aa | ||
|
|
b9189d7647 | ||
|
|
20226c914b | ||
|
|
434e7f8a09 | ||
|
|
a29d733a02 | ||
|
|
9abe4b967b | ||
|
|
3b6a4ece0f | ||
|
|
28d2471b4d | ||
|
|
d122af9fed | ||
|
|
77271f3856 | ||
|
|
ededfb68a6 | ||
|
|
4a3affdd24 | ||
|
|
8f8ea120d3 | ||
|
|
0fa88009f8 | ||
|
|
4375a807df | ||
|
|
b2d97c5908 | ||
|
|
ec89dd606d | ||
|
|
198508a7c3 | ||
|
|
4845e986bb | ||
|
|
1da8a307fc | ||
|
|
b4886e604e | ||
|
|
e84544136e | ||
|
|
ce70252a69 | ||
|
|
5c56962ea1 | ||
|
|
d2ed53b946 | ||
|
|
a4da80b498 | ||
|
|
9bd01492b1 | ||
|
|
da032941b4 | ||
|
|
c138fcc2e2 | ||
|
|
cbb69b0350 | ||
|
|
a8aed3354d | ||
|
|
e8790a4d4c | ||
|
|
df6ef3aaa0 | ||
|
|
2820d99f7b | ||
|
|
077aa4445a | ||
|
|
23bfc119d9 | ||
|
|
ab712ac637 | ||
|
|
b056826e94 | ||
|
|
6311627899 | ||
|
|
37cea5fb61 | ||
|
|
655a8cd60d | ||
|
|
4c8babc96a | ||
|
|
612bacebed | ||
|
|
ade7c8566d | ||
|
|
19553ce5c8 | ||
|
|
18ed2527e8 | ||
|
|
b0652bc884 | ||
|
|
15c9ad23fe | ||
|
|
578bb12562 | ||
|
|
f82cfda07f | ||
|
|
9e52b2788d | ||
|
|
2e56a113d9 | ||
|
|
4722d777e6 | ||
|
|
2141d54ae0 | ||
|
|
e346225136 | ||
|
|
012d4dae56 | ||
|
|
b4d9fe70af | ||
|
|
85e83b5441 | ||
|
|
6b2a453b8f | ||
|
|
27021538d8 | ||
|
|
8b57a2b055 | ||
|
|
75dd894685 | ||
|
|
9101ef8774 | ||
|
|
5932540630 | ||
|
|
ec376b2e47 | ||
|
|
a176562ad0 | ||
|
|
becf37b676 | ||
|
|
9b5efab8f8 | ||
|
|
e98a8ba599 | ||
|
|
7ddac50008 | ||
|
|
9837ae359f | ||
|
|
710a829dcb | ||
|
|
ccd84fa454 | ||
|
|
335b36d3a9 | ||
|
|
2be30fae00 | ||
|
|
db5cd21884 | ||
|
|
bfd3020031 | ||
|
|
344c36997a | ||
|
|
dfd9272b70 | ||
|
|
359f4520f5 | ||
|
|
aecf014f4e | ||
|
|
d2a89ddf84 | ||
|
|
c01fe153ae | ||
|
|
4f4a838799 | ||
|
|
ac6f2567eb | ||
|
|
05a5816ac6 | ||
|
|
9c8f6e9195 | ||
|
|
2fd001f6d2 | ||
|
|
d641d32413 | ||
|
|
18064ef6a2 | ||
|
|
5cb9216add | ||
|
|
91c36dc810 | ||
|
|
6efb02fa32 | ||
|
|
97313e4180 | ||
|
|
568ab24fd9 | ||
|
|
5a745efcd3 | ||
|
|
c651570e62 | ||
|
|
8980598085 | ||
|
|
c07c742feb | ||
|
|
1053abb9a9 | ||
|
|
2c9e57cbb1 | ||
|
|
c6eaa2c8a6 | ||
|
|
5ab5e913ee | ||
|
|
cea53ca476 | ||
|
|
58af09114b | ||
|
|
c4c0417e2d | ||
|
|
74f90e6947 | ||
|
|
ad5c339780 | ||
|
|
305823db00 | ||
|
|
baf58b298f | ||
|
|
c37367d018 | ||
|
|
1c98796e64 | ||
|
|
e686d9a6ea | ||
|
|
a1936b9d59 | ||
|
|
834f9c9337 | ||
|
|
615f8cfd3b | ||
|
|
8ed134105f | ||
|
|
5d6169b270 | ||
|
|
e83de8b938 | ||
|
|
ee55e039b2 | ||
|
|
086dd89144 | ||
|
|
68e5d4dd2c | ||
|
|
55a35c6bec | ||
|
|
d09b4885fe | ||
|
|
a46773e6d8 | ||
|
|
a422d0220c | ||
|
|
e5eba8430a | ||
|
|
3d235dc316 | ||
|
|
80d3b4be8c | ||
|
|
fe8b7480df | ||
|
|
cebfc3aaa0 | ||
|
|
f778b5a12d | ||
|
|
2244050160 | ||
|
|
9284e42b62 | ||
|
|
ee40120496 | ||
|
|
30cd2149ea | ||
|
|
395df36d57 | ||
|
|
79597ea0e5 | ||
|
|
283f39270a | ||
|
|
7d892bb19d | ||
|
|
a025f124f3 | ||
|
|
84f7287bf8 | ||
|
|
a58544b502 | ||
|
|
4d26175ebe | ||
|
|
78f0e6ff6b | ||
|
|
3af97af634 | ||
|
|
2c2663c8a4 | ||
|
|
1122b8a2f7 | ||
|
|
5b9f38948b | ||
|
|
507eb3b424 | ||
|
|
56fbc0ed6c | ||
|
|
7aaad314e3 | ||
|
|
356949dd54 | ||
|
|
9878baca53 | ||
|
|
9cbc7c2939 | ||
|
|
4680b63911 | ||
|
|
ce4a2d95f2 | ||
|
|
b2e048de8d | ||
|
|
d25a9d7515 | ||
|
|
dc130d3705 | ||
|
|
2391850218 | ||
|
|
c8f7ca920e | ||
|
|
e3e39af6fb | ||
|
|
f38114f5a5 | ||
|
|
1ee9d041df | ||
|
|
9c6f412f04 | ||
|
|
4fa0f2d04a | ||
|
|
e566a66ea4 | ||
|
|
58a42abc67 | ||
|
|
5676bd9d0d | ||
|
|
9691010e7b | ||
|
|
d19be3ad52 | ||
|
|
ec3cbf788b | ||
|
|
1282fd0b76 | ||
|
|
93430e5607 | ||
|
|
14201f4052 | ||
|
|
47979bf16d | ||
|
|
29530f3b17 | ||
|
|
af548e6ef8 | ||
|
|
ed24a9c990 | ||
|
|
cb1d86d08b | ||
|
|
88f3f628ef | ||
|
|
295bea37bc | ||
|
|
bd7d756254 | ||
|
|
4261147fe8 | ||
|
|
a70adc5eb3 | ||
|
|
0d51b04d79 | ||
|
|
06d40b8a81 | ||
|
|
2358510cba | ||
|
|
e6d13cb7d7 | ||
|
|
39e21c3f36 | ||
|
|
8da900ee72 | ||
|
|
9f4e81a1a3 | ||
|
|
0b918c2f51 | ||
|
|
085cd2a314 | ||
|
|
98d2399568 | ||
|
|
515d9a0008 | ||
|
|
aece1fa7d3 | ||
|
|
abc614ecfd | ||
|
|
1180d3fdde | ||
|
|
1639d1725a | ||
|
|
5df1deecbc | ||
|
|
fe3c0cf76e | ||
|
|
cc0df0182c | ||
|
|
02c530dcbe | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 | ||
|
|
4ad7e1f8e6 | ||
|
|
2007ba0c3b | ||
|
|
2009dc11db | ||
|
|
62f2196a0c | ||
|
|
e63c65da4f | ||
|
|
570a082227 | ||
|
|
c445fc0f8a | ||
|
|
699493cf24 | ||
|
|
6c89686f31 |
@@ -8,7 +8,6 @@ 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/db/migration.db-journal
|
||||||
|
|||||||
13
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
13
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -2,20 +2,25 @@ name: 🐞 Bug report
|
|||||||
description: Create a bug report to help us improve coolify
|
description: Create a bug report to help us improve coolify
|
||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
labels: [Bug]
|
labels: [Bug]
|
||||||
assignees:
|
|
||||||
- andrasbacsai
|
|
||||||
- vasani-arpit
|
|
||||||
body:
|
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
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ name: 🛠️ Feature request
|
|||||||
description: Suggest an idea to improve coolify
|
description: Suggest an idea to improve coolify
|
||||||
title: '[Feature]: '
|
title: '[Feature]: '
|
||||||
labels: [Enhancement]
|
labels: [Enhancement]
|
||||||
assignees:
|
|
||||||
- andrasbacsai
|
|
||||||
- vasani-arpit
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
93
.github/workflows/fluent-bit-release.yml
vendored
93
.github/workflows/fluent-bit-release.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
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
93
.github/workflows/pocketbase-release.yml
vendored
@@ -1,93 +0,0 @@
|
|||||||
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
|
|
||||||
106
.github/workflows/production-release.yml
vendored
106
.github/workflows/production-release.yml
vendored
@@ -1,91 +1,81 @@
|
|||||||
name: production-release
|
name: Production Release to ghcr.io
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [released]
|
types: [released]
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
jobs:
|
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: 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/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
|
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
- name: Get current package version
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
- name: Extract metadata
|
||||||
id: package-version
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
- name: Get current package version
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
- name: Extract metadata
|
||||||
id: package-version
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}-aarch64
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
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, arm64, aarch64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -93,18 +83,22 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
- name: Get current package version
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
- name: Extract metadata
|
||||||
id: package-version
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{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 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
|
||||||
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()
|
||||||
with:
|
with:
|
||||||
|
|||||||
90
.github/workflows/release-candidate.yml
vendored
90
.github/workflows/release-candidate.yml
vendored
@@ -1,90 +0,0 @@
|
|||||||
name: release-candidate
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [prereleased]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
arm64-making-something-cool:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- 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/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{github.event.release.name}}-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64,mode=max
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
amd64-making-something-cool:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- 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: 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@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{github.event.release.name}}-amd64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64,mode=max
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
merge-manifest-to-be-cool:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
|
||||||
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:${{github.event.release.name}} --amend coollabsio/coolify:${{github.event.release.name}}-amd64 --amend coollabsio/coolify:${{github.event.release.name}}-arm64
|
|
||||||
docker manifest push coollabsio/coolify:${{github.event.release.name}}
|
|
||||||
|
|
||||||
117
.github/workflows/staging-release.yml
vendored
117
.github/workflows/staging-release.yml
vendored
@@ -1,76 +1,76 @@
|
|||||||
name: staging-release
|
name: Staging Release to ghcr.io
|
||||||
|
concurrency:
|
||||||
|
group: staging_environment
|
||||||
|
cancel-in-progress: true
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
|
||||||
- "**"
|
|
||||||
- "!others/fluentbit"
|
|
||||||
- "!others/pocketbase"
|
|
||||||
- "!.github/workflows/fluent-bit-release.yml"
|
|
||||||
- "!.github/workflows/pocketbase-release.yml"
|
|
||||||
branches:
|
branches:
|
||||||
- next
|
- "v3"
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
arm64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- 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/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:next-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "next"
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
- name: Get current package version
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
- name: Extract metadata (tags, labels)
|
||||||
id: package-version
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:next-amd64
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
aarch64:
|
||||||
|
runs-on:
|
||||||
|
group: aarch-runners
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract metadata (tags, labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [arm64, amd64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -78,15 +78,20 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract metadata (tags, labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
|
||||||
docker manifest push coollabsio/coolify:next
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,20 +1,25 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
build
|
/apps/ui/build
|
||||||
|
/build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
dist
|
dist
|
||||||
client
|
|
||||||
apps/api/db/*.db
|
apps/api/db/*.db
|
||||||
apps/api/db/migration.db-journal
|
apps/api/db/migration.db-journal
|
||||||
apps/api/core*
|
apps/api/core*
|
||||||
|
apps/server/build
|
||||||
apps/backup/backups/*
|
apps/backup/backups/*
|
||||||
!apps/backup/backups/.gitkeep
|
!apps/backup/backups/.gitkeep
|
||||||
logs
|
/logs
|
||||||
others/certificates
|
others/certificates
|
||||||
backups/*
|
backups/*
|
||||||
!backups/.gitkeep
|
!backups/.gitkeep
|
||||||
|
|
||||||
|
# Trpc
|
||||||
|
apps/server/db/*.db
|
||||||
|
apps/server/db/*.db-journal
|
||||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -18,5 +18,14 @@
|
|||||||
"ts",
|
"ts",
|
||||||
"json"
|
"json"
|
||||||
],
|
],
|
||||||
"i18n-ally.extract.autoDetect": true
|
"i18n-ally.extract.autoDetect": true,
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/.svn": true,
|
||||||
|
"**/.hg": true,
|
||||||
|
"**/CVS": true,
|
||||||
|
"**/.DS_Store": true,
|
||||||
|
"**/Thumbs.db": true
|
||||||
|
},
|
||||||
|
"hide-files.files": []
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
|
|||||||
|
|
||||||
## 1) Setup your development environment
|
## 1) Setup your development environment
|
||||||
|
|
||||||
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recomended*
|
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recommended*
|
||||||
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
|
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
|
||||||
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md)
|
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md)
|
||||||
- ☁️ [GitPod](docs/dev_setup/GitPod.md)
|
- ☁️ [GitPod](docs/dev_setup/GitPod.md)
|
||||||
@@ -20,7 +20,7 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
|
|||||||
|
|
||||||
- [Install Pnpm](https://pnpm.io/installation)
|
- [Install Pnpm](https://pnpm.io/installation)
|
||||||
- [Install Docker Engine](https://docs.docker.com/engine/install/)
|
- [Install Docker Engine](https://docs.docker.com/engine/install/)
|
||||||
- [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/)
|
- [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/)
|
||||||
- [Setup GIT LFS Support](https://git-lfs.github.com/)
|
- [Setup GIT LFS Support](https://git-lfs.github.com/)
|
||||||
|
|
||||||
## 3) Setup Coolify
|
## 3) Setup Coolify
|
||||||
@@ -28,12 +28,12 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
|
|||||||
- Copy `apps/api/.env.example` to `apps/api/.env`
|
- Copy `apps/api/.env.example` to `apps/api/.env`
|
||||||
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
|
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||||
- Run `pnpm install` to install dependencies.
|
- 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:push` to create a local SQlite database. This will apply all migrations at `db/dev.db`.
|
||||||
- Run `pnpm db:seed` seed the database.
|
- Run `pnpm db:seed` seed the database.
|
||||||
- Run `pnpm dev` start coding.
|
- Run `pnpm dev` start coding.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Or... Copy and paste commands bellow:
|
# Or... Copy and paste commands below:
|
||||||
cp apps/api/.env.example apps/api/.env
|
cp apps/api/.env.example apps/api/.env
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm db:push
|
pnpm db:push
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ ARG DOCKER_VERSION=20.10.18
|
|||||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
# 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
|
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=v0.27.0
|
ARG PACK_VERSION=0.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 vim
|
||||||
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}
|
||||||
@@ -38,7 +38,7 @@ RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /
|
|||||||
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /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
|
||||||
COPY --from=build /app/apps/ui/build/ ./public
|
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 .
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ ARG DOCKER_VERSION=20.10.18
|
|||||||
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
|
# 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
|
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=v0.27.0
|
ARG PACK_VERSION=0.27.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -16,7 +16,7 @@ If you have a new service / build pack you would like to add, raise an idea [her
|
|||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
|
|
||||||
For more details goto the [docs](https://docs.coollabs.io/coolify/installation).
|
For more details goto the [docs](https://docs.coollabs.io/coolify-v3/installation).
|
||||||
|
|
||||||
Installation is automated with the following command:
|
Installation is automated with the following command:
|
||||||
|
|
||||||
@@ -79,9 +79,9 @@ Deploy your resource to:
|
|||||||
### Services
|
### Services
|
||||||
|
|
||||||
- [Appwrite](https://appwrite.io)
|
- [Appwrite](https://appwrite.io)
|
||||||
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress)
|
- [WordPress](https://docs.coollabs.io/coolify-v3/services/wordpress)
|
||||||
- [Ghost](https://ghost.org)
|
- [Ghost](https://ghost.org)
|
||||||
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics)
|
- [Plausible Analytics](https://docs.coollabs.io/coolify-v3/services/plausible-analytics)
|
||||||
- [NocoDB](https://nocodb.com)
|
- [NocoDB](https://nocodb.com)
|
||||||
- [VSCode Server](https://github.com/cdr/code-server)
|
- [VSCode Server](https://github.com/cdr/code-server)
|
||||||
- [MinIO](https://min.io)
|
- [MinIO](https://min.io)
|
||||||
@@ -100,7 +100,7 @@ Deploy your resource to:
|
|||||||
|
|
||||||
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@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)
|
- Twitter: [@andrasbacsai](https://twitter.com/heyandras)
|
||||||
- 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)
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ Learn how to contribute to Coolify as as ...
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
→ 🧑🏽🎨 Designer
|
→ 🧑🏽🎨 Designer
|
||||||
→ 🙋♀️ Community Managemer
|
→ 🙋♀️ Community Manager
|
||||||
→ 🧙🏻♂️ Text Content Creator
|
→ 🧙🏻♂️ Text Content Creator
|
||||||
→ 👨🏼🎤 Video Content Creator
|
→ 👨🏼🎤 Video Content Creator
|
||||||
-->
|
-->
|
||||||
@@ -130,12 +130,12 @@ Learn how to contribute to Coolify as as ...
|
|||||||
|
|
||||||
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)]
|
||||||
|
|
||||||
### Individuals
|
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
|
||||||
|
|
||||||
### Organizations
|
### Organizations
|
||||||
|
|
||||||
|
Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
@@ -148,3 +148,11 @@ Support this project with your organization. Your logo will show up here with a
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
### Individuals
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||||
|
|||||||
2
apps/api/.gitignore
vendored
2
apps/api/.gitignore
vendored
@@ -9,3 +9,5 @@ package
|
|||||||
dist
|
dist
|
||||||
dev.db
|
dev.db
|
||||||
client
|
client
|
||||||
|
testTemplate.yaml
|
||||||
|
testTags.json
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,85 +1,83 @@
|
|||||||
{
|
{
|
||||||
"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:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:push": "prisma db push && prisma generate",
|
"db:push": "prisma db push && prisma generate",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||||
"dev": "nodemon",
|
"dev": "nodemon",
|
||||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
"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"
|
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.5.0",
|
"@fastify/autoload": "5.7.0",
|
||||||
"@fastify/cookie": "8.3.0",
|
"@fastify/cookie": "8.3.0",
|
||||||
"@fastify/cors": "8.2.0",
|
"@fastify/cors": "8.2.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/env": "4.2.0",
|
||||||
"@fastify/jwt": "6.3.3",
|
"@fastify/jwt": "6.5.0",
|
||||||
"@fastify/multipart": "7.3.0",
|
"@fastify/multipart": "7.4.1",
|
||||||
"@fastify/static": "6.5.1",
|
"@fastify/static": "6.6.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@ladjs/graceful": "3.0.2",
|
"@ladjs/graceful": "3.2.1",
|
||||||
"@prisma/client": "4.6.1",
|
"@prisma/client": "4.8.1",
|
||||||
"@sentry/node": "7.21.1",
|
"axe": "11.2.1",
|
||||||
"@sentry/tracing": "7.21.1",
|
"bcryptjs": "2.4.3",
|
||||||
"axe": "11.0.0",
|
"bree": "9.1.3",
|
||||||
"bcryptjs": "2.4.3",
|
"cabin": "11.1.1",
|
||||||
"bree": "9.1.2",
|
"compare-versions": "5.0.1",
|
||||||
"cabin": "11.0.1",
|
"csv-parse": "5.3.3",
|
||||||
"compare-versions": "5.0.1",
|
"csvtojson": "2.0.10",
|
||||||
"csv-parse": "5.3.2",
|
"cuid": "2.1.8",
|
||||||
"csvtojson": "2.0.10",
|
"dayjs": "1.11.7",
|
||||||
"cuid": "2.1.8",
|
"dockerode": "3.3.4",
|
||||||
"dayjs": "1.11.6",
|
"dotenv-extended": "2.9.0",
|
||||||
"dockerode": "3.3.4",
|
"execa": "6.1.0",
|
||||||
"dotenv-extended": "2.9.0",
|
"fastify": "4.11.0",
|
||||||
"execa": "6.1.0",
|
"fastify-plugin": "4.3.0",
|
||||||
"fastify": "4.10.2",
|
"fastify-socket.io": "4.0.0",
|
||||||
"fastify-plugin": "4.3.0",
|
"generate-password": "1.7.0",
|
||||||
"fastify-socket.io": "4.0.0",
|
"got": "12.5.3",
|
||||||
"generate-password": "1.7.0",
|
"is-ip": "5.0.0",
|
||||||
"got": "12.5.3",
|
"is-port-reachable": "4.0.0",
|
||||||
"is-ip": "5.0.0",
|
"js-yaml": "4.1.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"jsonwebtoken": "9.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"minimist": "^1.2.7",
|
||||||
"jsonwebtoken": "8.5.1",
|
"node-forge": "1.3.1",
|
||||||
"minimist": "^1.2.7",
|
"node-os-utils": "1.3.7",
|
||||||
"node-forge": "1.3.1",
|
"p-all": "4.0.0",
|
||||||
"node-os-utils": "1.3.7",
|
"p-throttle": "5.0.0",
|
||||||
"p-all": "4.0.0",
|
"prisma": "4.8.1",
|
||||||
"p-throttle": "5.0.0",
|
"public-ip": "6.0.1",
|
||||||
"prisma": "4.6.1",
|
"pump": "3.0.0",
|
||||||
"public-ip": "6.0.1",
|
"shell-quote": "^1.7.4",
|
||||||
"pump": "3.0.0",
|
"socket.io": "4.5.4",
|
||||||
"shell-quote": "^1.7.4",
|
"ssh-config": "4.2.0",
|
||||||
"socket.io": "4.5.3",
|
"strip-ansi": "7.0.1",
|
||||||
"ssh-config": "4.1.6",
|
"unique-names-generator": "4.7.1"
|
||||||
"strip-ansi": "7.0.1",
|
},
|
||||||
"unique-names-generator": "4.7.1"
|
"devDependencies": {
|
||||||
},
|
"@types/node": "18.11.18",
|
||||||
"devDependencies": {
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@types/node": "18.11.9",
|
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@typescript-eslint/parser": "5.48.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
"esbuild": "0.16.16",
|
||||||
"@typescript-eslint/parser": "5.44.0",
|
"eslint": "8.31.0",
|
||||||
"esbuild": "0.15.15",
|
"eslint-config-prettier": "8.6.0",
|
||||||
"eslint": "8.28.0",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"nodemon": "2.0.20",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"prettier": "2.8.2",
|
||||||
"nodemon": "2.0.20",
|
"rimraf": "3.0.2",
|
||||||
"prettier": "2.7.1",
|
"tsconfig-paths": "4.1.2",
|
||||||
"rimraf": "3.0.2",
|
"types-fastify-socket.io": "0.0.1",
|
||||||
"tsconfig-paths": "4.1.0",
|
"typescript": "4.9.4"
|
||||||
"types-fastify-socket.io": "0.0.1",
|
},
|
||||||
"typescript": "4.9.3"
|
"prisma": {
|
||||||
},
|
"seed": "node prisma/seed.js"
|
||||||
"prisma": {
|
}
|
||||||
"seed": "node prisma/seed.js"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_GitSource" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"type" TEXT,
|
||||||
|
"apiUrl" TEXT,
|
||||||
|
"htmlUrl" TEXT,
|
||||||
|
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||||
|
"customUser" TEXT NOT NULL DEFAULT 'git',
|
||||||
|
"organization" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"githubAppId" TEXT,
|
||||||
|
"gitlabAppId" TEXT,
|
||||||
|
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||||
|
DROP TABLE "GitSource";
|
||||||
|
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||||
|
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||||
|
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||||
|
DROP TABLE "ApplicationSettings";
|
||||||
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||||
|
DROP TABLE "ApplicationSettings";
|
||||||
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -135,6 +135,8 @@ model Application {
|
|||||||
dockerRegistryId String?
|
dockerRegistryId String?
|
||||||
dockerRegistryImageName String?
|
dockerRegistryImageName String?
|
||||||
simpleDockerfile String?
|
simpleDockerfile String?
|
||||||
|
basicAuthUser String?
|
||||||
|
basicAuthPw String?
|
||||||
|
|
||||||
persistentStorage ApplicationPersistentStorage[]
|
persistentStorage ApplicationPersistentStorage[]
|
||||||
secrets Secret[]
|
secrets Secret[]
|
||||||
@@ -186,6 +188,8 @@ model ApplicationSettings {
|
|||||||
isPublicRepository Boolean @default(false)
|
isPublicRepository Boolean @default(false)
|
||||||
isDBBranching Boolean @default(false)
|
isDBBranching Boolean @default(false)
|
||||||
isCustomSSL Boolean @default(false)
|
isCustomSSL Boolean @default(false)
|
||||||
|
isHttp2 Boolean @default(false)
|
||||||
|
basicAuth 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])
|
||||||
@@ -194,6 +198,7 @@ model ApplicationSettings {
|
|||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
applicationId String
|
applicationId String
|
||||||
|
hostPath String?
|
||||||
path String
|
path String
|
||||||
oldPath Boolean @default(false)
|
oldPath Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -325,6 +330,7 @@ model GitSource {
|
|||||||
apiUrl String?
|
apiUrl String?
|
||||||
htmlUrl String?
|
htmlUrl String?
|
||||||
customPort Int @default(22)
|
customPort Int @default(22)
|
||||||
|
customUser String @default("git")
|
||||||
organization String?
|
organization String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async function main() {
|
|||||||
await prisma.setting.create({
|
await prisma.setting.create({
|
||||||
data: {
|
data: {
|
||||||
id: '0',
|
id: '0',
|
||||||
arch: process.arch,
|
arch: process.arch
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -81,16 +81,295 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Set new preview secrets
|
// Set new preview secrets
|
||||||
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } })
|
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } });
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
for (const secret of secrets) {
|
for (const secret of secrets) {
|
||||||
const previewSecrets = await prisma.secret.findMany({ where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true } })
|
const previewSecrets = await prisma.secret.findMany({
|
||||||
|
where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true }
|
||||||
|
});
|
||||||
if (previewSecrets.length === 0) {
|
if (previewSecrets.length === 0) {
|
||||||
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } })
|
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async function reEncryptSecrets() {
|
||||||
|
const { execaCommand } = await import('execa');
|
||||||
|
const image = await execaCommand("docker inspect coolify --format '{{ .Config.Image }}'", {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
const version = image.stdout.split(':')[1] ?? null;
|
||||||
|
const date = new Date().getTime();
|
||||||
|
|
||||||
|
let backupfile = `/app/db/prod.db_${date}`;
|
||||||
|
if (version) {
|
||||||
|
backupfile = `/app/db/prod.db_${version}_${date}`;
|
||||||
|
}
|
||||||
|
await execaCommand('env | grep "^COOLIFY" | sort > .env', {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
const secretOld = process.env['COOLIFY_SECRET_KEY'];
|
||||||
|
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||||
|
if (!secretNew) {
|
||||||
|
console.log('No COOLIFY_SECRET_KEY_BETTER found... Generating new one...');
|
||||||
|
const { stdout: newKey } = await execaCommand(
|
||||||
|
'openssl rand -base64 1024 | sha256sum | base64 | head -c 32',
|
||||||
|
{ shell: true }
|
||||||
|
);
|
||||||
|
secretNew = newKey;
|
||||||
|
}
|
||||||
|
if (secretOld !== secretNew) {
|
||||||
|
console.log(`Backup database to ${backupfile}.`);
|
||||||
|
await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true });
|
||||||
|
console.log(
|
||||||
|
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
|
||||||
|
);
|
||||||
|
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true });
|
||||||
|
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY_BETTER=/d' .env`, { shell: true });
|
||||||
|
await execaCommand(`echo "COOLIFY_SECRET_KEY=${secretNew}" >> .env`, { shell: true });
|
||||||
|
await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + secretNew + '" >> .env ', {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
const transactions = [];
|
||||||
|
const secrets = await prisma.secret.findMany();
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
for (const secret of secrets) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(secret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.secret.update({
|
||||||
|
where: { id: secret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const serviceSecrets = await prisma.serviceSecret.findMany();
|
||||||
|
if (serviceSecrets.length > 0) {
|
||||||
|
for (const secret of serviceSecrets) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(secret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.serviceSecret.update({
|
||||||
|
where: { id: secret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const gitlabApps = await prisma.gitlabApp.findMany();
|
||||||
|
if (gitlabApps.length > 0) {
|
||||||
|
for (const gitlabApp of gitlabApps) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(gitlabApp.privateSshKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
|
||||||
|
const newAppSecret = encrypt(appSecret, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.gitlabApp.update({
|
||||||
|
where: { id: gitlabApp.id },
|
||||||
|
data: { privateSshKey: newValue, appSecret: newAppSecret }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const githubApps = await prisma.githubApp.findMany();
|
||||||
|
if (githubApps.length > 0) {
|
||||||
|
for (const githubApp of githubApps) {
|
||||||
|
try {
|
||||||
|
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
|
||||||
|
const newClientSecret = encrypt(clientSecret, secretNew);
|
||||||
|
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
|
||||||
|
const newWebhookSecret = encrypt(webhookSecret, secretNew);
|
||||||
|
const privateKey = decrypt(githubApp.privateKey, secretOld);
|
||||||
|
const newPrivateKey = encrypt(privateKey, secretNew);
|
||||||
|
|
||||||
|
transactions.push(
|
||||||
|
prisma.githubApp.update({
|
||||||
|
where: { id: githubApp.id },
|
||||||
|
data: {
|
||||||
|
clientSecret: newClientSecret,
|
||||||
|
webhookSecret: newWebhookSecret,
|
||||||
|
privateKey: newPrivateKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const databases = await prisma.database.findMany();
|
||||||
|
if (databases.length > 0) {
|
||||||
|
for (const database of databases) {
|
||||||
|
try {
|
||||||
|
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
|
||||||
|
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
|
||||||
|
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
|
||||||
|
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.database.update({
|
||||||
|
where: { id: database.id },
|
||||||
|
data: {
|
||||||
|
dbUserPassword: newDbUserPassword,
|
||||||
|
rootUserPassword: newRootUserPassword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const databaseSecrets = await prisma.databaseSecret.findMany();
|
||||||
|
if (databaseSecrets.length > 0) {
|
||||||
|
for (const databaseSecret of databaseSecrets) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(databaseSecret.value, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.databaseSecret.update({
|
||||||
|
where: { id: databaseSecret.id },
|
||||||
|
data: { value: newValue }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wordpresses = await prisma.wordpress.findMany();
|
||||||
|
if (wordpresses.length > 0) {
|
||||||
|
for (const wordpress of wordpresses) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(wordpress.ftpHostKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
|
||||||
|
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
|
||||||
|
let newFtpPassword = undefined;
|
||||||
|
if (wordpress.ftpPassword != null) {
|
||||||
|
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
|
||||||
|
newFtpPassword = encrypt(ftpPassword, secretNew);
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions.push(
|
||||||
|
prisma.wordpress.update({
|
||||||
|
where: { id: wordpress.id },
|
||||||
|
data: {
|
||||||
|
ftpHostKey: newValue,
|
||||||
|
ftpHostKeyPrivate: newFtpHostKeyPrivate,
|
||||||
|
ftpPassword: newFtpPassword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sshKeys = await prisma.sshKey.findMany();
|
||||||
|
if (sshKeys.length > 0) {
|
||||||
|
for (const key of sshKeys) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(key.privateKey, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.sshKey.update({
|
||||||
|
where: { id: key.id },
|
||||||
|
data: {
|
||||||
|
privateKey: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dockerRegistries = await prisma.dockerRegistry.findMany();
|
||||||
|
if (dockerRegistries.length > 0) {
|
||||||
|
for (const registry of dockerRegistries) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(registry.password, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.dockerRegistry.update({
|
||||||
|
where: { id: registry.id },
|
||||||
|
data: {
|
||||||
|
password: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const certificates = await prisma.certificate.findMany();
|
||||||
|
if (certificates.length > 0) {
|
||||||
|
for (const certificate of certificates) {
|
||||||
|
try {
|
||||||
|
const value = decrypt(certificate.key, secretOld);
|
||||||
|
const newValue = encrypt(value, secretNew);
|
||||||
|
transactions.push(
|
||||||
|
prisma.certificate.update({
|
||||||
|
where: { id: certificate.id },
|
||||||
|
data: {
|
||||||
|
key: newValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await prisma.$transaction(transactions);
|
||||||
|
} else {
|
||||||
|
console.log('secrets are the same, so no need to re-encrypt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypt = (text, secret) => {
|
||||||
|
if (text && secret) {
|
||||||
|
const iv = crypto.randomBytes(16);
|
||||||
|
const cipher = crypto.createCipheriv(algorithm, secret, iv);
|
||||||
|
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||||
|
return JSON.stringify({
|
||||||
|
iv: iv.toString('hex'),
|
||||||
|
content: encrypted.toString('hex')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const decrypt = (hashString, secret) => {
|
||||||
|
if (hashString && secret) {
|
||||||
|
const hash = JSON.parse(hashString);
|
||||||
|
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
|
||||||
|
const decrpyted = Buffer.concat([
|
||||||
|
decipher.update(Buffer.from(hash.content, 'hex')),
|
||||||
|
decipher.final()
|
||||||
|
]);
|
||||||
|
if (/<2F>/.test(decrpyted.toString())) {
|
||||||
|
throw new Error('Invalid secret. Skipping...');
|
||||||
|
}
|
||||||
|
return decrpyted.toString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
main()
|
main()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -99,15 +378,11 @@ main()
|
|||||||
.finally(async () => {
|
.finally(async () => {
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
});
|
});
|
||||||
|
reEncryptSecrets()
|
||||||
const encrypt = (text) => {
|
.catch((e) => {
|
||||||
if (text) {
|
console.error(e);
|
||||||
const iv = crypto.randomBytes(16);
|
process.exit(1);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
})
|
||||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
.finally(async () => {
|
||||||
return JSON.stringify({
|
await prisma.$disconnect();
|
||||||
iv: iv.toString('hex'),
|
});
|
||||||
content: encrypted.toString('hex')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,35 +1,47 @@
|
|||||||
import Fastify from 'fastify';
|
|
||||||
import cors from '@fastify/cors';
|
|
||||||
import serve from '@fastify/static';
|
|
||||||
import env from '@fastify/env';
|
|
||||||
import cookie from '@fastify/cookie';
|
|
||||||
import multipart from '@fastify/multipart';
|
|
||||||
import path, { join } from 'path';
|
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import socketIO from 'fastify-socket.io'
|
import cookie from '@fastify/cookie';
|
||||||
import socketIOServer from './realtime'
|
import cors from '@fastify/cors';
|
||||||
|
import env from '@fastify/env';
|
||||||
|
import multipart from '@fastify/multipart';
|
||||||
|
import serve from '@fastify/static';
|
||||||
|
import Fastify from 'fastify';
|
||||||
|
import socketIO from 'fastify-socket.io';
|
||||||
|
import path, { join } from 'path';
|
||||||
|
import socketIOServer from './realtime';
|
||||||
|
|
||||||
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
|
import Graceful from '@ladjs/graceful';
|
||||||
import { scheduler } from './lib/scheduler';
|
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import Graceful from '@ladjs/graceful'
|
|
||||||
import yaml from 'js-yaml'
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
import yaml from 'js-yaml';
|
||||||
import { checkContainer } from './lib/docker';
|
|
||||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||||
|
import {
|
||||||
|
cleanupDockerStorage,
|
||||||
|
createRemoteEngineConfiguration,
|
||||||
|
decrypt,
|
||||||
|
executeCommand,
|
||||||
|
generateDatabaseConfiguration,
|
||||||
|
isDev,
|
||||||
|
listSettings,
|
||||||
|
prisma,
|
||||||
|
startTraefikProxy,
|
||||||
|
startTraefikTCPProxy,
|
||||||
|
version
|
||||||
|
} from './lib/common';
|
||||||
|
import { checkContainer } from './lib/docker';
|
||||||
|
import { scheduler } from './lib/scheduler';
|
||||||
|
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||||
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
|
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_SECRET_KEY_BETTER: string | null;
|
||||||
COOLIFY_IS_ON: string,
|
COOLIFY_DATABASE_URL: string;
|
||||||
COOLIFY_WHITE_LABELED: string,
|
COOLIFY_IS_ON: string;
|
||||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
COOLIFY_WHITE_LABELED: string;
|
||||||
COOLIFY_AUTO_UPDATE: string,
|
COOLIFY_WHITE_LABELED_ICON: string | null;
|
||||||
|
COOLIFY_AUTO_UPDATE: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +50,7 @@ const port = isDev ? 3001 : 3000;
|
|||||||
const host = '0.0.0.0';
|
const host = '0.0.0.0';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const settings = await prisma.setting.findFirst()
|
const settings = await prisma.setting.findFirst();
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
logger: settings?.isAPIDebuggingEnabled || false,
|
logger: settings?.isAPIDebuggingEnabled || false,
|
||||||
trustProxy: true
|
trustProxy: true
|
||||||
@@ -49,10 +61,14 @@ const host = '0.0.0.0';
|
|||||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||||
properties: {
|
properties: {
|
||||||
COOLIFY_APP_ID: {
|
COOLIFY_APP_ID: {
|
||||||
type: 'string',
|
type: 'string'
|
||||||
},
|
},
|
||||||
COOLIFY_SECRET_KEY: {
|
COOLIFY_SECRET_KEY: {
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
|
COOLIFY_SECRET_KEY_BETTER: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
COOLIFY_DATABASE_URL: {
|
COOLIFY_DATABASE_URL: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -73,8 +89,7 @@ const host = '0.0.0.0';
|
|||||||
COOLIFY_AUTO_UPDATE: {
|
COOLIFY_AUTO_UPDATE: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'false'
|
default: 'false'
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const options = {
|
const options = {
|
||||||
@@ -103,13 +118,13 @@ const host = '0.0.0.0';
|
|||||||
fastify.register(autoLoad, {
|
fastify.register(autoLoad, {
|
||||||
dir: join(__dirname, 'routes')
|
dir: join(__dirname, 'routes')
|
||||||
});
|
});
|
||||||
fastify.register(cookie)
|
fastify.register(cookie);
|
||||||
fastify.register(cors);
|
fastify.register(cors);
|
||||||
fastify.register(socketIO, {
|
fastify.register(socketIO, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: isDev ? "*" : ''
|
origin: isDev ? '*' : ''
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// To detect allowed origins
|
// To detect allowed origins
|
||||||
// fastify.addHook('onRequest', async (request, reply) => {
|
// fastify.addHook('onRequest', async (request, reply) => {
|
||||||
// console.log(request.headers.host)
|
// console.log(request.headers.host)
|
||||||
@@ -131,10 +146,9 @@ const host = '0.0.0.0';
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fastify.listen({ port, host })
|
await fastify.listen({ port, host });
|
||||||
await socketIOServer(fastify)
|
await socketIOServer(fastify);
|
||||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||||
|
|
||||||
migrateServicesToNewTemplate();
|
migrateServicesToNewTemplate();
|
||||||
@@ -148,106 +162,125 @@ const host = '0.0.0.0';
|
|||||||
if (!scheduler.workers.has('deployApplication')) {
|
if (!scheduler.workers.has('deployApplication')) {
|
||||||
scheduler.run('deployApplication');
|
scheduler.run('deployApplication');
|
||||||
}
|
}
|
||||||
}, 2000)
|
}, 2000);
|
||||||
|
|
||||||
// autoUpdater
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await autoUpdater()
|
await autoUpdater();
|
||||||
}, 60000 * 15)
|
}, 60000 * 60);
|
||||||
|
|
||||||
// cleanupStorage
|
// cleanupStorage
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await cleanupStorage()
|
await cleanupStorage();
|
||||||
}, 60000 * 10)
|
}, 60000 * 15);
|
||||||
|
|
||||||
|
// Cleanup stucked containers (not defined in Coolify, but still running and managed by Coolify)
|
||||||
|
setInterval(async () => {
|
||||||
|
await cleanupStuckedContainers();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
// checkProxies, checkFluentBit & refresh templates
|
// checkProxies, checkFluentBit & refresh templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await checkProxies();
|
await checkProxies();
|
||||||
await checkFluentBit();
|
await checkFluentBit();
|
||||||
}, 60000)
|
}, 60000);
|
||||||
|
|
||||||
// Refresh and check templates
|
// Refresh and check templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTemplates()
|
await refreshTemplates();
|
||||||
}, 60000)
|
}, 60000 * 10);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTags()
|
await refreshTags();
|
||||||
}, 60000)
|
}, 60000 * 10);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(
|
||||||
await migrateServicesToNewTemplate()
|
async () => {
|
||||||
}, isDev ? 10000 : 60000)
|
await migrateServicesToNewTemplate();
|
||||||
|
},
|
||||||
|
isDev ? 10000 : 60000 * 10
|
||||||
|
);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await copySSLCertificates();
|
await copySSLCertificates();
|
||||||
}, 10000)
|
}, 10000);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getTagsTemplates(),
|
getTagsTemplates(),
|
||||||
getArch(),
|
getArch(),
|
||||||
getIPAddress(),
|
getIPAddress(),
|
||||||
configureRemoteDockers(),
|
configureRemoteDockers(),
|
||||||
])
|
refreshTemplates(),
|
||||||
|
refreshTags()
|
||||||
|
// cleanupStuckedContainers()
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
async function getIPAddress() {
|
async function getIPAddress() {
|
||||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
const { publicIpv4, publicIpv6 } = await import('public-ip');
|
||||||
try {
|
try {
|
||||||
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...`);
|
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...`);
|
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 getTagsTemplates() {
|
async function getTagsTemplates() {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
|
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
|
try {
|
||||||
await fs.writeFile('./tags.json', tags)
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
console.log('[004] Tags and templates loaded in dev mode...')
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
} else {
|
}
|
||||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
} catch (error) { }
|
||||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
try {
|
||||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
if (await fs.stat('./testTags.json')) {
|
||||||
await fs.writeFile('/app/tags.json', tags)
|
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||||
console.log('[004] Tags and templates loaded...')
|
if (testTags.length > 0) {
|
||||||
}
|
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
console.log("Couldn't get latest templates.")
|
console.log("Couldn't get latest templates.");
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function initServer() {
|
async function initServer() {
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } })
|
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||||
try {
|
try {
|
||||||
if (settings.doNotTrack === true) {
|
if (settings.doNotTrack === true) {
|
||||||
console.log('[000] Telemetry disabled...')
|
console.log('[000] Telemetry disabled...');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (settings.sentryDSN !== sentryDSN) {
|
|
||||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } })
|
|
||||||
}
|
|
||||||
// Initialize Sentry
|
// Initialize Sentry
|
||||||
// Sentry.init({
|
// Sentry.init({
|
||||||
// dsn: sentryDSN,
|
// dsn: sentryDSN,
|
||||||
@@ -257,7 +290,7 @@ async function initServer() {
|
|||||||
// console.log('[000] Sentry initialized...')
|
// console.log('[000] Sentry initialized...')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log(`[001] Initializing server...`);
|
console.log(`[001] Initializing server...`);
|
||||||
@@ -267,27 +300,73 @@ async function initServer() {
|
|||||||
console.log(`[002] Cleanup stucked builds...`);
|
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 {
|
try {
|
||||||
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||||
await fs.rm('/tmp/build-sources', { recursive: true, force: true })
|
if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(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...`);
|
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) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cleanupStuckedContainers() {
|
||||||
|
try {
|
||||||
|
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) {
|
||||||
|
if (!destination.remoteVerified) continue;
|
||||||
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
|
}
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: destination.id,
|
||||||
|
command: `docker container ps -a --filter "label=coolify.managed=true" --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containersArray = containers.trim().split('\n');
|
||||||
|
if (containersArray.length > 0) {
|
||||||
|
for (const container of containersArray) {
|
||||||
|
const containerId = container.split('-')[0];
|
||||||
|
const application = await prisma.application.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
const service = await prisma.service.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
const database = await prisma.database.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
if (!application && !service && !database) {
|
||||||
|
await executeCommand({ command: `docker container rm -f ${container}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function configureRemoteDockers() {
|
async function configureRemoteDockers() {
|
||||||
try {
|
try {
|
||||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||||
@@ -296,37 +375,51 @@ async function configureRemoteDockers() {
|
|||||||
if (remoteDocker.length > 0) {
|
if (remoteDocker.length > 0) {
|
||||||
console.log(`Verifying Remote Docker Engines...`);
|
console.log(`Verifying Remote Docker Engines...`);
|
||||||
for (const docker of remoteDocker) {
|
for (const docker of remoteDocker) {
|
||||||
console.log('Verifying:', docker.remoteIpAddress)
|
console.log('Verifying:', docker.remoteIpAddress);
|
||||||
await verifyRemoteDockerEngineFn(docker.id);
|
await verifyRemoteDockerEngineFn(docker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoUpdater() {
|
async function autoUpdater() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
const currentVersion = version;
|
const currentVersion = version;
|
||||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
const { coolify } = await got
|
||||||
searchParams: {
|
.get('https://get.coollabs.io/versions.json', {
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
searchParams: {
|
||||||
version: currentVersion
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
}
|
version: currentVersion
|
||||||
}).json()
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
const latestVersion = coolify.main.version;
|
const latestVersion = coolify.main.version;
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
if (isUpdateAvailable === 1) {
|
if (isUpdateAvailable === 1) {
|
||||||
const activeCount = 0
|
const activeCount = 0;
|
||||||
if (activeCount === 0) {
|
if (activeCount === 0) {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
if (isAutoUpdateEnabled) {
|
if (isAutoUpdateEnabled) {
|
||||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
|
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` })
|
try {
|
||||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
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"` })
|
} catch (error) {
|
||||||
|
image = `coollabsio/coolify:${latestVersion}`;
|
||||||
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .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 ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .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 {
|
} else {
|
||||||
console.log('Updating (not really in dev mode).');
|
console.log('Updating (not really in dev mode).');
|
||||||
@@ -334,7 +427,7 @@ async function autoUpdater() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,14 +438,18 @@ async function checkFluentBit() {
|
|||||||
const { id } = await prisma.destinationDocker.findFirst({
|
const { id } = await prisma.destinationDocker.findFirst({
|
||||||
where: { engine, network: 'coolify' }
|
where: { engine, network: 'coolify' }
|
||||||
});
|
});
|
||||||
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
|
const { found } = await checkContainer({
|
||||||
|
dockerId: id,
|
||||||
|
container: 'coolify-fluentbit',
|
||||||
|
remove: true
|
||||||
|
});
|
||||||
if (!found) {
|
if (!found) {
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||||
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function checkProxies() {
|
async function checkProxies() {
|
||||||
@@ -368,7 +465,7 @@ async function checkProxies() {
|
|||||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||||
});
|
});
|
||||||
if (localDocker) {
|
if (localDocker) {
|
||||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
portReachable = await isReachable(80, { host: ipv4 || ipv6 });
|
||||||
if (!portReachable) {
|
if (!portReachable) {
|
||||||
await startTraefikProxy(localDocker.id);
|
await startTraefikProxy(localDocker.id);
|
||||||
}
|
}
|
||||||
@@ -380,13 +477,13 @@ async function checkProxies() {
|
|||||||
if (remoteDocker.length > 0) {
|
if (remoteDocker.length > 0) {
|
||||||
for (const docker of remoteDocker) {
|
for (const docker of remoteDocker) {
|
||||||
if (docker.isCoolifyProxyUsed) {
|
if (docker.isCoolifyProxyUsed) {
|
||||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
portReachable = await isReachable(80, { host: docker.remoteIpAddress });
|
||||||
if (!portReachable) {
|
if (!portReachable) {
|
||||||
await startTraefikProxy(docker.id);
|
await startTraefikProxy(docker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await createRemoteEngineConfiguration(docker.id)
|
await createRemoteEngineConfiguration(docker.id);
|
||||||
} catch (error) { }
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -426,112 +523,148 @@ async function checkProxies() {
|
|||||||
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
} catch (error) {
|
} catch (error) { }
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copySSLCertificates() {
|
async function copySSLCertificates() {
|
||||||
try {
|
try {
|
||||||
const pAll = await import('p-all');
|
const pAll = await import('p-all');
|
||||||
const actions = []
|
const actions = [];
|
||||||
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
const certificates = await prisma.certificate.findMany({ include: { team: true } });
|
||||||
const teamIds = certificates.map(c => c.teamId)
|
const teamIds = certificates.map((c) => c.teamId);
|
||||||
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
const destinations = await prisma.destinationDocker.findMany({
|
||||||
|
where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } }
|
||||||
|
});
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
const { id, key, cert } = certificate
|
const { id, key, cert } = certificate;
|
||||||
const decryptedKey = decrypt(key)
|
const decryptedKey = decrypt(key);
|
||||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey);
|
||||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
await fs.writeFile(`/tmp/${id}-cert.pem`, cert);
|
||||||
for (const destination of destinations) {
|
for (const destination of destinations) {
|
||||||
if (destination.remoteEngine) {
|
if (destination.remoteEngine) {
|
||||||
if (destination.remoteVerified) {
|
if (destination.remoteVerified) {
|
||||||
const { id: dockerId, remoteIpAddress } = destination
|
const { id: dockerId, remoteIpAddress } = destination;
|
||||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actions.push(async () => copyLocalCertificates(id))
|
actions.push(async () => copyLocalCertificates(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await pAll.default(actions, { concurrency: 1 })
|
await pAll.default(actions, { concurrency: 1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
|
try {
|
||||||
|
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
||||||
try {
|
try {
|
||||||
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
|
await executeCommand({
|
||||||
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/'` })
|
command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`
|
||||||
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/` })
|
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) {
|
} catch (error) {
|
||||||
console.log({ error })
|
console.log({ error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function copyLocalCertificates(id: string) {
|
async function copyLocalCertificates(id: string) {
|
||||||
try {
|
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({
|
||||||
await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`,
|
||||||
await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/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) {
|
} catch (error) {
|
||||||
console.log({ error })
|
console.log({ error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupStorage() {
|
async function cleanupStorage() {
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
let enginesDone = new Set()
|
let enginesDone = new Set();
|
||||||
for (const destination of destinationDockers) {
|
for (const destination of destinationDockers) {
|
||||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
|
||||||
if (destination.engine) enginesDone.add(destination.engine)
|
if (destination.engine) {
|
||||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
enginesDone.add(destination.engine);
|
||||||
|
}
|
||||||
|
if (destination.remoteIpAddress) {
|
||||||
|
if (!destination.remoteVerified) continue;
|
||||||
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
|
}
|
||||||
|
await cleanupDockerStorage(destination.id);
|
||||||
|
// 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;
|
||||||
|
|
||||||
let lowDiskSpace = false;
|
// while ((match = regex.exec(header))) {
|
||||||
try {
|
// boundaries.push(match[0].length);
|
||||||
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[boundaries.length - 1] = -1;
|
||||||
boundaries.push(match[0].length);
|
// const data = lines.slice(1).map((line) => {
|
||||||
}
|
// const cl = boundaries.map((boundary) => {
|
||||||
|
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||||
boundaries[boundaries.length - 1] = -1;
|
// line = line.slice(boundary);
|
||||||
const data = lines.slice(1).map((line) => {
|
// return column.trim();
|
||||||
const cl = boundaries.map((boundary) => {
|
// });
|
||||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
// return {
|
||||||
line = line.slice(boundary);
|
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||||
return column.trim();
|
// };
|
||||||
});
|
// });
|
||||||
return {
|
// if (data.length > 0) {
|
||||||
capacity: Number.parseInt(cl[5], 10) / 100
|
// const { capacity } = data[0];
|
||||||
};
|
// if (capacity > 0.8) {
|
||||||
});
|
// lowDiskSpace = true;
|
||||||
if (data.length > 0) {
|
// }
|
||||||
const { capacity } = data[0];
|
// }
|
||||||
if (capacity > 0.8) {
|
// } catch (error) {}
|
||||||
lowDiskSpace = true;
|
// if (lowDiskSpace) {
|
||||||
}
|
// await cleanupDockerStorage(destination.id);
|
||||||
}
|
// }
|
||||||
} catch (error) { }
|
|
||||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,26 @@ 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, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, saveDockerRegistryCredentials, setDefaultConfiguration } from '../lib/buildPacks/common';
|
import {
|
||||||
import { createDirectories, decrypt, defaultComposeConfiguration, getDomain, prisma, decryptApplication, isDev, pushToRegistry, executeCommand } from '../lib/common';
|
copyBaseConfigurationFiles,
|
||||||
|
makeLabelForSimpleDockerfile,
|
||||||
|
makeLabelForStandaloneApplication,
|
||||||
|
saveBuildLog,
|
||||||
|
saveDockerRegistryCredentials,
|
||||||
|
setDefaultConfiguration
|
||||||
|
} from '../lib/buildPacks/common';
|
||||||
|
import {
|
||||||
|
createDirectories,
|
||||||
|
decrypt,
|
||||||
|
defaultComposeConfiguration,
|
||||||
|
getDomain,
|
||||||
|
prisma,
|
||||||
|
decryptApplication,
|
||||||
|
isDev,
|
||||||
|
pushToRegistry,
|
||||||
|
executeCommand,
|
||||||
|
generateSecrets
|
||||||
|
} 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,33 +32,63 @@ 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: { dockerRegistry: true, destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
let application = await prisma.application.findUnique({
|
||||||
|
where: { id: queueBuild.applicationId },
|
||||||
let { id: buildId, type, gitSourceId, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild
|
include: {
|
||||||
application = decryptApplication(application)
|
dockerRegistry: true,
|
||||||
|
destinationDocker: true,
|
||||||
|
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||||
|
persistentStorage: true,
|
||||||
|
secrets: true,
|
||||||
|
settings: true,
|
||||||
|
teams: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!application) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: queueBuild.id },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
throw new Error('Application not found');
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
id: buildId,
|
||||||
|
type,
|
||||||
|
gitSourceId,
|
||||||
|
sourceBranch = null,
|
||||||
|
pullmergeRequestId = null,
|
||||||
|
previewApplicationId = null,
|
||||||
|
forceRebuild,
|
||||||
|
sourceRepository = null
|
||||||
|
} = queueBuild;
|
||||||
|
application = decryptApplication(application);
|
||||||
|
|
||||||
if (!gitSourceId && application.simpleDockerfile) {
|
if (!gitSourceId && application.simpleDockerfile) {
|
||||||
const {
|
const {
|
||||||
@@ -53,102 +101,110 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
exposePort,
|
exposePort,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
dockerRegistry
|
dockerRegistry
|
||||||
} = application
|
} = application;
|
||||||
const { workdir } = await createDirectories({ repository: applicationId, buildId });
|
const { workdir } = await createDirectories({ repository: applicationId, buildId });
|
||||||
try {
|
try {
|
||||||
if (queueBuild.status === 'running') {
|
if (queueBuild.status === 'running') {
|
||||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
await saveBuildLog({
|
||||||
|
line: 'Building halted, restarting...',
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
if (storage.oldPath) {
|
if (storage.oldPath) {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-').replace('-app', '')}:${storage.path}`;
|
return `${applicationId}${storage.path
|
||||||
|
.replace(/\//gi, '-')
|
||||||
|
.replace('-app', '')}:${storage.path}`;
|
||||||
|
}
|
||||||
|
if (storage.hostPath) {
|
||||||
|
return `${storage.hostPath}:${storage.path}`;
|
||||||
}
|
}
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'running' }
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const { stdout: containers } = await executeCommand({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
|
||||||
})
|
});
|
||||||
if (containers) {
|
if (containers) {
|
||||||
const containerArray = containers.split('\n');
|
const containerArray = containers.split('\n');
|
||||||
if (containerArray.length > 0) {
|
if (containerArray.length > 0) {
|
||||||
for (const container of containerArray) {
|
for (const container of containerArray) {
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
await executeCommand({
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [
|
let envs = [];
|
||||||
`PORT=${port}`
|
|
||||||
];
|
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
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);
|
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry
|
const { url, username, password } = dockerRegistry;
|
||||||
await saveDockerRegistryCredentials({ url, username, password, workdir })
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
|
|
||||||
const labels = makeLabelForSimpleDockerfile({
|
const labels = makeLabelForSimpleDockerfile({
|
||||||
applicationId,
|
applicationId,
|
||||||
type,
|
type,
|
||||||
port: exposePort ? `${exposePort}:${port}` : port,
|
port: exposePort ? `${exposePort}:${port}` : port
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes
|
||||||
return {
|
.filter((v) => {
|
||||||
[`${volume.split(':')[0]}`]: {
|
if (
|
||||||
name: volume.split(':')[0]
|
!v.startsWith('.') &&
|
||||||
|
!v.startsWith('..') &&
|
||||||
|
!v.startsWith('/') &&
|
||||||
|
!v.startsWith('~')
|
||||||
|
) {
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
});
|
.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
const composeFile = {
|
const composeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
[applicationId]: {
|
[applicationId]: {
|
||||||
build: {
|
build: {
|
||||||
context: workdir,
|
context: workdir
|
||||||
},
|
},
|
||||||
image: `${applicationId}:${buildId}`,
|
image: `${applicationId}:${buildId}`,
|
||||||
container_name: applicationId,
|
container_name: applicationId,
|
||||||
volumes,
|
volumes,
|
||||||
labels,
|
labels,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
environment: envs,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
expose: [port],
|
expose: [port],
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
...defaultComposeConfiguration(destinationDocker.network),
|
...defaultComposeConfiguration(destinationDocker.network)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -159,11 +215,15 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
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 executeCommand({ debug: true, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
await executeCommand({
|
||||||
|
debug: true,
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||||
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
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 },
|
||||||
@@ -174,10 +234,9 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} 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 },
|
||||||
@@ -190,18 +249,26 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
|
await saveBuildLog({
|
||||||
|
line: error.message,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await fs.rm(workdir, { recursive: true, force: true });
|
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (application.dockerRegistryImageName) {
|
if (application.dockerRegistryImageName) {
|
||||||
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
|
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
|
||||||
const imageName = application.dockerRegistryImageName.split(':')[0];
|
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 saveBuildLog({
|
||||||
await pushToRegistry(application, workdir, buildId, imageName, customTag)
|
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||||
await saveBuildLog({ line: "Success", buildId, applicationId: application.id });
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
await pushToRegistry(application, workdir, buildId, imageName, customTag);
|
||||||
|
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.stdout) {
|
if (error.stdout) {
|
||||||
@@ -211,13 +278,16 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await fs.rm(workdir, { recursive: true, force: true });
|
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'success' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalApplicationId = application.id
|
const originalApplicationId = application.id;
|
||||||
const {
|
const {
|
||||||
id: applicationId,
|
id: applicationId,
|
||||||
name,
|
name,
|
||||||
@@ -241,7 +311,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
deploymentType,
|
deploymentType,
|
||||||
gitCommitHash,
|
gitCommitHash,
|
||||||
dockerRegistry
|
dockerRegistry
|
||||||
} = application
|
} = application;
|
||||||
|
|
||||||
let {
|
let {
|
||||||
branch,
|
branch,
|
||||||
@@ -257,7 +327,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
dockerComposeFileLocation,
|
dockerComposeFileLocation,
|
||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
denoMainFile
|
denoMainFile
|
||||||
} = application
|
} = application;
|
||||||
|
|
||||||
let imageId = applicationId;
|
let imageId = applicationId;
|
||||||
let domain = getDomain(fqdn);
|
let domain = getDomain(fqdn);
|
||||||
@@ -272,9 +342,11 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
let imageFoundRemotely = false;
|
let imageFoundRemotely = false;
|
||||||
|
|
||||||
if (pullmergeRequestId) {
|
if (pullmergeRequestId) {
|
||||||
const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } })
|
const previewApplications = await prisma.previewApplication.findMany({
|
||||||
|
where: { applicationId: originalApplicationId, pullmergeRequestId }
|
||||||
|
});
|
||||||
if (previewApplications.length > 0) {
|
if (previewApplications.length > 0) {
|
||||||
previewApplicationId = previewApplications[0].id
|
previewApplicationId = previewApplications[0].id;
|
||||||
}
|
}
|
||||||
// Previews, we need to get the source branch and set subdomain
|
// Previews, we need to get the source branch and set subdomain
|
||||||
branch = sourceBranch;
|
branch = sourceBranch;
|
||||||
@@ -285,7 +357,11 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||||
try {
|
try {
|
||||||
if (queueBuild.status === 'running') {
|
if (queueBuild.status === 'running') {
|
||||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
await saveBuildLog({
|
||||||
|
line: 'Building halted, restarting...',
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentHash = crypto
|
const currentHash = crypto
|
||||||
@@ -323,15 +399,19 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
if (storage.oldPath) {
|
if (storage.oldPath) {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-').replace('-app', '')}:${storage.path}`;
|
return `${applicationId}${storage.path
|
||||||
|
.replace(/\//gi, '-')
|
||||||
|
.replace('-app', '')}:${storage.path}`;
|
||||||
|
}
|
||||||
|
if (storage.hostPath) {
|
||||||
|
return `${storage.hostPath}:${storage.path}`;
|
||||||
}
|
}
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration)
|
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration);
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
let deployNeeded = true;
|
let deployNeeded = true;
|
||||||
let destinationType;
|
let destinationType;
|
||||||
|
|
||||||
@@ -339,7 +419,10 @@ 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({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'running' }
|
||||||
|
});
|
||||||
|
|
||||||
const configuration = await setDefaultConfiguration(application);
|
const configuration = await setDefaultConfiguration(application);
|
||||||
|
|
||||||
@@ -348,7 +431,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
installCommand = configuration.installCommand;
|
installCommand = configuration.installCommand;
|
||||||
startCommand = configuration.startCommand;
|
startCommand = configuration.startCommand;
|
||||||
buildCommand = configuration.buildCommand;
|
buildCommand = configuration.buildCommand;
|
||||||
publishDirectory = configuration.publishDirectory;
|
publishDirectory = configuration.publishDirectory || '';
|
||||||
baseDirectory = configuration.baseDirectory || '';
|
baseDirectory = configuration.baseDirectory || '';
|
||||||
dockerFileLocation = configuration.dockerFileLocation;
|
dockerFileLocation = configuration.dockerFileLocation;
|
||||||
dockerComposeFileLocation = configuration.dockerComposeFileLocation;
|
dockerComposeFileLocation = configuration.dockerComposeFileLocation;
|
||||||
@@ -361,6 +444,7 @@ 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,
|
||||||
|
customUser: gitSource.customUser,
|
||||||
gitCommitHash,
|
gitCommitHash,
|
||||||
configuration,
|
configuration,
|
||||||
repository,
|
repository,
|
||||||
@@ -381,10 +465,10 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||||
}
|
}
|
||||||
if (application.dockerRegistryImageName) {
|
if (application.dockerRegistryImageName) {
|
||||||
imageName = application.dockerRegistryImageName.split(':')[0]
|
imageName = application.dockerRegistryImageName.split(':')[0];
|
||||||
customTag = application.dockerRegistryImageName.split(':')[1] || tag
|
customTag = application.dockerRegistryImageName.split(':')[1] || tag;
|
||||||
} else {
|
} else {
|
||||||
customTag = tag
|
customTag = tag;
|
||||||
imageName = applicationId;
|
imageName = applicationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,13 +478,17 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
|
|
||||||
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;
|
||||||
@@ -413,30 +501,43 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker image inspect ${applicationId}:${tag}`
|
command: `docker image inspect ${applicationId}:${tag}`
|
||||||
})
|
});
|
||||||
imageFoundLocally = true;
|
imageFoundLocally = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry
|
const { url, username, password } = dockerRegistry;
|
||||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
location = await saveDockerRegistryCredentials({
|
||||||
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
workdir
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker ${location ? `--config ${location}` : ''} pull ${imageName}:${customTag}`
|
command: `docker ${
|
||||||
})
|
location ? `--config ${location}` : ''
|
||||||
|
} pull ${imageName}:${customTag}`
|
||||||
|
});
|
||||||
imageFoundRemotely = true;
|
imageFoundRemotely = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
let imageFound = `${applicationId}:${tag}`
|
let imageFound = `${applicationId}:${tag}`;
|
||||||
if (imageFoundRemotely) {
|
if (imageFoundRemotely) {
|
||||||
imageFound = `${imageName}:${customTag}`
|
imageFound = `${imageName}:${customTag}`;
|
||||||
}
|
}
|
||||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
await copyBaseConfigurationFiles(
|
||||||
|
buildPack,
|
||||||
|
workdir,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
baseImage
|
||||||
|
);
|
||||||
const labels = makeLabelForStandaloneApplication({
|
const labels = makeLabelForStandaloneApplication({
|
||||||
applicationId,
|
applicationId,
|
||||||
fqdn,
|
fqdn,
|
||||||
@@ -455,7 +556,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
baseDirectory,
|
baseDirectory,
|
||||||
publishDirectory
|
publishDirectory
|
||||||
});
|
});
|
||||||
if (forceRebuild) deployNeeded = true
|
if (forceRebuild) deployNeeded = true;
|
||||||
if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) {
|
if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) {
|
||||||
if (buildpacks[buildPack])
|
if (buildpacks[buildPack])
|
||||||
await buildpacks[buildPack]({
|
await buildpacks[buildPack]({
|
||||||
@@ -496,33 +597,53 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
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 {
|
||||||
if (imageFoundRemotely || deployNeeded) {
|
if (imageFoundRemotely || deployNeeded) {
|
||||||
await saveBuildLog({ line: `Container image ${imageFound} found in Docker Registry - reuising it`, buildId, applicationId });
|
await saveBuildLog({
|
||||||
|
line: `Container image ${imageFound} found in Docker Registry - reuising it`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (imageFoundLocally || deployNeeded) {
|
if (imageFoundLocally || deployNeeded) {
|
||||||
await saveBuildLog({ line: `Container image ${imageFound} found locally - reuising it`, buildId, applicationId });
|
await saveBuildLog({
|
||||||
|
line: `Container image ${imageFound} found locally - reuising it`,
|
||||||
|
buildId,
|
||||||
|
applicationId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildPack === 'compose') {
|
if (buildPack === 'compose') {
|
||||||
|
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||||
try {
|
try {
|
||||||
const { stdout: containers } = await executeCommand({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
|
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
|
||||||
})
|
});
|
||||||
if (containers) {
|
if (containers) {
|
||||||
const containerArray = containers.split('\n');
|
const containerArray = containers.split('\n');
|
||||||
if (containerArray.length > 0) {
|
if (containerArray.length > 0) {
|
||||||
for (const container of containerArray) {
|
for (const container of containerArray) {
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
await executeCommand({
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,17 +651,25 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log({ debug })
|
await executeCommand({
|
||||||
await executeCommand({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
debug,
|
||||||
|
buildId,
|
||||||
|
applicationId,
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d`
|
||||||
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
await prisma.build.update({
|
||||||
|
where: { id: buildId },
|
||||||
|
data: { status: 'success' }
|
||||||
|
});
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id: applicationId },
|
where: { id: applicationId },
|
||||||
data: { configHash: currentHash }
|
data: { configHash: currentHash }
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
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 },
|
||||||
@@ -551,64 +680,62 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const { stdout: containers } = await executeCommand({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}`
|
command: `docker ps -a --filter 'label=com.docker.compose.service=${
|
||||||
})
|
pullmergeRequestId ? imageId : applicationId
|
||||||
|
}' --format {{.ID}}`
|
||||||
|
});
|
||||||
if (containers) {
|
if (containers) {
|
||||||
const containerArray = containers.split('\n');
|
const containerArray = containers.split('\n');
|
||||||
if (containerArray.length > 0) {
|
if (containerArray.length > 0) {
|
||||||
for (const container of containerArray) {
|
for (const container of containerArray) {
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
await executeCommand({
|
||||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [
|
let envs = [];
|
||||||
`PORT=${port}`
|
|
||||||
];
|
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
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'));
|
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry
|
const { url, username, password } = dockerRegistry;
|
||||||
await saveDockerRegistryCredentials({ url, username, password, workdir })
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
|
||||||
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes
|
||||||
return {
|
.filter((v) => {
|
||||||
[`${volume.split(':')[0]}`]: {
|
if (
|
||||||
name: volume.split(':')[0]
|
!v.startsWith('.') &&
|
||||||
|
!v.startsWith('..') &&
|
||||||
|
!v.startsWith('/') &&
|
||||||
|
!v.startsWith('~')
|
||||||
|
) {
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
});
|
.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
const composeFile = {
|
const composeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -616,12 +743,12 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
image: imageFound,
|
image: imageFound,
|
||||||
container_name: imageId,
|
container_name: imageId,
|
||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
environment: envs,
|
||||||
labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
expose: [port],
|
expose: [port],
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
...defaultComposeConfiguration(destinationDocker.network),
|
...defaultComposeConfiguration(destinationDocker.network)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
networks: {
|
networks: {
|
||||||
@@ -632,11 +759,15 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
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 executeCommand({ debug, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
await executeCommand({
|
||||||
|
debug,
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||||
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await saveBuildLog({ line: error, buildId, applicationId });
|
await saveBuildLog({ line: error, buildId, applicationId });
|
||||||
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 },
|
||||||
@@ -648,14 +779,15 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pullmergeRequestId) await prisma.application.update({
|
if (!pullmergeRequestId)
|
||||||
where: { id: applicationId },
|
await prisma.application.update({
|
||||||
data: { configHash: currentHash }
|
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 },
|
||||||
@@ -668,16 +800,24 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
|
await saveBuildLog({
|
||||||
|
line: error.message,
|
||||||
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await fs.rm(workdir, { recursive: true, force: true });
|
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (application.dockerRegistryImageName && (!imageFoundRemotely || forceRebuild)) {
|
if (application.dockerRegistryImageName && (!imageFoundRemotely || forceRebuild)) {
|
||||||
await saveBuildLog({ line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`, buildId, applicationId: application.id });
|
await saveBuildLog({
|
||||||
await pushToRegistry(application, workdir, tag, imageName, customTag)
|
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||||
await saveBuildLog({ line: "Success", buildId, applicationId: application.id });
|
buildId,
|
||||||
|
applicationId: application.id
|
||||||
|
});
|
||||||
|
await pushToRegistry(application, workdir, tag, imageName, customTag);
|
||||||
|
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.stdout) {
|
if (error.stdout) {
|
||||||
@@ -686,21 +826,20 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
if (error.stderr) {
|
if (error.stderr) {
|
||||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
await fs.rm(workdir, { recursive: true, force: true });
|
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
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);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -86,19 +86,20 @@ export async function migrateServicesToNewTemplate() {
|
|||||||
if (template.variables) {
|
if (template.variables) {
|
||||||
if (template.variables.length > 0) {
|
if (template.variables.length > 0) {
|
||||||
for (const variable of template.variables) {
|
for (const variable of template.variables) {
|
||||||
const { defaultValue } = variable;
|
let { defaultValue } = variable;
|
||||||
|
defaultValue = defaultValue.toString();
|
||||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||||
if (variable.defaultValue.startsWith('$$generate_password')) {
|
if (defaultValue.startsWith('$$generate_password')) {
|
||||||
variable.value = generatePassword({ length });
|
variable.value = generatePassword({ length });
|
||||||
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
} else if (defaultValue.startsWith('$$generate_hex')) {
|
||||||
variable.value = generatePassword({ length, isHex: true });
|
variable.value = generatePassword({ length, isHex: true });
|
||||||
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
} else if (defaultValue.startsWith('$$generate_username')) {
|
||||||
variable.value = cuid();
|
variable.value = cuid();
|
||||||
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
} else if (defaultValue.startsWith('$$generate_token')) {
|
||||||
variable.value = generateToken()
|
variable.value = generateToken()
|
||||||
} else {
|
} else {
|
||||||
variable.value = variable.defaultValue || '';
|
variable.value = defaultValue || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { base64Encode, decrypt, encrypt, executeCommand, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common";
|
import {
|
||||||
|
base64Encode,
|
||||||
|
decrypt,
|
||||||
|
encrypt,
|
||||||
|
executeCommand,
|
||||||
|
generateSecrets,
|
||||||
|
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 +29,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',
|
||||||
@@ -316,8 +331,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,
|
||||||
@@ -326,8 +341,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 = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||||
payload.baseImages = isARM(process.arch) ? staticVersions.filter((version) => !version.value.includes('webdevops')) : staticVersions;
|
payload.baseImages = isARM()
|
||||||
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
} else {
|
} else {
|
||||||
@@ -338,8 +355,10 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (staticApps.includes(buildPack)) {
|
if (staticApps.includes(buildPack)) {
|
||||||
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||||
payload.baseImages = isARM(process.arch) ? staticVersions.filter((version) => !version.value.includes('webdevops')) : staticVersions;
|
payload.baseImages = isARM()
|
||||||
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
@@ -357,12 +376,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 = isARM(process.arch) ? 'php:8.1-fpm-alpine' : 'webdevops/php-apache:8.2-alpine';
|
payload.baseImage = isARM()
|
||||||
payload.baseImages = isARM(process.arch) ? phpVersions.filter((version) => !version.value.includes('webdevops')) : phpVersions
|
? 'php:8.1-fpm-alpine'
|
||||||
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
|
payload.baseImages = isARM()
|
||||||
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: phpVersions;
|
||||||
}
|
}
|
||||||
if (buildPack === 'laravel') {
|
if (buildPack === 'laravel') {
|
||||||
payload.baseImage = isARM(process.arch) ? 'php:8.1-fpm-alpine' : 'webdevops/php-apache:8.2-alpine';
|
payload.baseImage = isARM()
|
||||||
payload.baseImages = isARM(process.arch) ? phpVersions.filter((version) => !version.value.includes('webdevops')) : phpVersions
|
? 'php:8.1-fpm-alpine'
|
||||||
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
|
payload.baseImages = isARM()
|
||||||
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
|
: phpVersions;
|
||||||
payload.baseBuildImage = 'node:18';
|
payload.baseBuildImage = 'node:18';
|
||||||
payload.baseBuildImages = nodeVersions;
|
payload.baseBuildImages = nodeVersions;
|
||||||
}
|
}
|
||||||
@@ -402,10 +429,16 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
startCommand = template?.startCommand || 'yarn start';
|
startCommand = template?.startCommand || 'yarn start';
|
||||||
if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel')
|
if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel')
|
||||||
buildCommand = template?.buildCommand || null;
|
buildCommand = template?.buildCommand || null;
|
||||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
if (!publishDirectory) {
|
||||||
|
publishDirectory = template?.publishDirectory || null;
|
||||||
|
} else {
|
||||||
|
if (!publishDirectory.startsWith('/')) publishDirectory = `/${publishDirectory}`;
|
||||||
|
if (publishDirectory.endsWith('/')) publishDirectory = publishDirectory.slice(0, -1);
|
||||||
|
}
|
||||||
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}`;
|
||||||
@@ -414,8 +447,10 @@ export const setDefaultConfiguration = async (data: any) => {
|
|||||||
dockerFileLocation = '/Dockerfile';
|
dockerFileLocation = '/Dockerfile';
|
||||||
}
|
}
|
||||||
if (dockerComposeFileLocation) {
|
if (dockerComposeFileLocation) {
|
||||||
if (!dockerComposeFileLocation.startsWith('/')) dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
if (!dockerComposeFileLocation.startsWith('/'))
|
||||||
if (dockerComposeFileLocation.endsWith('/')) dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||||
|
if (dockerComposeFileLocation.endsWith('/'))
|
||||||
|
dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||||
} else {
|
} else {
|
||||||
dockerComposeFileLocation = '/Dockerfile';
|
dockerComposeFileLocation = '/Dockerfile';
|
||||||
}
|
}
|
||||||
@@ -479,7 +514,6 @@ export const scanningTemplates = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const saveBuildLog = async ({
|
export const saveBuildLog = async ({
|
||||||
line,
|
line,
|
||||||
buildId,
|
buildId,
|
||||||
@@ -491,7 +525,7 @@ export const saveBuildLog = async ({
|
|||||||
}): Promise<any> => {
|
}): Promise<any> => {
|
||||||
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
||||||
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
if (typeof line === 'object' && line) {
|
if (typeof line === 'object' && line) {
|
||||||
if (line.shortMessage) {
|
if (line.shortMessage) {
|
||||||
line = line.shortMessage + '\n' + line.stderr;
|
line = line.shortMessage + '\n' + line.stderr;
|
||||||
@@ -504,7 +538,11 @@ export const saveBuildLog = async ({
|
|||||||
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 ? process.env.COOLIFY_CONTAINER_DEV === 'true' ? 'http://coolify-fluentbit:24224' : '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 && !process.env.COOLIFY_CONTAINER_DEV) {
|
if (isDev && !process.env.COOLIFY_CONTAINER_DEV) {
|
||||||
console.debug(`[${applicationId}] ${addTimestamp}`);
|
console.debug(`[${applicationId}] ${addTimestamp}`);
|
||||||
@@ -514,15 +552,17 @@ export const saveBuildLog = async ({
|
|||||||
json: {
|
json: {
|
||||||
line: encrypt(line)
|
line: encrypt(line)
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
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(
|
||||||
@@ -610,7 +650,7 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
|
|||||||
|
|
||||||
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let decryptedPassword = decrypt(password);
|
let decryptedPassword = decrypt(password);
|
||||||
@@ -619,17 +659,17 @@ export async function saveDockerRegistryCredentials({ url, username, password, w
|
|||||||
try {
|
try {
|
||||||
await fs.mkdir(`${workdir}/.docker`);
|
await fs.mkdir(`${workdir}/.docker`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
}
|
}
|
||||||
const payload = JSON.stringify({
|
const payload = JSON.stringify({
|
||||||
"auths": {
|
auths: {
|
||||||
[url]: {
|
[url]: {
|
||||||
"auth": Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
await fs.writeFile(`${location}/config.json`, payload)
|
await fs.writeFile(`${location}/config.json`, payload);
|
||||||
return location
|
return location;
|
||||||
}
|
}
|
||||||
export async function buildImage({
|
export async function buildImage({
|
||||||
applicationId,
|
applicationId,
|
||||||
@@ -640,29 +680,40 @@ export async function buildImage({
|
|||||||
isCache = false,
|
isCache = false,
|
||||||
debug = false,
|
debug = false,
|
||||||
dockerFileLocation = '/Dockerfile',
|
dockerFileLocation = '/Dockerfile',
|
||||||
commit
|
commit,
|
||||||
|
forceRebuild = false
|
||||||
}) {
|
}) {
|
||||||
if (isCache) {
|
if (isCache) {
|
||||||
await saveBuildLog({ line: `Building cache image...`, buildId, applicationId });
|
await saveBuildLog({ line: `Building cache image...`, buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||||
}
|
}
|
||||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`;
|
||||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`;
|
||||||
|
let location = null;
|
||||||
|
|
||||||
let location = null
|
const { dockerRegistry } = await prisma.application.findUnique({
|
||||||
|
where: { id: applicationId },
|
||||||
const { dockerRegistry } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
|
select: { dockerRegistry: true }
|
||||||
|
});
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry
|
const { url, username, password } = dockerRegistry;
|
||||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
location = await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
|
|
||||||
await executeCommand({ stream: true, debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
await executeCommand({
|
||||||
|
stream: true,
|
||||||
|
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 } })
|
const { status } = await prisma.build.findUnique({ where: { id: buildId } });
|
||||||
if (status === 'canceled') {
|
if (status === 'canceled') {
|
||||||
throw new Error('Canceled.')
|
throw new Error('Canceled.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
|
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
|
||||||
@@ -741,21 +792,8 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
@@ -765,50 +803,33 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
if (installCommand) {
|
if (installCommand) {
|
||||||
Dockerfile.push(`RUN ${installCommand}`);
|
Dockerfile.push(`RUN ${installCommand}`);
|
||||||
}
|
}
|
||||||
// Dockerfile.push(`ARG CACHEBUST=1`);
|
|
||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||||
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
||||||
|
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
||||||
Dockerfile.push(`COPY resources /app/resources`);
|
Dockerfile.push(`COPY resources /app/resources`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`RUN yarn install && yarn production`);
|
Dockerfile.push(`RUN yarn install && yarn production`);
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
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}`);
|
||||||
@@ -823,6 +844,7 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
|||||||
Dockerfile.push('RUN cargo install cargo-chef');
|
Dockerfile.push('RUN cargo install cargo-chef');
|
||||||
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
|
||||||
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { defaultComposeConfiguration, executeCommand } from '../common';
|
import { defaultComposeConfiguration, executeCommand, generateSecrets } from '../common';
|
||||||
import { saveBuildLog } from './common';
|
import { saveBuildLog } from './common';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
@@ -19,65 +19,128 @@ export default async function (data) {
|
|||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
dockerComposeFileLocation
|
dockerComposeFileLocation
|
||||||
} = data;
|
} = data;
|
||||||
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
const baseDir = `${workdir}${baseDirectory}`;
|
||||||
|
const envFile = `${baseDir}/.env`;
|
||||||
|
const fileYaml = `${baseDir}${dockerComposeFileLocation}`;
|
||||||
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
|
||||||
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
const dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||||
if (!dockerComposeYaml.services) {
|
if (!dockerComposeYaml.services) {
|
||||||
throw 'No Services found in docker-compose file.';
|
throw 'No Services found in docker-compose file.';
|
||||||
}
|
}
|
||||||
const envs = [];
|
let envs = [];
|
||||||
|
let buildEnvs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||||
if (pullmergeRequestId) {
|
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||||
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(envFile, envs.join('\n'));
|
||||||
const composeVolumes = [];
|
const composeVolumes = [];
|
||||||
if (volumes.length > 0) {
|
if (volumes.length > 0) {
|
||||||
for (const volume of volumes) {
|
for (const volume of volumes) {
|
||||||
let [v, path] = volume.split(':');
|
let [v, path] = volume.split(':');
|
||||||
composeVolumes[v] = {
|
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||||
name: v
|
composeVolumes[v] = {
|
||||||
};
|
name: v
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let networks = {};
|
let networks = {};
|
||||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||||
value['container_name'] = `${applicationId}-${key}`;
|
value['container_name'] = `${applicationId}-${key}`;
|
||||||
value['env_file'] = envFound ? [`${workdir}/.env`] : [];
|
|
||||||
|
if (value['env_file']) {
|
||||||
|
delete value['env_file'];
|
||||||
|
}
|
||||||
|
value['env_file'] = [envFile];
|
||||||
|
|
||||||
|
// let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||||
|
// let finalEnvs = [...envs];
|
||||||
|
// if (Object.keys(environment).length > 0) {
|
||||||
|
// for (const arg of Object.keys(environment)) {
|
||||||
|
// const [key, _] = arg.split('=');
|
||||||
|
// if (finalEnvs.filter((env) => env.startsWith(key)).length === 0) {
|
||||||
|
// finalEnvs.push(arg);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// value['environment'] = [...finalEnvs];
|
||||||
|
|
||||||
|
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||||
|
if (typeof build === 'string') {
|
||||||
|
build = { context: build };
|
||||||
|
}
|
||||||
|
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||||
|
let finalBuildArgs = [...buildEnvs];
|
||||||
|
if (Object.keys(buildArgs).length > 0) {
|
||||||
|
for (const arg of Object.keys(buildArgs)) {
|
||||||
|
const [key, _] = arg.split('=');
|
||||||
|
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||||
|
finalBuildArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (build.length > 0 || buildArgs.length > 0) {
|
||||||
|
value['build'] = {
|
||||||
|
...build,
|
||||||
|
args: finalBuildArgs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
value['labels'] = labels;
|
value['labels'] = labels;
|
||||||
// TODO: If we support separated volume for each service, we need to add it here
|
// TODO: If we support separated volume for each service, we need to add it here
|
||||||
if (value['volumes']?.length > 0) {
|
if (value['volumes']?.length > 0) {
|
||||||
value['volumes'] = value['volumes'].map((volume) => {
|
value['volumes'] = value['volumes'].map((volume) => {
|
||||||
let [v, path, permission] = volume.split(':');
|
if (typeof volume === 'string') {
|
||||||
if (!path) {
|
let [v, path, permission] = volume.split(':');
|
||||||
path = v;
|
if (
|
||||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
v.startsWith('.') ||
|
||||||
} else {
|
v.startsWith('..') ||
|
||||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
v.startsWith('/') ||
|
||||||
|
v.startsWith('~') ||
|
||||||
|
v.startsWith('$PWD')
|
||||||
|
) {
|
||||||
|
v = v
|
||||||
|
.replace(/^\./, `~`)
|
||||||
|
.replace(/^\.\./, '~')
|
||||||
|
.replace(/^\$PWD/, '~');
|
||||||
|
} else {
|
||||||
|
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 (typeof volume === 'object') {
|
||||||
|
let { source, target, mode } = volume;
|
||||||
|
if (
|
||||||
|
source.startsWith('.') ||
|
||||||
|
source.startsWith('..') ||
|
||||||
|
source.startsWith('/') ||
|
||||||
|
source.startsWith('~') ||
|
||||||
|
source.startsWith('$PWD')
|
||||||
|
) {
|
||||||
|
source = source
|
||||||
|
.replace(/^\./, `~`)
|
||||||
|
.replace(/^\.\./, '~')
|
||||||
|
.replace(/^\$PWD/, '~');
|
||||||
|
} else {
|
||||||
|
if (!target) {
|
||||||
|
target = source;
|
||||||
|
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||||
|
} else {
|
||||||
|
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${source}:${target}${mode ? ':' + mode : ''}`;
|
||||||
}
|
}
|
||||||
composeVolumes[v] = {
|
|
||||||
name: v
|
|
||||||
};
|
|
||||||
return `${v}:${path}${permission ? ':' + permission : ''}`;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (volumes.length > 0) {
|
if (volumes.length > 0) {
|
||||||
@@ -85,17 +148,24 @@ export default async function (data) {
|
|||||||
value['volumes'].push(volume);
|
value['volumes'].push(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dockerComposeConfiguration[key].port) {
|
if (dockerComposeConfiguration[key]?.port) {
|
||||||
value['expose'] = [dockerComposeConfiguration[key].port];
|
value['expose'] = [dockerComposeConfiguration[key].port];
|
||||||
}
|
}
|
||||||
if (value['networks']?.length > 0) {
|
value['networks'] = [network];
|
||||||
value['networks'].forEach((network) => {
|
if (value['build']?.network) {
|
||||||
networks[network] = {
|
delete value['build']['network'];
|
||||||
name: network
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
value['networks'] = [...(value['networks'] || ''), network];
|
// if (value['networks']?.length > 0) {
|
||||||
|
// value['networks'].forEach((network) => {
|
||||||
|
// networks[network] = {
|
||||||
|
// name: network
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// value['networks'] = [...(value['networks'] || ''), network];
|
||||||
|
// } else {
|
||||||
|
// value['networks'] = [network];
|
||||||
|
// }
|
||||||
|
|
||||||
dockerComposeYaml.services[key] = {
|
dockerComposeYaml.services[key] = {
|
||||||
...dockerComposeYaml.services[key],
|
...dockerComposeYaml.services[key],
|
||||||
restart: defaultComposeConfiguration(network).restart,
|
restart: defaultComposeConfiguration(network).restart,
|
||||||
@@ -106,13 +176,14 @@ export default async function (data) {
|
|||||||
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
||||||
}
|
}
|
||||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||||
|
|
||||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug,
|
debug,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker compose --project-directory ${workdir} pull`
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
@@ -120,7 +191,7 @@ export default async function (data) {
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker compose --project-directory ${workdir} build --progress plain`
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (depsFound) {
|
if (depsFound) {
|
||||||
@@ -48,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
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('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
|
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
|
|||||||
@@ -1,47 +1,27 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
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()
|
|
||||||
.trim()
|
|
||||||
.split('\n');
|
|
||||||
Dockerfile.forEach((line, index) => {
|
Dockerfile.forEach((line, index) => {
|
||||||
if (line.startsWith('FROM')) {
|
if (line.startsWith('FROM')) {
|
||||||
Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`);
|
Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.forEach((line, index) => {
|
||||||
if (
|
if (line.startsWith('FROM')) {
|
||||||
(pullmergeRequestId && secret.isPRMRSecret) ||
|
Dockerfile.splice(index + 1, 0, env);
|
||||||
(!pullmergeRequestId && !secret.isPRMRSecret)
|
|
||||||
) {
|
|
||||||
Dockerfile.forEach((line, index) => {
|
|
||||||
if (line.startsWith('FROM')) {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${imageforBuild}`);
|
Dockerfile.push(`FROM ${imageforBuild}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageForLaravel, buildImage } from './common';
|
import { buildCacheImageForLaravel, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, applicationId, tag, buildId, port } = data;
|
const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
|
Dockerfile.push(env);
|
||||||
|
});
|
||||||
|
}
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
||||||
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
||||||
@@ -24,6 +30,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
|
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
|
||||||
);
|
);
|
||||||
Dockerfile.push(`COPY --chown=application:application . ./`);
|
Dockerfile.push(`COPY --chown=application:application . ./`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
|||||||
import { buildCacheImageWithNode, buildImage } from './common';
|
import { buildCacheImageWithNode, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
|
const { buildId, applicationId, tag, port, startCommand, workdir, publishDirectory } = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
const isPnpm = startCommand.includes('pnpm');
|
const isPnpm = startCommand.includes('pnpm');
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD ${startCommand}`);
|
Dockerfile.push(`CMD ${startCommand}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
@@ -48,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
Dockerfile.push(`RUN ${installCommand}`);
|
Dockerfile.push(`RUN ${installCommand}`);
|
||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD ${startCommand}`);
|
Dockerfile.push(`CMD ${startCommand}`);
|
||||||
} else if (deploymentType === 'static') {
|
} else if (deploymentType === 'static') {
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE 80`);
|
Dockerfile.push(`EXPOSE 80`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage, checkPnpm } from './common';
|
import { buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -20,21 +21,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
@@ -46,6 +34,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
}
|
}
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`CMD ${startCommand}`);
|
Dockerfile.push(`CMD ${startCommand}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
@@ -48,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
Dockerfile.push(`RUN ${installCommand}`);
|
Dockerfile.push(`RUN ${installCommand}`);
|
||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD ${startCommand}`);
|
Dockerfile.push(`CMD ${startCommand}`);
|
||||||
} else if (deploymentType === 'static') {
|
} else if (deploymentType === 'static') {
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE 80`);
|
Dockerfile.push(`EXPOSE 80`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||||
@@ -13,21 +14,8 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
@@ -40,6 +28,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -18,21 +19,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
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) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||||
@@ -64,7 +52,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`CMD python ${pythonModule}`);
|
Dockerfile.push(`CMD python ${pythonModule}`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
|
|||||||
);
|
);
|
||||||
Dockerfile.push(`RUN update-ca-certificates`);
|
Dockerfile.push(`RUN update-ca-certificates`);
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
Dockerfile.push(`CMD ["/app/${name}"]`);
|
Dockerfile.push(`CMD ["/app/${name}"]`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage } from './common';
|
import { buildCacheImageWithNode, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -25,31 +26,19 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
}
|
}
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (buildCommand) {
|
if (buildCommand) {
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
} else {
|
} else {
|
||||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||||
}
|
}
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||||
if (baseImage?.includes('nginx')) {
|
if (baseImage?.includes('nginx')) {
|
||||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||||
}
|
}
|
||||||
|
Dockerfile.push('RUN rm -fr .git');
|
||||||
Dockerfile.push(`EXPOSE ${port}`);
|
Dockerfile.push(`EXPOSE ${port}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { exec } from 'node:child_process';
|
|
||||||
import util from 'util';
|
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import fsNormal from 'fs';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import forge from 'node-forge';
|
import forge from 'node-forge';
|
||||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||||
@@ -8,21 +7,22 @@ import type { Config } from 'unique-names-generator';
|
|||||||
import generator from 'generate-password';
|
import generator from 'generate-password';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { promises as dns } from 'dns';
|
import { promises as dns } from 'dns';
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import sshConfig from 'ssh-config';
|
import * as SSHConfig from 'ssh-config/src/ssh-config';
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
import { checkContainer, removeContainer } from './docker';
|
import { checkContainer, removeContainer } from './docker';
|
||||||
import { day } from './dayjs';
|
import { day } from './dayjs';
|
||||||
import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common';
|
import { saveBuildLog } from './buildPacks/common';
|
||||||
import { scheduler } from './scheduler';
|
import { scheduler } from './scheduler';
|
||||||
import type { ExecaChildProcess } from 'execa';
|
import type { ExecaChildProcess } from 'execa';
|
||||||
|
import { FastifyReply } from 'fastify';
|
||||||
|
|
||||||
export const version = '3.12.1';
|
export const version = '3.12.39';
|
||||||
export const isDev = process.env.NODE_ENV === 'development';
|
export const isDev = process.env.NODE_ENV === 'development';
|
||||||
export const sentryDSN =
|
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
||||||
|
|
||||||
const algorithm = 'aes-256-ctr';
|
const algorithm = 'aes-256-ctr';
|
||||||
const customConfig: Config = {
|
const customConfig: Config = {
|
||||||
dictionaries: [adjectives, colors, animals],
|
dictionaries: [adjectives, colors, animals],
|
||||||
@@ -170,13 +170,19 @@ export const base64Encode = (text: string): string => {
|
|||||||
export const base64Decode = (text: string): string => {
|
export const base64Decode = (text: string): string => {
|
||||||
return Buffer.from(text, 'base64').toString('ascii');
|
return Buffer.from(text, 'base64').toString('ascii');
|
||||||
};
|
};
|
||||||
|
export const getSecretKey = () => {
|
||||||
|
if (process.env['COOLIFY_SECRET_KEY_BETTER']) {
|
||||||
|
return process.env['COOLIFY_SECRET_KEY_BETTER'];
|
||||||
|
}
|
||||||
|
return process.env['COOLIFY_SECRET_KEY'];
|
||||||
|
};
|
||||||
export const decrypt = (hashString: string) => {
|
export const decrypt = (hashString: string) => {
|
||||||
if (hashString) {
|
if (hashString) {
|
||||||
try {
|
try {
|
||||||
const hash = JSON.parse(hashString);
|
const hash = JSON.parse(hashString);
|
||||||
const decipher = crypto.createDecipheriv(
|
const decipher = crypto.createDecipheriv(
|
||||||
algorithm,
|
algorithm,
|
||||||
process.env['COOLIFY_SECRET_KEY'],
|
getSecretKey(),
|
||||||
Buffer.from(hash.iv, 'hex')
|
Buffer.from(hash.iv, 'hex')
|
||||||
);
|
);
|
||||||
const decrpyted = Buffer.concat([
|
const decrpyted = Buffer.concat([
|
||||||
@@ -193,7 +199,7 @@ export const decrypt = (hashString: string) => {
|
|||||||
export const encrypt = (text: string) => {
|
export const encrypt = (text: string) => {
|
||||||
if (text) {
|
if (text) {
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
const cipher = crypto.createCipheriv(algorithm, getSecretKey(), iv);
|
||||||
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
iv: iv.toString('hex'),
|
iv: iv.toString('hex'),
|
||||||
@@ -400,8 +406,8 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
fancyName: 'MongoDB',
|
fancyName: 'MongoDB',
|
||||||
baseImage: 'bitnami/mongodb',
|
baseImage: 'bitnami/mongodb',
|
||||||
baseImageARM: 'mongo',
|
baseImageARM: 'mongo',
|
||||||
versions: ['5.0', '4.4', '4.2'],
|
versions: ['6.0', '5.0', '4.4', '4.2'],
|
||||||
versionsARM: ['5.0', '4.4', '4.2']
|
versionsARM: ['6.0', '5.0', '4.4', '4.2']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mysql',
|
name: 'mysql',
|
||||||
@@ -416,16 +422,16 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
fancyName: 'MariaDB',
|
fancyName: 'MariaDB',
|
||||||
baseImage: 'bitnami/mariadb',
|
baseImage: 'bitnami/mariadb',
|
||||||
baseImageARM: 'mariadb',
|
baseImageARM: 'mariadb',
|
||||||
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
|
versions: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
|
||||||
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
versionsARM: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'postgresql',
|
name: 'postgresql',
|
||||||
fancyName: 'PostgreSQL',
|
fancyName: 'PostgreSQL',
|
||||||
baseImage: 'bitnami/postgresql',
|
baseImage: 'bitnami/postgresql',
|
||||||
baseImageARM: 'postgres',
|
baseImageARM: 'postgres',
|
||||||
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
|
versions: ['15.2.0', '14.7.0', '14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
|
||||||
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22']
|
versionsARM: ['15.2', '14.7', '14.5', '13.8', '12.12', '11.17', '10.22']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'redis',
|
name: 'redis',
|
||||||
@@ -440,14 +446,14 @@ export const supportedDatabaseTypesAndVersions = [
|
|||||||
fancyName: 'CouchDB',
|
fancyName: 'CouchDB',
|
||||||
baseImage: 'bitnami/couchdb',
|
baseImage: 'bitnami/couchdb',
|
||||||
baseImageARM: 'couchdb',
|
baseImageARM: 'couchdb',
|
||||||
versions: ['3.2.2', '3.1.2', '2.3.1'],
|
versions: ['3.3.1', '3.2.2', '3.1.2', '2.3.1'],
|
||||||
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
|
versionsARM: ['3.3', '3.2.2', '3.1.2', '2.3.1']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'edgedb',
|
name: 'edgedb',
|
||||||
fancyName: 'EdgeDB',
|
fancyName: 'EdgeDB',
|
||||||
baseImage: 'edgedb/edgedb',
|
baseImage: 'edgedb/edgedb',
|
||||||
versions: ['latest', '2.1', '2.0', '1.4']
|
versions: ['latest', '2.9', '2.8', '2.7']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -496,33 +502,56 @@ export async function getFreeSSHLocalPort(id: string): Promise<number | boolean>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the ssh config file with a host
|
||||||
|
*
|
||||||
|
* @param id Destination ID
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export async function createRemoteEngineConfiguration(id: string) {
|
export async function createRemoteEngineConfiguration(id: string) {
|
||||||
const homedir = os.homedir();
|
|
||||||
const sshKeyFile = `/tmp/id_rsa-${id}`;
|
const sshKeyFile = `/tmp/id_rsa-${id}`;
|
||||||
const localPort = await getFreeSSHLocalPort(id);
|
const localPort = await getFreeSSHLocalPort(id);
|
||||||
const {
|
const {
|
||||||
sshKey: { privateKey },
|
sshKey: { privateKey },
|
||||||
network,
|
|
||||||
remoteIpAddress,
|
remoteIpAddress,
|
||||||
remotePort,
|
remotePort,
|
||||||
remoteUser
|
remoteUser
|
||||||
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
||||||
|
|
||||||
|
// Write new keyfile
|
||||||
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
||||||
const config = sshConfig.parse('');
|
|
||||||
const Host = `${remoteIpAddress}-remote`;
|
const Host = `${remoteIpAddress}-remote`;
|
||||||
|
|
||||||
|
// Removes previous ssh-keys
|
||||||
try {
|
try {
|
||||||
await executeCommand({ command: `ssh-keygen -R ${Host}` });
|
await executeCommand({ command: `ssh-keygen -R ${Host}` });
|
||||||
await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` });
|
await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` });
|
||||||
await executeCommand({ command: `ssh-keygen -R localhost:${localPort}` });
|
await executeCommand({ command: `ssh-keygen -R localhost:${localPort}` });
|
||||||
} catch (error) {}
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const homedir = os.homedir();
|
||||||
|
let currentConfigFileContent = '';
|
||||||
|
try {
|
||||||
|
// Read the current config file
|
||||||
|
currentConfigFileContent = (await fs.readFile(`${homedir}/.ssh/config`)).toString();
|
||||||
|
} catch (error) {
|
||||||
|
// File doesn't exist, so we do nothing, a new one is going to be created
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the config file
|
||||||
|
const config = SSHConfig.parse(currentConfigFileContent);
|
||||||
|
|
||||||
|
// Remove current config for the given host
|
||||||
const found = config.find({ Host });
|
const found = config.find({ Host });
|
||||||
const foundIp = config.find({ Host: remoteIpAddress });
|
const foundIp = config.find({ Host: remoteIpAddress });
|
||||||
|
|
||||||
if (found) config.remove({ Host });
|
if (found) config.remove({ Host });
|
||||||
if (foundIp) config.remove({ Host: remoteIpAddress });
|
if (foundIp) config.remove({ Host: remoteIpAddress });
|
||||||
|
|
||||||
|
// Create the new config
|
||||||
config.append({
|
config.append({
|
||||||
Host,
|
Host,
|
||||||
Hostname: remoteIpAddress,
|
Hostname: remoteIpAddress,
|
||||||
@@ -535,13 +564,17 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
ControlPersist: '10m'
|
ControlPersist: '10m'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if .ssh folder exists, and if not create one
|
||||||
try {
|
try {
|
||||||
await fs.stat(`${homedir}/.ssh/`);
|
await fs.stat(`${homedir}/.ssh/`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await fs.mkdir(`${homedir}/.ssh/`);
|
await fs.mkdir(`${homedir}/.ssh/`);
|
||||||
}
|
}
|
||||||
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config));
|
|
||||||
|
// Write the config
|
||||||
|
return await fs.writeFile(`${homedir}/.ssh/config`, SSHConfig.stringify(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function executeCommand({
|
export async function executeCommand({
|
||||||
command,
|
command,
|
||||||
dockerId = null,
|
dockerId = null,
|
||||||
@@ -550,7 +583,8 @@ export async function executeCommand({
|
|||||||
stream = false,
|
stream = false,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
debug
|
debug,
|
||||||
|
timeout = 0
|
||||||
}: {
|
}: {
|
||||||
command: string;
|
command: string;
|
||||||
sshCommand?: boolean;
|
sshCommand?: boolean;
|
||||||
@@ -560,6 +594,7 @@ export async function executeCommand({
|
|||||||
buildId?: string;
|
buildId?: string;
|
||||||
applicationId?: string;
|
applicationId?: string;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
timeout?: number;
|
||||||
}): Promise<ExecaChildProcess<string>> {
|
}): Promise<ExecaChildProcess<string>> {
|
||||||
const { execa, execaCommand } = await import('execa');
|
const { execa, execaCommand } = await import('execa');
|
||||||
const { parse } = await import('shell-quote');
|
const { parse } = await import('shell-quote');
|
||||||
@@ -584,20 +619,26 @@ export async function executeCommand({
|
|||||||
}
|
}
|
||||||
if (sshCommand) {
|
if (sshCommand) {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`);
|
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs]);
|
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs], {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (stream) {
|
if (stream) {
|
||||||
return await new Promise(async (resolve, reject) => {
|
return await new Promise(async (resolve, reject) => {
|
||||||
let subprocess = null;
|
let subprocess = null;
|
||||||
if (shell) {
|
if (shell) {
|
||||||
subprocess = execaCommand(command, {
|
subprocess = execaCommand(command, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
subprocess = execa(dockerCommand, dockerArgs, {
|
subprocess = execa(dockerCommand, dockerArgs, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const logs = [];
|
const logs = [];
|
||||||
@@ -651,19 +692,26 @@ export async function executeCommand({
|
|||||||
} else {
|
} else {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return await execaCommand(command, {
|
return await execaCommand(command, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return await execa(dockerCommand, dockerArgs, {
|
return await execa(dockerCommand, dockerArgs, {
|
||||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||||
|
timeout
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (shell) {
|
if (shell) {
|
||||||
return execaCommand(command, { shell: true });
|
return execaCommand(command, {
|
||||||
|
shell: true,
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return await execa(dockerCommand, dockerArgs);
|
return await execa(dockerCommand, dockerArgs, {
|
||||||
|
timeout
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,10 +760,12 @@ export async function startTraefikProxy(id: string): Promise<void> {
|
|||||||
-v coolify-traefik-letsencrypt:/etc/traefik/acme \
|
-v coolify-traefik-letsencrypt:/etc/traefik/acme \
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
--network coolify-infra \
|
--network coolify-infra \
|
||||||
-p "80:80" \
|
-p ${proxyPort ? `${proxyPort}:80` : `80:80`} \
|
||||||
-p "443:443" \
|
-p ${proxySecurePort ? `${proxySecurePort}:443` : `443:443`} \
|
||||||
|
${isDev ? '-p "8080:8080"' : ''} \
|
||||||
--name coolify-proxy \
|
--name coolify-proxy \
|
||||||
-d ${defaultTraefikImage} \
|
-d ${defaultTraefikImage} \
|
||||||
|
${isDev ? '--api.insecure=true' : ''} \
|
||||||
--entrypoints.web.address=:80 \
|
--entrypoints.web.address=:80 \
|
||||||
--entrypoints.web.forwardedHeaders.insecure=true \
|
--entrypoints.web.forwardedHeaders.insecure=true \
|
||||||
--entrypoints.websecure.address=:443 \
|
--entrypoints.websecure.address=:443 \
|
||||||
@@ -795,7 +845,7 @@ export function generateToken() {
|
|||||||
{
|
{
|
||||||
nbf: Math.floor(Date.now() / 1000) - 30
|
nbf: Math.floor(Date.now() / 1000) - 30
|
||||||
},
|
},
|
||||||
process.env['COOLIFY_SECRET_KEY']
|
getSecretKey()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function generatePassword({
|
export function generatePassword({
|
||||||
@@ -818,101 +868,101 @@ export function generatePassword({
|
|||||||
|
|
||||||
type DatabaseConfiguration =
|
type DatabaseConfiguration =
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MYSQL_DATABASE: string;
|
MYSQL_DATABASE: string;
|
||||||
MYSQL_PASSWORD: string;
|
MYSQL_PASSWORD: string;
|
||||||
MYSQL_ROOT_USER: string;
|
MYSQL_ROOT_USER: string;
|
||||||
MYSQL_USER: string;
|
MYSQL_USER: string;
|
||||||
MYSQL_ROOT_PASSWORD: string;
|
MYSQL_ROOT_PASSWORD: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MONGO_INITDB_ROOT_USERNAME?: string;
|
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||||
MONGO_INITDB_ROOT_PASSWORD?: string;
|
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||||
MONGODB_ROOT_USER?: string;
|
MONGODB_ROOT_USER?: string;
|
||||||
MONGODB_ROOT_PASSWORD?: string;
|
MONGODB_ROOT_PASSWORD?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
MARIADB_ROOT_USER: string;
|
MARIADB_ROOT_USER: string;
|
||||||
MARIADB_ROOT_PASSWORD: string;
|
MARIADB_ROOT_PASSWORD: string;
|
||||||
MARIADB_USER: string;
|
MARIADB_USER: string;
|
||||||
MARIADB_PASSWORD: string;
|
MARIADB_PASSWORD: string;
|
||||||
MARIADB_DATABASE: string;
|
MARIADB_DATABASE: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
POSTGRES_PASSWORD?: string;
|
POSTGRES_PASSWORD?: string;
|
||||||
POSTGRES_USER?: string;
|
POSTGRES_USER?: string;
|
||||||
POSTGRES_DB?: string;
|
POSTGRES_DB?: string;
|
||||||
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
||||||
POSTGRESQL_USERNAME?: string;
|
POSTGRESQL_USERNAME?: string;
|
||||||
POSTGRESQL_PASSWORD?: string;
|
POSTGRESQL_PASSWORD?: string;
|
||||||
POSTGRESQL_DATABASE?: string;
|
POSTGRESQL_DATABASE?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
REDIS_AOF_ENABLED: string;
|
REDIS_AOF_ENABLED: string;
|
||||||
REDIS_PASSWORD: string;
|
REDIS_PASSWORD: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
COUCHDB_PASSWORD: string;
|
COUCHDB_PASSWORD: string;
|
||||||
COUCHDB_USER: string;
|
COUCHDB_USER: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
volume: string;
|
volume: string;
|
||||||
image: string;
|
image: string;
|
||||||
command?: string;
|
command?: string;
|
||||||
ulimits: Record<string, unknown>;
|
ulimits: Record<string, unknown>;
|
||||||
privatePort: number;
|
privatePort: number;
|
||||||
environmentVariables: {
|
environmentVariables: {
|
||||||
EDGEDB_SERVER_PASSWORD: string;
|
EDGEDB_SERVER_PASSWORD: string;
|
||||||
EDGEDB_SERVER_USER: string;
|
EDGEDB_SERVER_USER: string;
|
||||||
EDGEDB_SERVER_DATABASE: string;
|
EDGEDB_SERVER_DATABASE: string;
|
||||||
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export function generateDatabaseConfiguration(database: any, arch: string): DatabaseConfiguration {
|
export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
|
||||||
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
|
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
|
||||||
database;
|
database;
|
||||||
const baseImage = getDatabaseImage(type, arch);
|
const baseImage = getDatabaseImage(type);
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
const configuration = {
|
const configuration = {
|
||||||
privatePort: 3306,
|
privatePort: 3306,
|
||||||
@@ -927,7 +977,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||||
}
|
}
|
||||||
return configuration;
|
return configuration;
|
||||||
@@ -945,7 +995,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||||
}
|
}
|
||||||
return configuration;
|
return configuration;
|
||||||
@@ -960,7 +1010,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.environmentVariables = {
|
configuration.environmentVariables = {
|
||||||
MONGO_INITDB_ROOT_USERNAME: rootUser,
|
MONGO_INITDB_ROOT_USERNAME: rootUser,
|
||||||
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
|
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
|
||||||
@@ -981,8 +1031,8 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
|
configuration.volume = `${id}-${type}-data:/var/lib/postgresql/data`;
|
||||||
configuration.environmentVariables = {
|
configuration.environmentVariables = {
|
||||||
POSTGRES_PASSWORD: dbUserPassword,
|
POSTGRES_PASSWORD: dbUserPassword,
|
||||||
POSTGRES_USER: dbUser,
|
POSTGRES_USER: dbUser,
|
||||||
@@ -1005,11 +1055,10 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/data`;
|
configuration.volume = `${id}-${type}-data:/data`;
|
||||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${
|
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
|
||||||
appendOnly ? 'yes' : 'no'
|
} --requirepass ${dbUserPassword}`;
|
||||||
} --requirepass ${dbUserPassword}`;
|
|
||||||
}
|
}
|
||||||
return configuration;
|
return configuration;
|
||||||
} else if (type === 'couchdb') {
|
} else if (type === 'couchdb') {
|
||||||
@@ -1023,7 +1072,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
||||||
ulimits: {}
|
ulimits: {}
|
||||||
};
|
};
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
|
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
|
||||||
}
|
}
|
||||||
return configuration;
|
return configuration;
|
||||||
@@ -1043,16 +1092,17 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
|||||||
return configuration;
|
return configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function isARM(arch: string) {
|
export function isARM() {
|
||||||
|
const arch = process.arch;
|
||||||
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
|
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
export function getDatabaseImage(type: string, arch: string): string {
|
export function getDatabaseImage(type: string): string {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
return found.baseImageARM || found.baseImage;
|
return found.baseImageARM || found.baseImage;
|
||||||
}
|
}
|
||||||
return found.baseImage;
|
return found.baseImage;
|
||||||
@@ -1060,10 +1110,10 @@ export function getDatabaseImage(type: string, arch: string): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDatabaseVersions(type: string, arch: string): string[] {
|
export function getDatabaseVersions(type: string): string[] {
|
||||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||||
if (found) {
|
if (found) {
|
||||||
if (isARM(arch)) {
|
if (isARM()) {
|
||||||
return found.versionsARM || found.versions;
|
return found.versionsARM || found.versions;
|
||||||
}
|
}
|
||||||
return found.versions;
|
return found.versions;
|
||||||
@@ -1093,12 +1143,12 @@ export type ComposeFileService = {
|
|||||||
command?: string;
|
command?: string;
|
||||||
ports?: string[];
|
ports?: string[];
|
||||||
build?:
|
build?:
|
||||||
| {
|
| {
|
||||||
context: string;
|
context: string;
|
||||||
dockerfile: string;
|
dockerfile: string;
|
||||||
args?: Record<string, unknown>;
|
args?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
| string;
|
| string;
|
||||||
deploy?: {
|
deploy?: {
|
||||||
restart_policy?: {
|
restart_policy?: {
|
||||||
condition?: string;
|
condition?: string;
|
||||||
@@ -1169,7 +1219,7 @@ export const createDirectories = async ({
|
|||||||
let workdirFound = false;
|
let workdirFound = false;
|
||||||
try {
|
try {
|
||||||
workdirFound = !!(await fs.stat(workdir));
|
workdirFound = !!(await fs.stat(workdir));
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
if (workdirFound) {
|
if (workdirFound) {
|
||||||
await executeCommand({ command: `rm -fr ${workdir}` });
|
await executeCommand({ command: `rm -fr ${workdir}` });
|
||||||
}
|
}
|
||||||
@@ -1629,8 +1679,8 @@ export function errorHandler({
|
|||||||
type?: string | null;
|
type?: string | null;
|
||||||
}) {
|
}) {
|
||||||
if (message.message) message = message.message;
|
if (message.message) message = message.message;
|
||||||
if (type === 'normal') {
|
if (message.includes('Unique constraint failed')) {
|
||||||
Sentry.captureException(message);
|
message = 'This data is unique and already exists. Please try again with a different value.';
|
||||||
}
|
}
|
||||||
throw { status, message };
|
throw { status, message };
|
||||||
}
|
}
|
||||||
@@ -1693,7 +1743,7 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1712,69 +1762,28 @@ export function convertTolOldVolumeNames(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
export async function cleanupDockerStorage(dockerId, volumes = false) {
|
||||||
// Cleanup old coolify images
|
// Cleanup images that are not used by any container
|
||||||
try {
|
try {
|
||||||
let { stdout: images } = await executeCommand({
|
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
// Prune coolify managed containers
|
||||||
|
try {
|
||||||
|
await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`,
|
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||||
shell: true
|
|
||||||
});
|
});
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
images = images.trim();
|
// Cleanup build caches
|
||||||
if (images) {
|
try {
|
||||||
await executeCommand({
|
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||||
dockerId,
|
} catch (error) { }
|
||||||
command: `docker rmi -f ${images}" -q | xargs -r`,
|
if (volumes) {
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
if (lowDiskSpace || force) {
|
|
||||||
// Cleanup images that are not used
|
|
||||||
try {
|
try {
|
||||||
await executeCommand({ dockerId, command: `docker image prune -f` });
|
await executeCommand({ dockerId, command: `docker volume prune -af` });
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
|
|
||||||
const { numberOfDockerImagesKeptLocally } = await prisma.setting.findUnique({
|
|
||||||
where: { id: '0' }
|
|
||||||
});
|
|
||||||
const { stdout: images } = await executeCommand({
|
|
||||||
dockerId,
|
|
||||||
command: `docker images|grep -v "<none>"|grep -v REPOSITORY|awk '{print $1, $2}'`,
|
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
|
|
||||||
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
|
|
||||||
let deleteImage = [];
|
|
||||||
for (const image of imagesSet) {
|
|
||||||
let keepImage = [];
|
|
||||||
for (const image2 of imagesArray) {
|
|
||||||
if (image2.startsWith(image)) {
|
|
||||||
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
|
|
||||||
deleteImage.push(image2);
|
|
||||||
} else {
|
|
||||||
keepImage.push(image2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const image of deleteImage) {
|
|
||||||
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune coolify managed containers
|
|
||||||
try {
|
|
||||||
await executeCommand({
|
|
||||||
dockerId,
|
|
||||||
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
|
||||||
});
|
|
||||||
} catch (error) {}
|
|
||||||
|
|
||||||
// Cleanup build caches
|
|
||||||
try {
|
|
||||||
await executeCommand({ dockerId, command: `docker builder prune -a -f` });
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1875,3 +1884,112 @@ export async function pushToRegistry(
|
|||||||
command: pushCommand
|
command: pushCommand
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseSecret(secret, isBuild) {
|
||||||
|
if (secret.value.includes('$')) {
|
||||||
|
secret.value = secret.value.replaceAll('$', '$$$$');
|
||||||
|
}
|
||||||
|
if (secret.value.includes('\\n')) {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}=${secret.value}`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}=${secret.value}`;
|
||||||
|
}
|
||||||
|
} else if (secret.value.includes(' ')) {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}='${secret.value}'`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}='${secret.value}'`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}=${secret.value}`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}=${secret.value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function generateSecrets(
|
||||||
|
secrets: Array<any>,
|
||||||
|
pullmergeRequestId: string,
|
||||||
|
isBuild = false,
|
||||||
|
port = null,
|
||||||
|
compose = false
|
||||||
|
): Array<string> {
|
||||||
|
const envs = [];
|
||||||
|
const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret);
|
||||||
|
const normalSecrets = secrets.filter((s) => !s.isPRMRSecret);
|
||||||
|
if (pullmergeRequestId && isPRMRSecret.length > 0) {
|
||||||
|
isPRMRSecret.forEach((secret) => {
|
||||||
|
if (isBuild && !secret.isBuildSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const build = isBuild && secret.isBuildSecret;
|
||||||
|
envs.push(parseSecret(secret, compose ? false : build));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!pullmergeRequestId && normalSecrets.length > 0) {
|
||||||
|
normalSecrets.forEach((secret) => {
|
||||||
|
if (isBuild && !secret.isBuildSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const build = isBuild && secret.isBuildSecret;
|
||||||
|
envs.push(parseSecret(secret, compose ? false : build));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const portFound = envs.filter((env) => env.startsWith('PORT'));
|
||||||
|
if (portFound.length === 0 && port && !isBuild) {
|
||||||
|
envs.push(`PORT=${port}`);
|
||||||
|
}
|
||||||
|
const nodeEnv = envs.filter((env) => env.startsWith('NODE_ENV'));
|
||||||
|
if (nodeEnv.length === 0 && !isBuild) {
|
||||||
|
envs.push(`NODE_ENV=production`);
|
||||||
|
}
|
||||||
|
return envs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function backupDatabaseNow(database, reply) {
|
||||||
|
const backupFolder = '/tmp'
|
||||||
|
const fileName = `${database.id}-${new Date().getTime()}.gz`
|
||||||
|
const backupFileName = `${backupFolder}/${fileName}`
|
||||||
|
const backupStorageFilename = `/app/backups/${fileName}`
|
||||||
|
let command = null
|
||||||
|
switch (database?.type) {
|
||||||
|
case 'postgresql':
|
||||||
|
command = `docker exec ${database.id} sh -c "PGPASSWORD=${database.rootUserPassword} pg_dumpall -U postgres | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'mongodb':
|
||||||
|
command = `docker exec ${database.id} sh -c "mongodump --archive=${backupFileName} --gzip --username=${database.rootUser} --password=${database.rootUserPassword}"`
|
||||||
|
break;
|
||||||
|
case 'mysql':
|
||||||
|
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'mariadb':
|
||||||
|
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
|
||||||
|
break;
|
||||||
|
case 'couchdb':
|
||||||
|
command = `docker exec ${database.id} sh -c "tar -czvf ${backupFileName} /bitnami/couchdb/data"`
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command,
|
||||||
|
});
|
||||||
|
const copyCommand = `docker cp ${database.id}:${backupFileName} ${backupFileName}`
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command: copyCommand
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: database.destinationDockerId,
|
||||||
|
command: `docker cp ${database.id}:${backupFileName} /app/backups/`
|
||||||
|
});
|
||||||
|
const stream = fsNormal.createReadStream(backupFileName);
|
||||||
|
reply.header('Content-Type', 'application/octet-stream');
|
||||||
|
reply.header('Content-Disposition', `attachment; filename=${fileName}`);
|
||||||
|
reply.header('Content-Length', fsNormal.statSync(backupFileName).size);
|
||||||
|
reply.header('Content-Transfer-Encoding', 'binary');
|
||||||
|
return reply.send(stream)
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
privateSshKey,
|
privateSshKey,
|
||||||
customPort,
|
customPort,
|
||||||
forPublic
|
forPublic,
|
||||||
|
customUser,
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
@@ -25,6 +26,7 @@ export default async function ({
|
|||||||
privateSshKey: string;
|
privateSshKey: string;
|
||||||
customPort: number;
|
customPort: number;
|
||||||
forPublic: boolean;
|
forPublic: boolean;
|
||||||
|
customUser: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||||
if (!forPublic) {
|
if (!forPublic) {
|
||||||
@@ -53,7 +55,7 @@ export default async function ({
|
|||||||
} else {
|
} else {
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
command:
|
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
|
`git clone -q -b ${branch} ${customUser}@${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
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,47 @@
|
|||||||
import { isARM, isDev } from "./common";
|
import { isARM, isDev } from './common';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
export async function getTemplates() {
|
export async function getTemplates() {
|
||||||
const templatePath = isDev ? './templates.json' : '/app/templates.json';
|
const templatePath = isDev ? './templates.json' : '/app/templates.json';
|
||||||
const open = await fs.open(templatePath, 'r');
|
const open = await fs.open(templatePath, 'r');
|
||||||
try {
|
try {
|
||||||
let data = await open.readFile({ encoding: 'utf-8' });
|
let data = await open.readFile({ encoding: 'utf-8' });
|
||||||
let jsonData = JSON.parse(data)
|
let jsonData = JSON.parse(data);
|
||||||
if (isARM(process.arch)) {
|
if (isARM()) {
|
||||||
jsonData = jsonData.filter(d => d.arch !== 'amd64')
|
jsonData = jsonData.filter((d) => d.arch !== 'amd64');
|
||||||
}
|
}
|
||||||
return jsonData;
|
return jsonData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return []
|
return [];
|
||||||
} finally {
|
} finally {
|
||||||
await open?.close()
|
await open?.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const compareSemanticVersions = (a: string, b: string) => {
|
const compareSemanticVersions = (a: string, b: string) => {
|
||||||
const a1 = a.split('.');
|
const a1 = a.split('.');
|
||||||
const b1 = b.split('.');
|
const b1 = b.split('.');
|
||||||
const len = Math.min(a1.length, b1.length);
|
const len = Math.min(a1.length, b1.length);
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const a2 = +a1[i] || 0;
|
const a2 = +a1[i] || 0;
|
||||||
const b2 = +b1[i] || 0;
|
const b2 = +b1[i] || 0;
|
||||||
if (a2 !== b2) {
|
if (a2 !== b2) {
|
||||||
return a2 > b2 ? 1 : -1;
|
return a2 > b2 ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b1.length - a1.length;
|
return b1.length - a1.length;
|
||||||
};
|
};
|
||||||
export async function getTags(type: string) {
|
export async function getTags(type: string) {
|
||||||
|
try {
|
||||||
try {
|
if (type) {
|
||||||
if (type) {
|
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||||
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
const data = await fs.readFile(tagsPath, 'utf8');
|
||||||
const data = await fs.readFile(tagsPath, 'utf8')
|
let tags = JSON.parse(data);
|
||||||
let tags = JSON.parse(data)
|
if (tags) {
|
||||||
if (tags) {
|
tags = tags.find((tag: any) => tag.name.includes(type));
|
||||||
tags = tags.find((tag: any) => tag.name.includes(type))
|
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
return tags;
|
||||||
return tags
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
} catch (error) {
|
return [];
|
||||||
return []
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const service = await getServiceFromDB({ id, teamId });
|
const service = await getServiceFromDB({ id, teamId });
|
||||||
const arm = isARM(service.arch);
|
const arm = isARM();
|
||||||
const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } =
|
const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } =
|
||||||
service;
|
service;
|
||||||
|
|
||||||
@@ -50,24 +50,12 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
|||||||
const config = {};
|
const config = {};
|
||||||
for (const s in template.services) {
|
for (const s in template.services) {
|
||||||
let newEnvironments = []
|
let newEnvironments = []
|
||||||
if (arm) {
|
if (template.services[s]?.environment?.length > 0) {
|
||||||
if (template.services[s]?.environmentArm?.length > 0) {
|
for (const environment of template.services[s].environment) {
|
||||||
for (const environment of template.services[s].environmentArm) {
|
let [env, ...value] = environment.split("=");
|
||||||
let [env, ...value] = environment.split("=");
|
value = value.join("=")
|
||||||
value = value.join("=")
|
if (!value.startsWith('$$secret') && value !== '') {
|
||||||
if (!value.startsWith('$$secret') && value !== '') {
|
newEnvironments.push(`${env}=${value}`)
|
||||||
newEnvironments.push(`${env}=${value}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (template.services[s]?.environment?.length > 0) {
|
|
||||||
for (const environment of template.services[s].environment) {
|
|
||||||
let [env, ...value] = environment.split("=");
|
|
||||||
value = value.join("=")
|
|
||||||
if (!value.startsWith('$$secret') && value !== '') {
|
|
||||||
newEnvironments.push(`${env}=${value}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,12 +75,13 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
|||||||
}
|
}
|
||||||
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
|
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
|
||||||
let volumes = new Set()
|
let volumes = new Set()
|
||||||
if (arm) {
|
if (arm && template.services[s]?.volumesArm?.length > 0) {
|
||||||
template.services[s]?.volumesArm && template.services[s].volumesArm.length > 0 && template.services[s].volumesArm.forEach(v => volumes.add(v))
|
template.services[s].volumesArm.forEach(v => volumes.add(v))
|
||||||
} else {
|
} else {
|
||||||
template.services[s]?.volumes && template.services[s].volumes.length > 0 && template.services[s].volumes.forEach(v => volumes.add(v))
|
if (template.services[s]?.volumes?.length > 0) {
|
||||||
|
template.services[s].volumes.forEach(v => volumes.add(v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround: old plausible analytics service wrong volume id name
|
// Workaround: old plausible analytics service wrong volume id name
|
||||||
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
|
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
|
||||||
let temp = Array.from(volumes)
|
let temp = Array.from(volumes)
|
||||||
|
|||||||
@@ -624,7 +624,7 @@ export const glitchTip = [{
|
|||||||
isEncrypted: false
|
isEncrypted: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'emailSmtpUseSsl',
|
name: 'emailSmtpUseTls',
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
isLowerCase: false,
|
isLowerCase: false,
|
||||||
isNumber: false,
|
isNumber: false,
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
import fp from 'fastify-plugin'
|
import fp from 'fastify-plugin';
|
||||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'
|
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt';
|
||||||
|
|
||||||
declare module "@fastify/jwt" {
|
declare module '@fastify/jwt' {
|
||||||
interface FastifyJWT {
|
interface FastifyJWT {
|
||||||
user: {
|
user: {
|
||||||
userId: string,
|
userId: string;
|
||||||
teamId: string,
|
teamId: string;
|
||||||
permission: string,
|
permission: string;
|
||||||
isAdmin: boolean
|
isAdmin: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
||||||
fastify.register(fastifyJwt, {
|
let secretKey = fastify.config.COOLIFY_SECRET_KEY_BETTER;
|
||||||
secret: fastify.config.COOLIFY_SECRET_KEY
|
if (!secretKey) {
|
||||||
})
|
secretKey = fastify.config.COOLIFY_SECRET_KEY;
|
||||||
|
}
|
||||||
|
fastify.register(fastifyJwt, {
|
||||||
|
secret: secretKey
|
||||||
|
});
|
||||||
|
|
||||||
fastify.decorate("authenticate", async function (request, reply) {
|
fastify.decorate('authenticate', async function (request, reply) {
|
||||||
try {
|
try {
|
||||||
await request.jwtVerify()
|
await request.jwtVerify();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reply.send(err)
|
reply.send(err);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
export interface FastifyInstance {
|
export interface FastifyInstance {
|
||||||
authenticate(): Promise<void>
|
authenticate(): Promise<void>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,154 +1,174 @@
|
|||||||
import type { OnlyId } from "../../../../types";
|
import type { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
export interface SaveApplication extends OnlyId {
|
export interface SaveApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
name: string,
|
name: string;
|
||||||
buildPack: string,
|
buildPack: string;
|
||||||
fqdn: string,
|
fqdn: string;
|
||||||
port: number,
|
port: number;
|
||||||
exposePort: number,
|
exposePort: number;
|
||||||
installCommand: string,
|
installCommand: string;
|
||||||
buildCommand: string,
|
buildCommand: string;
|
||||||
startCommand: string,
|
startCommand: string;
|
||||||
baseDirectory: string,
|
baseDirectory: string;
|
||||||
publishDirectory: string,
|
publishDirectory: 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;
|
||||||
baseImage: string,
|
baseImage: string;
|
||||||
gitCommitHash: string,
|
gitCommitHash: string;
|
||||||
baseBuildImage: string,
|
baseBuildImage: string;
|
||||||
deploymentType: string,
|
deploymentType: string;
|
||||||
baseDatabaseBranch: string,
|
baseDatabaseBranch: string;
|
||||||
dockerComposeFile: string,
|
dockerComposeFile: string;
|
||||||
dockerComposeFileLocation: string,
|
dockerComposeFileLocation: string;
|
||||||
dockerComposeConfiguration: string,
|
dockerComposeConfiguration: string;
|
||||||
simpleDockerfile: string,
|
simpleDockerfile: string;
|
||||||
dockerRegistryImageName: string
|
dockerRegistryImageName: string;
|
||||||
}
|
basicAuthPw: string;
|
||||||
|
basicAuthUser: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
|
Body: {
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
dualCerts: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
branch: string;
|
||||||
|
projectId: number;
|
||||||
|
isBot: boolean;
|
||||||
|
isDBBranching: boolean;
|
||||||
|
isCustomSSL: boolean;
|
||||||
|
isHttp2: boolean;
|
||||||
|
basicAuth: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: { force: boolean }
|
Body: { force: boolean };
|
||||||
}
|
}
|
||||||
export interface CheckDomain extends OnlyId {
|
export interface CheckDomain extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
}
|
}
|
||||||
export interface CheckDNS extends OnlyId {
|
export interface CheckDNS extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: {
|
Body: {
|
||||||
exposePort: number,
|
exposePort: number;
|
||||||
fqdn: string,
|
fqdn: string;
|
||||||
forceSave: boolean,
|
forceSave: boolean;
|
||||||
dualCerts: boolean
|
dualCerts: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeployApplication {
|
export interface DeployApplication {
|
||||||
Querystring: { domain: string }
|
Querystring: { domain: string };
|
||||||
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
|
||||||
}
|
}
|
||||||
export interface GetImages {
|
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, simpleDockerfile?: 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 };
|
||||||
}
|
}
|
||||||
export interface SaveDestination extends OnlyId {
|
export interface SaveDestination extends OnlyId {
|
||||||
Body: { destinationId: string }
|
Body: { destinationId: string };
|
||||||
}
|
}
|
||||||
export interface SaveSecret extends OnlyId {
|
export interface SaveSecret extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
name: string,
|
name: string;
|
||||||
value: string,
|
value: string;
|
||||||
isBuildSecret: boolean,
|
isBuildSecret: boolean;
|
||||||
previewSecret: boolean,
|
previewSecret: boolean;
|
||||||
isNew: boolean
|
isNew: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteSecret extends OnlyId {
|
export interface DeleteSecret extends OnlyId {
|
||||||
Body: { name: string }
|
Body: { name: string };
|
||||||
}
|
}
|
||||||
export interface SaveStorage extends OnlyId {
|
export interface SaveStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
hostPath?: string;
|
||||||
newStorage: boolean,
|
path: string;
|
||||||
storageId: string
|
newStorage: boolean;
|
||||||
}
|
storageId: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteStorage extends OnlyId {
|
export interface DeleteStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
path: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetApplicationLogs {
|
export interface GetApplicationLogs {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
containerId: string
|
containerId: string;
|
||||||
}
|
};
|
||||||
Querystring: {
|
Querystring: {
|
||||||
since: number,
|
since: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetBuilds extends OnlyId {
|
export interface GetBuilds extends OnlyId {
|
||||||
Querystring: {
|
Querystring: {
|
||||||
buildId: string
|
buildId: string;
|
||||||
skip: number,
|
skip: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetBuildIdLogs {
|
export interface GetBuildIdLogs {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
buildId: string
|
buildId: string;
|
||||||
},
|
};
|
||||||
Querystring: {
|
Querystring: {
|
||||||
sequence: number
|
sequence: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface SaveDeployKey extends OnlyId {
|
export interface SaveDeployKey extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
deployKeyId: number
|
deployKeyId: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface CancelDeployment {
|
export interface CancelDeployment {
|
||||||
Body: {
|
Body: {
|
||||||
buildId: string,
|
buildId: string;
|
||||||
applicationId: string
|
applicationId: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeployApplication extends OnlyId {
|
export interface DeployApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
branch: string,
|
branch: string;
|
||||||
forceRebuild?: boolean
|
forceRebuild?: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StopPreviewApplication extends OnlyId {
|
export interface StopPreviewApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface RestartPreviewApplication {
|
export interface RestartPreviewApplication {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface RestartApplication {
|
export interface RestartApplication {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
},
|
};
|
||||||
Body: {
|
Body: {
|
||||||
imageId: string | null,
|
imageId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@@ -1,31 +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.addHook('onRequest', async (request) => {
|
fastify.addHook('onRequest', async (request) => {
|
||||||
try {
|
try {
|
||||||
await request.jwtVerify()
|
await request.jwtVerify();
|
||||||
} catch(error) {
|
} catch (error) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fastify.get('/', async (request) => {
|
fastify.get('/', async (request) => {
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const settings = await listSettings()
|
const settings = await listSettings();
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
ipv4: teamId ? settings.ipv4 : null,
|
ipv4: teamId ? settings.ipv4 : null,
|
||||||
ipv6: teamId ? settings.ipv6 : null,
|
ipv6: teamId ? settings.ipv6 : null,
|
||||||
version,
|
version,
|
||||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||||
}
|
isARM: isARM()
|
||||||
} catch ({ status, message }) {
|
};
|
||||||
return errorHandler({ status, message })
|
} catch ({ status, message }) {
|
||||||
}
|
return errorHandler({ status, message });
|
||||||
});
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
|
|
||||||
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
|
||||||
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
|
||||||
|
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
|
|||||||
Body: { type: string }
|
Body: { type: string }
|
||||||
}
|
}
|
||||||
export interface DeleteDatabase extends OnlyId {
|
export interface DeleteDatabase extends OnlyId {
|
||||||
Body: { force: string }
|
Body: { }
|
||||||
}
|
}
|
||||||
export interface SaveVersion extends OnlyId {
|
export interface SaveVersion extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
|
|||||||
@@ -1,279 +1,384 @@
|
|||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import sshConfig from 'ssh-config'
|
import {
|
||||||
import fs from 'fs/promises'
|
errorHandler,
|
||||||
import os from 'os';
|
executeCommand,
|
||||||
|
listSettings,
|
||||||
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
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';
|
||||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
import type {
|
||||||
|
CheckDestination,
|
||||||
|
ListDestinations,
|
||||||
|
NewDestination,
|
||||||
|
Proxy,
|
||||||
|
SaveDestinationSettings
|
||||||
|
} from './types';
|
||||||
|
import { removeService } from '../../../../lib/services/common';
|
||||||
|
|
||||||
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { onlyVerified = false } = request.query
|
const { onlyVerified = false } = request.query;
|
||||||
let destinations = []
|
let destinations = [];
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||||
} else {
|
} else {
|
||||||
destinations = await prisma.destinationDocker.findMany({
|
destinations = await prisma.destinationDocker.findMany({
|
||||||
where: { teams: { some: { id: teamId } } },
|
where: { teams: { some: { id: teamId } } },
|
||||||
include: { teams: true }
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (onlyVerified) {
|
if (onlyVerified) {
|
||||||
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
|
destinations = destinations.filter(
|
||||||
}
|
(destination) =>
|
||||||
return {
|
destination.engine || (destination.remoteEngine && destination.remoteVerified)
|
||||||
destinations
|
);
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
return {
|
||||||
return errorHandler({ status, message })
|
destinations
|
||||||
}
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
|
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
|
||||||
try {
|
try {
|
||||||
const { network } = request.body;
|
const { network } = request.body;
|
||||||
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw {
|
throw {
|
||||||
message: `Network already exists: ${network}`
|
message: `Network already exists: ${network}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getDestination(request: FastifyRequest<OnlyId>) {
|
export async function getDestination(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const destination = await prisma.destinationDocker.findFirst({
|
const destination = await prisma.destinationDocker.findFirst({
|
||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { sshKey: true, application: true, service: true, database: true }
|
include: { sshKey: true, application: true, service: true, database: true }
|
||||||
});
|
});
|
||||||
if (!destination && id !== 'new') {
|
if (!destination && id !== 'new') {
|
||||||
throw { status: 404, message: `Destination not found.` };
|
throw { status: 404, message: `Destination not found.` };
|
||||||
}
|
}
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
const payload = {
|
const payload = {
|
||||||
destination,
|
destination,
|
||||||
settings
|
settings
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
...payload
|
...payload
|
||||||
};
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
} catch ({ status, message }) {
|
return errorHandler({ status, message });
|
||||||
return errorHandler({ status, message })
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
|
|
||||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
|
||||||
if (id === 'new') {
|
request.body;
|
||||||
if (engine) {
|
if (id === 'new') {
|
||||||
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
|
if (engine) {
|
||||||
if (stdout === '') {
|
const { stdout } = await await executeCommand({
|
||||||
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
|
||||||
}
|
});
|
||||||
await prisma.destinationDocker.create({
|
if (stdout === '') {
|
||||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
||||||
});
|
}
|
||||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
await prisma.destinationDocker.create({
|
||||||
const destination = destinations.find((destination) => destination.network === network);
|
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||||
if (destinations.length > 0) {
|
});
|
||||||
const proxyConfigured = destinations.find(
|
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
const destination = destinations.find((destination) => destination.network === network);
|
||||||
);
|
if (destinations.length > 0) {
|
||||||
if (proxyConfigured) {
|
const proxyConfigured = destinations.find(
|
||||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
(destination) =>
|
||||||
}
|
destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
);
|
||||||
}
|
if (proxyConfigured) {
|
||||||
if (isCoolifyProxyUsed) {
|
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||||
await startTraefikProxy(destination.id);
|
}
|
||||||
}
|
await prisma.destinationDocker.updateMany({
|
||||||
return reply.code(201).send({ id: destination.id });
|
where: { engine },
|
||||||
} else {
|
data: { isCoolifyProxyUsed }
|
||||||
const destination = await prisma.destinationDocker.create({
|
});
|
||||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort: Number(remotePort) }
|
}
|
||||||
});
|
if (isCoolifyProxyUsed) {
|
||||||
return reply.code(201).send({ id: destination.id })
|
await startTraefikProxy(destination.id);
|
||||||
}
|
}
|
||||||
} else {
|
return reply.code(201).send({ id: destination.id });
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
} else {
|
||||||
return reply.code(201).send();
|
const destination = await prisma.destinationDocker.create({
|
||||||
}
|
data: {
|
||||||
|
name,
|
||||||
} catch ({ status, message }) {
|
teams: { connect: { id: teamId } },
|
||||||
return errorHandler({ status, message })
|
engine,
|
||||||
}
|
network,
|
||||||
|
isCoolifyProxyUsed,
|
||||||
|
remoteEngine: true,
|
||||||
|
remoteIpAddress,
|
||||||
|
remoteUser,
|
||||||
|
remotePort: Number(remotePort)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reply.code(201).send({ id: destination.id });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||||
|
return reply.code(201).send();
|
||||||
|
}
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
|
||||||
|
try {
|
||||||
|
const { id } = request.params;
|
||||||
|
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const service of services) {
|
||||||
|
await removeService({ id: service.id });
|
||||||
|
}
|
||||||
|
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const application of applications) {
|
||||||
|
await prisma.applicationSettings.deleteMany({ where: { application: { id: application.id } } });
|
||||||
|
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.build.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
await prisma.previewApplication.deleteMany({ where: { applicationId: application.id } });
|
||||||
|
}
|
||||||
|
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
|
||||||
|
for (const database of databases) {
|
||||||
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
|
||||||
|
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
|
||||||
|
await prisma.database.delete({ where: { id: database.id } });
|
||||||
|
}
|
||||||
|
await prisma.destinationDocker.delete({ where: { id } });
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
|
||||||
if (isCoolifyProxyUsed) {
|
const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
|
||||||
if (engine || remoteVerified) {
|
const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
|
||||||
const { stdout: found } = await executeCommand({
|
if (appFound || serviceFound || databaseFound) {
|
||||||
dockerId: id,
|
throw {
|
||||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
|
||||||
})
|
};
|
||||||
if (found) {
|
}
|
||||||
await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
|
const { network, remoteVerified, engine, isCoolifyProxyUsed } =
|
||||||
await executeCommand({ dockerId: id, command: `docker network rm ${network}` })
|
await prisma.destinationDocker.findUnique({ where: { id } });
|
||||||
}
|
if (isCoolifyProxyUsed) {
|
||||||
}
|
if (engine || remoteVerified) {
|
||||||
}
|
const { stdout: found } = await executeCommand({
|
||||||
await prisma.destinationDocker.delete({ where: { id } });
|
dockerId: id,
|
||||||
return {}
|
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||||
} catch ({ status, message }) {
|
});
|
||||||
return errorHandler({ status, message })
|
if (found) {
|
||||||
}
|
await executeCommand({
|
||||||
|
dockerId: id,
|
||||||
|
command: `docker network disconnect ${network} coolify-proxy`
|
||||||
|
});
|
||||||
|
await executeCommand({ dockerId: id, command: `docker network rm ${network}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await prisma.destinationDocker.delete({ where: { id } });
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
|
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
|
||||||
try {
|
try {
|
||||||
const { engine, isCoolifyProxyUsed } = request.body;
|
const { engine, isCoolifyProxyUsed } = request.body;
|
||||||
await prisma.destinationDocker.updateMany({
|
await prisma.destinationDocker.updateMany({
|
||||||
where: { engine },
|
where: { engine },
|
||||||
data: { isCoolifyProxyUsed }
|
data: { isCoolifyProxyUsed }
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 202
|
status: 202
|
||||||
}
|
};
|
||||||
// return reply.code(201).send();
|
// return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function startProxy(request: FastifyRequest<Proxy>) {
|
export async function startProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
try {
|
try {
|
||||||
await startTraefikProxy(id);
|
await startTraefikProxy(id);
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
await stopTraefikProxy(id);
|
await stopTraefikProxy(id);
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
try {
|
try {
|
||||||
await stopTraefikProxy(id);
|
await stopTraefikProxy(id);
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
try {
|
try {
|
||||||
await stopTraefikProxy(id);
|
await stopTraefikProxy(id);
|
||||||
await startTraefikProxy(id);
|
await startTraefikProxy(id);
|
||||||
await prisma.destinationDocker.update({
|
await prisma.destinationDocker.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isCoolifyProxyUsed: true }
|
data: { isCoolifyProxyUsed: true }
|
||||||
});
|
});
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
await prisma.destinationDocker.update({
|
await prisma.destinationDocker.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isCoolifyProxyUsed: false }
|
data: { isCoolifyProxyUsed: false }
|
||||||
});
|
});
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function assignSSHKey(request: FastifyRequest) {
|
export async function assignSSHKey(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const { id: sshKeyId } = request.body;
|
const { id: sshKeyId } = request.body;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
|
await prisma.destinationDocker.update({
|
||||||
return {}
|
where: { id },
|
||||||
} catch ({ status, message }) {
|
data: { sshKey: { connect: { id: sshKeyId } } }
|
||||||
return errorHandler({ status, message })
|
});
|
||||||
}
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function verifyRemoteDockerEngineFn(id: string) {
|
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
|
||||||
const daemonJson = `daemon-${id}.json`
|
{ where: { id } }
|
||||||
try {
|
);
|
||||||
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id });
|
const daemonJson = `daemon-${id}.json`;
|
||||||
} catch (error) {
|
try {
|
||||||
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
|
await executeCommand({
|
||||||
}
|
sshCommand: true,
|
||||||
|
command: `docker network inspect ${network}`,
|
||||||
|
dockerId: id
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await executeCommand({
|
||||||
|
command: `docker network create --attachable ${network}`,
|
||||||
|
dockerId: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id });
|
await executeCommand({
|
||||||
} catch (error) {
|
sshCommand: true,
|
||||||
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
|
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);
|
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||||
let isUpdated = false;
|
let isUpdated = false;
|
||||||
let daemonJsonParsed = {
|
let daemonJsonParsed = {
|
||||||
"live-restore": true,
|
'live-restore': true,
|
||||||
"features": {
|
features: {
|
||||||
"buildkit": true
|
buildkit: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
|
const { stdout: daemonJson } = await executeCommand({
|
||||||
daemonJsonParsed = JSON.parse(daemonJson);
|
sshCommand: true,
|
||||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
dockerId: id,
|
||||||
isUpdated = true;
|
command: `cat /etc/docker/daemon.json`
|
||||||
daemonJsonParsed['live-restore'] = true
|
});
|
||||||
|
daemonJsonParsed = JSON.parse(daemonJson);
|
||||||
}
|
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||||
if (!daemonJsonParsed?.features?.buildkit) {
|
isUpdated = true;
|
||||||
isUpdated = true;
|
daemonJsonParsed['live-restore'] = true;
|
||||||
daemonJsonParsed.features = {
|
}
|
||||||
buildkit: true
|
if (!daemonJsonParsed?.features?.buildkit) {
|
||||||
}
|
isUpdated = true;
|
||||||
}
|
daemonJsonParsed.features = {
|
||||||
} catch (error) {
|
buildkit: true
|
||||||
isUpdated = true;
|
};
|
||||||
}
|
}
|
||||||
try {
|
} catch (error) {
|
||||||
if (isUpdated) {
|
isUpdated = true;
|
||||||
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` });
|
try {
|
||||||
await executeCommand({ command: `rm /tmp/${daemonJson}` })
|
if (isUpdated) {
|
||||||
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
|
await executeCommand({
|
||||||
}
|
shell: true,
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
|
||||||
} catch (error) {
|
});
|
||||||
throw new Error('Error while verifying remote docker engine')
|
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) {
|
||||||
|
console.log(error)
|
||||||
|
throw new Error('Error while verifying remote docker engine');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
export async function verifyRemoteDockerEngine(
|
||||||
const { id } = request.params;
|
request: FastifyRequest<OnlyId>,
|
||||||
try {
|
reply: FastifyReply
|
||||||
await verifyRemoteDockerEngineFn(id);
|
) {
|
||||||
return reply.code(201).send()
|
const { id } = request.params;
|
||||||
} catch ({ status, message }) {
|
try {
|
||||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
|
await verifyRemoteDockerEngineFn(id);
|
||||||
return errorHandler({ status, message })
|
return reply.code(201).send();
|
||||||
}
|
} catch ({ status, message }) {
|
||||||
|
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
|
const destination = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||||
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
|
const { found: isRunning } = await checkContainer({
|
||||||
return {
|
dockerId: destination.id,
|
||||||
isRunning
|
container: 'coolify-proxy',
|
||||||
}
|
remove: true
|
||||||
} catch ({ status, message }) {
|
});
|
||||||
return errorHandler({ status, message })
|
return {
|
||||||
}
|
isRunning
|
||||||
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FastifyPluginAsync } from 'fastify';
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||||
|
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||||
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
|
||||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
|
||||||
|
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
|
||||||
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
|
||||||
|
|
||||||
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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 fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import {
|
import {
|
||||||
@@ -12,16 +12,14 @@ import {
|
|||||||
prisma,
|
prisma,
|
||||||
uniqueName,
|
uniqueName,
|
||||||
version,
|
version,
|
||||||
sentryDSN,
|
executeCommand
|
||||||
executeCommand,
|
} from '../../../lib/common';
|
||||||
} from "../../../lib/common";
|
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 ".";
|
import type { GetCurrentUser } from './types';
|
||||||
import type { GetCurrentUser } from "./types";
|
|
||||||
|
|
||||||
export async function hashPassword(password: string): Promise<string> {
|
export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
|
||||||
const saltRounds = 15;
|
|
||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +27,9 @@ export async function backup(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const { backupData } = request.params;
|
const { backupData } = request.params;
|
||||||
let std = null;
|
let std = null;
|
||||||
const [id, backupType, type, zipped, storage] = backupData.split(':')
|
const [id, backupType, type, zipped, storage] = backupData.split(':');
|
||||||
console.log(id, backupType, type, zipped, storage)
|
console.log(id, backupType, type, zipped, storage);
|
||||||
const database = await prisma.database.findUnique({ where: { id } })
|
const database = await prisma.database.findUnique({ where: { id } });
|
||||||
if (database) {
|
if (database) {
|
||||||
// await executeDockerCmd({
|
// await executeDockerCmd({
|
||||||
// dockerId: database.destinationDockerId,
|
// dockerId: database.destinationDockerId,
|
||||||
@@ -40,8 +38,7 @@ export async function backup(request: FastifyRequest) {
|
|||||||
std = await executeCommand({
|
std = await executeCommand({
|
||||||
dockerId: database.destinationDockerId,
|
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`
|
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) {
|
if (std.stdout) {
|
||||||
return std.stdout;
|
return std.stdout;
|
||||||
@@ -58,9 +55,9 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const { serverId } = request.body;
|
const { serverId } = request.body;
|
||||||
const destination = await prisma.destinationDocker.findUnique({
|
const destination = await prisma.destinationDocker.findUnique({
|
||||||
where: { id: serverId },
|
where: { id: serverId }
|
||||||
});
|
});
|
||||||
await cleanupDockerStorage(destination.id, true, true);
|
await cleanupDockerStorage(destination.id, true);
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -68,17 +65,25 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
}
|
}
|
||||||
export async function refreshTags() {
|
export async function refreshTags() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||||
await fs.writeFile('./tags.json', tags)
|
try {
|
||||||
|
if (await fs.stat('./testTags.json')) {
|
||||||
|
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||||
|
if (testTags.length > 0) {
|
||||||
|
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
await fs.writeFile('./tags.json', tags);
|
||||||
} else {
|
} else {
|
||||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||||
await fs.writeFile('/app/tags.json', tags)
|
await fs.writeFile('/app/tags.json', tags);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -88,17 +93,25 @@ export async function refreshTags() {
|
|||||||
}
|
}
|
||||||
export async function refreshTemplates() {
|
export async function refreshTemplates() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
try {
|
||||||
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||||
} else {
|
} else {
|
||||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
const response = await got
|
||||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||||
|
.text();
|
||||||
|
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -107,28 +120,29 @@ export async function refreshTemplates() {
|
|||||||
}
|
}
|
||||||
export async function checkUpdate(request: FastifyRequest) {
|
export async function checkUpdate(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
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 { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
const { coolify } = await got
|
||||||
searchParams: {
|
.get('https://get.coollabs.io/versions.json', {
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
searchParams: {
|
||||||
version: currentVersion
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
}
|
version: currentVersion
|
||||||
}).json()
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
const latestVersion = coolify.main.version;
|
const latestVersion = coolify.main.version;
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
if (isStaging) {
|
if (isStaging) {
|
||||||
return {
|
return {
|
||||||
isUpdateAvailable: true,
|
isUpdateAvailable: true,
|
||||||
latestVersion: "next",
|
latestVersion: 'next'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
||||||
latestVersion,
|
latestVersion
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -140,10 +154,22 @@ 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 executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||||
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
try {
|
||||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
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"` });
|
} catch (error) {
|
||||||
|
image = `coollabsio/coolify:${latestVersion}`;
|
||||||
|
await executeCommand({ command: `docker pull ${image}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .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 ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .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);
|
||||||
@@ -156,12 +182,12 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
export async function resetQueue(request: FastifyRequest<any>) {
|
export async function resetQueue(request: FastifyRequest<any>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (teamId === "0") {
|
if (teamId === '0') {
|
||||||
await prisma.build.updateMany({
|
await prisma.build.updateMany({
|
||||||
where: { status: { in: ["queued", "running"] } },
|
where: { status: { in: ['queued', 'running'] } },
|
||||||
data: { status: "canceled" },
|
data: { status: 'canceled' }
|
||||||
});
|
});
|
||||||
scheduler.workers.get("deployApplication").postMessage("cancel");
|
scheduler.workers.get('deployApplication').postMessage('cancel');
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -170,7 +196,7 @@ export async function resetQueue(request: FastifyRequest<any>) {
|
|||||||
export async function restartCoolify(request: FastifyRequest<any>) {
|
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (teamId === "0") {
|
if (teamId === '0') {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
await executeCommand({ command: `docker restart coolify` });
|
await executeCommand({ command: `docker restart coolify` });
|
||||||
return {};
|
return {};
|
||||||
@@ -180,7 +206,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
|||||||
}
|
}
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message: "You are not authorized to restart Coolify.",
|
message: 'You are not authorized to restart Coolify.'
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -192,43 +218,52 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
let applications = await prisma.application.findMany({
|
let applications = await prisma.application.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { settings: true, destinationDocker: true, teams: true },
|
include: { settings: true, destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const databases = await prisma.database.findMany({
|
const databases = await prisma.database.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { settings: true, destinationDocker: true, teams: true },
|
include: { settings: true, destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const services = await prisma.service.findMany({
|
const services = await prisma.service.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, teams: true },
|
include: { destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const gitSources = await prisma.gitSource.findMany({
|
const gitSources = await prisma.gitSource.findMany({
|
||||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
where: {
|
||||||
include: { teams: true },
|
OR: [
|
||||||
|
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
{ isSystemWide: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
const destinations = await prisma.destinationDocker.findMany({
|
const destinations = await prisma.destinationDocker.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
|
|
||||||
let foundUnconfiguredApplication = false;
|
let foundUnconfiguredApplication = false;
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
|
if (
|
||||||
foundUnconfiguredApplication = true
|
((!application.buildPack || !application.branch) && !application.simpleDockerfile) ||
|
||||||
|
!application.destinationDockerId ||
|
||||||
|
(!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose')
|
||||||
|
) {
|
||||||
|
foundUnconfiguredApplication = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let foundUnconfiguredService = false;
|
let foundUnconfiguredService = false;
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
if (!service.fqdn) {
|
if (!service.fqdn) {
|
||||||
foundUnconfiguredService = true
|
foundUnconfiguredService = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let foundUnconfiguredDatabase = false;
|
let foundUnconfiguredDatabase = false;
|
||||||
for (const database of databases) {
|
for (const database of databases) {
|
||||||
if (!database.version) {
|
if (!database.version) {
|
||||||
foundUnconfiguredDatabase = true
|
foundUnconfiguredDatabase = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -240,101 +275,94 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
services,
|
services,
|
||||||
gitSources,
|
gitSources,
|
||||||
destinations,
|
destinations,
|
||||||
settings,
|
settings
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(
|
export async function login(request: FastifyRequest<Login>, reply: FastifyReply) {
|
||||||
request: FastifyRequest<Login>,
|
|
||||||
reply: FastifyReply
|
|
||||||
) {
|
|
||||||
if (request.user) {
|
if (request.user) {
|
||||||
return reply.redirect("/dashboard");
|
return reply.redirect('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
const { email, password, isLogin } = request.body || {};
|
const { email, password, isLogin } = request.body || {};
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
throw { status: 500, message: "Email and password are required." };
|
throw { status: 500, message: 'Email and password are required.' };
|
||||||
}
|
}
|
||||||
const users = await prisma.user.count();
|
const users = await prisma.user.count();
|
||||||
const userFound = await prisma.user.findUnique({
|
const userFound = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
include: { teams: true, permission: true },
|
include: { teams: true, permission: true },
|
||||||
rejectOnNotFound: false,
|
rejectOnNotFound: false
|
||||||
});
|
});
|
||||||
if (!userFound && isLogin) {
|
if (!userFound && isLogin) {
|
||||||
throw { status: 500, message: "User not found." };
|
throw { status: 500, message: 'User not found.' };
|
||||||
}
|
}
|
||||||
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
||||||
let uid = cuid();
|
let uid = cuid();
|
||||||
let permission = "read";
|
let permission = 'read';
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
|
||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled: false },
|
data: { isRegistrationEnabled: false }
|
||||||
});
|
});
|
||||||
uid = "0";
|
uid = '0';
|
||||||
}
|
}
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
if (userFound.type === "email") {
|
if (userFound.type === 'email') {
|
||||||
if (userFound.password === "RESETME") {
|
if (userFound.password === 'RESETME') {
|
||||||
const hashedPassword = await hashPassword(password);
|
const hashedPassword = await hashPassword(password);
|
||||||
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||||
if (userFound.id === "0") {
|
if (userFound.id === '0') {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: "RESETME" },
|
data: { password: 'RESETME' }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: "RESETTIMEOUT" },
|
data: { password: 'RESETTIMEOUT' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message:
|
message: 'Password reset link has expired. Please request a new one.'
|
||||||
"Password reset link has expired. Please request a new one.",
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: hashedPassword },
|
data: { password: hashedPassword }
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
userId: userFound.id,
|
userId: userFound.id,
|
||||||
teamId: userFound.id,
|
teamId: userFound.id,
|
||||||
permission: userFound.permission,
|
permission: userFound.permission,
|
||||||
isAdmin: true,
|
isAdmin: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordMatch = await bcrypt.compare(
|
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||||
password,
|
|
||||||
userFound.password
|
|
||||||
);
|
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message: "Wrong password or email address.",
|
message: 'Wrong password or email address.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
uid = userFound.id;
|
uid = userFound.id;
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
permission = "owner";
|
permission = 'owner';
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
if (!isRegistrationEnabled) {
|
if (!isRegistrationEnabled) {
|
||||||
throw {
|
throw {
|
||||||
status: 404,
|
status: 404,
|
||||||
message: "Registration disabled by administrator.",
|
message: 'Registration disabled by administrator.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const hashedPassword = await hashPassword(password);
|
const hashedPassword = await hashPassword(password);
|
||||||
@@ -344,17 +372,17 @@ export async function login(
|
|||||||
id: uid,
|
id: uid,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
type: "email",
|
type: 'email',
|
||||||
teams: {
|
teams: {
|
||||||
create: {
|
create: {
|
||||||
id: uid,
|
id: uid,
|
||||||
name: uniqueName(),
|
name: uniqueName(),
|
||||||
destinationDocker: { connect: { network: "coolify" } },
|
destinationDocker: { connect: { network: 'coolify' } }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
permission: { create: { teamId: uid, permission: "owner" } },
|
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||||
},
|
},
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
@@ -362,16 +390,16 @@ export async function login(
|
|||||||
id: uid,
|
id: uid,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
type: "email",
|
type: 'email',
|
||||||
teams: {
|
teams: {
|
||||||
create: {
|
create: {
|
||||||
id: uid,
|
id: uid,
|
||||||
name: uniqueName(),
|
name: uniqueName()
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
permission: { create: { teamId: uid, permission: "owner" } },
|
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||||
},
|
},
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,23 +407,20 @@ export async function login(
|
|||||||
userId: uid,
|
userId: uid,
|
||||||
teamId: uid,
|
teamId: uid,
|
||||||
permission,
|
permission,
|
||||||
isAdmin,
|
isAdmin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCurrentUser(
|
export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
|
||||||
request: FastifyRequest<GetCurrentUser>,
|
|
||||||
fastify
|
|
||||||
) {
|
|
||||||
let token = null;
|
let token = null;
|
||||||
const { teamId } = request.query;
|
const { teamId } = request.query;
|
||||||
try {
|
try {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: request.user.userId },
|
where: { id: request.user.userId }
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw "User not found";
|
throw 'User not found';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 401, message: error };
|
throw { status: 401, message: error };
|
||||||
@@ -404,17 +429,15 @@ export async function getCurrentUser(
|
|||||||
try {
|
try {
|
||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
||||||
include: { teams: true, permission: true },
|
include: { teams: true, permission: true }
|
||||||
});
|
});
|
||||||
if (user) {
|
if (user) {
|
||||||
const permission = user.permission.find(
|
const permission = user.permission.find((p) => p.teamId === teamId).permission;
|
||||||
(p) => p.teamId === teamId
|
|
||||||
).permission;
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...request.user,
|
...request.user,
|
||||||
teamId,
|
teamId,
|
||||||
permission: permission || null,
|
permission: permission || null,
|
||||||
isAdmin: permission === "owner" || permission === "admin",
|
isAdmin: permission === 'owner' || permission === 'admin'
|
||||||
};
|
};
|
||||||
token = fastify.jwt.sign(payload);
|
token = fastify.jwt.sign(payload);
|
||||||
}
|
}
|
||||||
@@ -422,12 +445,13 @@ export async function getCurrentUser(
|
|||||||
// No new token -> not switching teams
|
// No new token -> not switching teams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.findUnique({ where: { id: "0" } }),
|
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||||
sentryDSN,
|
|
||||||
pendingInvitations,
|
pendingInvitations,
|
||||||
token,
|
token,
|
||||||
...request.user,
|
...request.user
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,235 +1,312 @@
|
|||||||
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 { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
|
import {
|
||||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
checkDomainsIsValidInDNS,
|
||||||
|
decrypt,
|
||||||
|
encrypt,
|
||||||
|
errorHandler,
|
||||||
|
executeCommand,
|
||||||
|
getDomain,
|
||||||
|
isDev,
|
||||||
|
isDNSValid,
|
||||||
|
isDomainConfigured,
|
||||||
|
listSettings,
|
||||||
|
prisma
|
||||||
|
} from '../../../../lib/common';
|
||||||
|
import {
|
||||||
|
AddDefaultRegistry,
|
||||||
|
CheckDNS,
|
||||||
|
CheckDomain,
|
||||||
|
DeleteDomain,
|
||||||
|
OnlyIdInBody,
|
||||||
|
SaveSettings,
|
||||||
|
SaveSSHKey,
|
||||||
|
SetDefaultRegistry
|
||||||
|
} from './types';
|
||||||
|
|
||||||
export async function listAllSettings(request: FastifyRequest) {
|
export async function listAllSettings(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
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 } } })
|
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } });
|
||||||
registries = registries.map((registry) => {
|
registries = registries.map((registry) => {
|
||||||
if (registry.password) {
|
if (registry.password) {
|
||||||
registry.password = decrypt(registry.password)
|
registry.password = decrypt(registry.password);
|
||||||
}
|
}
|
||||||
return registry
|
return registry;
|
||||||
})
|
});
|
||||||
const unencryptedKeys = []
|
const unencryptedKeys = [];
|
||||||
if (sshKeys.length > 0) {
|
if (sshKeys.length > 0) {
|
||||||
for (const key of sshKeys) {
|
for (const key of sshKeys) {
|
||||||
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
|
unencryptedKeys.push({
|
||||||
}
|
id: key.id,
|
||||||
}
|
name: key.name,
|
||||||
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } })
|
privateKey: decrypt(key.privateKey),
|
||||||
let cns = [];
|
createdAt: key.createdAt
|
||||||
for (const certificate of certificates) {
|
});
|
||||||
const x509 = new X509Certificate(certificate.cert);
|
}
|
||||||
cns.push({ commonName: x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''), id: certificate.id, createdAt: certificate.createdAt })
|
}
|
||||||
}
|
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } });
|
||||||
|
let cns = [];
|
||||||
|
for (const certificate of certificates) {
|
||||||
|
const x509 = new X509Certificate(certificate.cert);
|
||||||
|
cns.push({
|
||||||
|
commonName: x509.subject
|
||||||
|
.split('\n')
|
||||||
|
.find((s) => s.startsWith('CN='))
|
||||||
|
.replace('CN=', ''),
|
||||||
|
id: certificate.id,
|
||||||
|
createdAt: certificate.createdAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
settings,
|
settings,
|
||||||
certificates: cns,
|
certificates: cns,
|
||||||
sshKeys: unencryptedKeys,
|
sshKeys: unencryptedKeys,
|
||||||
registries
|
registries
|
||||||
}
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
let {
|
let {
|
||||||
previewSeparator,
|
previewSeparator,
|
||||||
numberOfDockerImagesKeptLocally,
|
numberOfDockerImagesKeptLocally,
|
||||||
doNotTrack,
|
doNotTrack,
|
||||||
fqdn,
|
fqdn,
|
||||||
isAPIDebuggingEnabled,
|
isAPIDebuggingEnabled,
|
||||||
isRegistrationEnabled,
|
isRegistrationEnabled,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
minPort,
|
minPort,
|
||||||
maxPort,
|
maxPort,
|
||||||
isAutoUpdateEnabled,
|
isAutoUpdateEnabled,
|
||||||
isDNSCheckEnabled,
|
isDNSCheckEnabled,
|
||||||
DNSServers,
|
DNSServers,
|
||||||
proxyDefaultRedirect
|
proxyDefaultRedirect
|
||||||
} = request.body
|
} = request.body;
|
||||||
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||||
if (numberOfDockerImagesKeptLocally) {
|
if (numberOfDockerImagesKeptLocally) {
|
||||||
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally)
|
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally);
|
||||||
}
|
}
|
||||||
if (previewSeparator == '') {
|
if (previewSeparator == '') {
|
||||||
previewSeparator = '.'
|
previewSeparator = '.';
|
||||||
}
|
}
|
||||||
if (SetPreviewSeparator != previewSeparator) {
|
if (SetPreviewSeparator != previewSeparator) {
|
||||||
const applications = await prisma.application.findMany({ where: { previewApplication: { some: { id: { not: undefined } } } }, include: { previewApplication: true } })
|
const applications = await prisma.application.findMany({
|
||||||
for (const application of applications) {
|
where: { previewApplication: { some: { id: { not: undefined } } } },
|
||||||
for (const preview of application.previewApplication) {
|
include: { previewApplication: true }
|
||||||
const { protocol } = new URL(preview.customDomain)
|
});
|
||||||
const { pullmergeRequestId } = preview
|
for (const application of applications) {
|
||||||
const { fqdn } = application
|
for (const preview of application.previewApplication) {
|
||||||
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}`
|
const { protocol } = new URL(preview.customDomain);
|
||||||
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } })
|
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: { previewSeparator, numberOfDockerImagesKeptLocally, doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
data: {
|
||||||
});
|
previewSeparator,
|
||||||
if (fqdn) {
|
numberOfDockerImagesKeptLocally,
|
||||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
doNotTrack,
|
||||||
}
|
isRegistrationEnabled,
|
||||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
dualCerts,
|
||||||
if (minPort && maxPort) {
|
isAutoUpdateEnabled,
|
||||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
isDNSCheckEnabled,
|
||||||
}
|
DNSServers,
|
||||||
if (doNotTrack === false) {
|
isAPIDebuggingEnabled
|
||||||
// Sentry.init({
|
}
|
||||||
// dsn: sentryDSN,
|
});
|
||||||
// environment: isDev ? 'development' : 'production',
|
if (fqdn) {
|
||||||
// release: version
|
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||||
// });
|
}
|
||||||
// console.log('Sentry initialized')
|
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||||
}
|
if (minPort && maxPort) {
|
||||||
return reply.code(201).send()
|
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||||
} catch ({ status, message }) {
|
}
|
||||||
return errorHandler({ status, message })
|
if (doNotTrack === false) {
|
||||||
}
|
// Sentry.init({
|
||||||
|
// dsn: sentryDSN,
|
||||||
|
// environment: isDev ? 'development' : 'production',
|
||||||
|
// release: version
|
||||||
|
// });
|
||||||
|
// console.log('Sentry initialized')
|
||||||
|
}
|
||||||
|
return reply.code(201).send();
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { fqdn } = request.body
|
const { fqdn } = request.body;
|
||||||
const { DNSServers } = await listSettings();
|
const { DNSServers } = await listSettings();
|
||||||
if (DNSServers) {
|
if (DNSServers) {
|
||||||
dns.setServers([...DNSServers.split(',')]);
|
dns.setServers([...DNSServers.split(',')]);
|
||||||
}
|
}
|
||||||
let ip;
|
let ip;
|
||||||
try {
|
try {
|
||||||
ip = await dns.resolve(fqdn);
|
ip = await dns.resolve(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do not care.
|
// Do not care.
|
||||||
}
|
}
|
||||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined)
|
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined);
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body
|
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body;
|
||||||
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 { message: "Domain already configured" };
|
throw { message: 'Domain already configured' };
|
||||||
}
|
}
|
||||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
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 });
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||||
try {
|
try {
|
||||||
const { domain } = request.params;
|
const { domain } = request.params;
|
||||||
await isDNSValid(request.hostname, domain);
|
await isDNSValid(request.hostname, domain);
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
|
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { privateKey, name } = request.body;
|
const { privateKey, name } = request.body;
|
||||||
const found = await prisma.sshKey.findMany({ where: { name } })
|
const found = await prisma.sshKey.findMany({ where: { name } });
|
||||||
if (found.length > 0) {
|
if (found.length > 0) {
|
||||||
throw {
|
throw {
|
||||||
message: "Name already used. Choose another one please."
|
message: 'Name already used. Choose another one please.'
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
const encryptedSSHKey = encrypt(privateKey)
|
const encryptedSSHKey = encrypt(privateKey);
|
||||||
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
|
await prisma.sshKey.create({
|
||||||
return reply.code(201).send()
|
data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
|
||||||
} catch ({ status, message }) {
|
});
|
||||||
return errorHandler({ status, message })
|
return reply.code(201).send();
|
||||||
}
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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 teamId = request.user.teamId;
|
||||||
const { id } = request.body;
|
const { id } = request.body;
|
||||||
await prisma.sshKey.deleteMany({ where: { id, teamId } })
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteCertificates(
|
||||||
try {
|
request: FastifyRequest<OnlyIdInBody>,
|
||||||
const teamId = request.user.teamId;
|
reply: FastifyReply
|
||||||
const { id } = request.body;
|
) {
|
||||||
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 })
|
try {
|
||||||
await prisma.certificate.deleteMany({ where: { id, teamId } })
|
const teamId = request.user.teamId;
|
||||||
return reply.code(201).send()
|
const { id } = request.body;
|
||||||
} catch ({ status, message }) {
|
await executeCommand({
|
||||||
return errorHandler({ status, message })
|
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.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) {
|
export async function setDockerRegistry(
|
||||||
try {
|
request: FastifyRequest<SetDefaultRegistry>,
|
||||||
const teamId = request.user.teamId;
|
reply: FastifyReply
|
||||||
const { id, username, password } = request.body;
|
) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id, username, password } = request.body;
|
||||||
|
|
||||||
let encryptedPassword = ''
|
let encryptedPassword = '';
|
||||||
if (password) encryptedPassword = encrypt(password)
|
if (password) encryptedPassword = encrypt(password);
|
||||||
|
|
||||||
if (teamId === '0') {
|
if (teamId === '0') {
|
||||||
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } })
|
await prisma.dockerRegistry.update({
|
||||||
} else {
|
where: { id },
|
||||||
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } })
|
data: { username, password: encryptedPassword }
|
||||||
}
|
});
|
||||||
return reply.code(201).send()
|
} else {
|
||||||
} catch ({ status, message }) {
|
await prisma.dockerRegistry.updateMany({
|
||||||
return errorHandler({ status, message })
|
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) {
|
export async function addDockerRegistry(
|
||||||
try {
|
request: FastifyRequest<AddDefaultRegistry>,
|
||||||
const teamId = request.user.teamId;
|
reply: FastifyReply
|
||||||
const { name, url, username, password } = request.body;
|
) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { name, url, username, password } = request.body;
|
||||||
|
|
||||||
let encryptedPassword = ''
|
let encryptedPassword = '';
|
||||||
if (password) encryptedPassword = encrypt(password)
|
if (password) encryptedPassword = encrypt(password);
|
||||||
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
|
await prisma.dockerRegistry.create({
|
||||||
|
data: { name, url, username, password: encryptedPassword, team: { connect: { 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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
export async function deleteDockerRegistry(
|
||||||
try {
|
request: FastifyRequest<OnlyIdInBody>,
|
||||||
const teamId = request.user.teamId;
|
reply: FastifyReply
|
||||||
const { id } = request.body;
|
) {
|
||||||
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
|
try {
|
||||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
const teamId = request.user.teamId;
|
||||||
return reply.code(201).send()
|
const { id } = request.body;
|
||||||
} catch ({ status, message }) {
|
await prisma.application.updateMany({
|
||||||
return errorHandler({ status, message })
|
where: { dockerRegistryId: id },
|
||||||
}
|
data: { dockerRegistryId: null }
|
||||||
|
});
|
||||||
|
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
|
||||||
|
return reply.code(201).send();
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,190 +1,231 @@
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import type { FastifyRequest } from 'fastify';
|
import type { FastifyRequest } from 'fastify';
|
||||||
import { FastifyReply } from 'fastify';
|
|
||||||
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
|
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||||
import { OnlyId } from '../../../../types';
|
import { OnlyId } from '../../../../types';
|
||||||
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
|
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
|
||||||
|
|
||||||
export async function listSources(request: FastifyRequest) {
|
export async function listSources(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user?.teamId;
|
const teamId = request.user?.teamId;
|
||||||
const sources = await prisma.gitSource.findMany({
|
const sources = await prisma.gitSource.findMany({
|
||||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
where: {
|
||||||
include: { teams: true, githubApp: true, gitlabApp: true }
|
OR: [
|
||||||
});
|
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
return {
|
{ isSystemWide: true }
|
||||||
sources
|
]
|
||||||
}
|
},
|
||||||
} catch ({ status, message }) {
|
include: { teams: true, githubApp: true, gitlabApp: true }
|
||||||
return errorHandler({ status, message })
|
});
|
||||||
}
|
return {
|
||||||
|
sources
|
||||||
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function saveSource(request, reply) {
|
export async function saveSource(request, reply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
|
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
|
||||||
if (customPort) customPort = Number(customPort)
|
if (customPort) customPort = Number(customPort);
|
||||||
await prisma.gitSource.update({
|
await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
|
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||||
});
|
});
|
||||||
return reply.code(201).send()
|
return reply.code(201).send();
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function getSource(request: FastifyRequest<OnlyId>) {
|
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 (id === 'new') {
|
if (id === 'new') {
|
||||||
return {
|
return {
|
||||||
source: {
|
source: {
|
||||||
name: null,
|
name: null,
|
||||||
type: null,
|
type: null,
|
||||||
htmlUrl: null,
|
htmlUrl: null,
|
||||||
apiUrl: null,
|
apiUrl: null,
|
||||||
organization: null,
|
organization: null,
|
||||||
customPort: 22,
|
customPort: 22,
|
||||||
},
|
customUser: 'git'
|
||||||
settings
|
},
|
||||||
}
|
settings
|
||||||
}
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const source = await prisma.gitSource.findFirst({
|
const source = await prisma.gitSource.findFirst({
|
||||||
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
where: {
|
||||||
include: { githubApp: true, gitlabApp: true }
|
id,
|
||||||
});
|
OR: [
|
||||||
if (!source) {
|
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
throw { status: 404, message: 'Source not found.' }
|
{ isSystemWide: true }
|
||||||
}
|
]
|
||||||
|
},
|
||||||
|
include: { githubApp: true, gitlabApp: true }
|
||||||
|
});
|
||||||
|
if (!source) {
|
||||||
|
throw { status: 404, message: 'Source not found.' };
|
||||||
|
}
|
||||||
|
|
||||||
if (source?.githubApp?.clientSecret)
|
if (source?.githubApp?.clientSecret)
|
||||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||||
if (source?.githubApp?.webhookSecret)
|
if (source?.githubApp?.webhookSecret)
|
||||||
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
||||||
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
if (source?.githubApp?.privateKey)
|
||||||
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
||||||
|
if (source?.gitlabApp?.appSecret)
|
||||||
|
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
source,
|
source,
|
||||||
settings
|
settings
|
||||||
};
|
};
|
||||||
|
} catch ({ status, message }) {
|
||||||
} catch ({ status, message }) {
|
return errorHandler({ status, message });
|
||||||
return errorHandler({ status, message })
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteSource(request) {
|
export async function deleteSource(request) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
const source = await prisma.gitSource.delete({
|
const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
|
||||||
where: { id },
|
if (gitAppFound) {
|
||||||
include: { githubApp: true, gitlabApp: true }
|
throw {
|
||||||
});
|
status: 400,
|
||||||
if (source.githubAppId) {
|
message: 'This source is used by an application. Please remove the application first.'
|
||||||
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
|
};
|
||||||
}
|
}
|
||||||
if (source.gitlabAppId) {
|
const source = await prisma.gitSource.delete({
|
||||||
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
where: { id },
|
||||||
}
|
include: { githubApp: true, gitlabApp: true }
|
||||||
return {}
|
});
|
||||||
} catch ({ status, message }) {
|
if (source.githubAppId) {
|
||||||
return errorHandler({ status, message })
|
await prisma.githubApp.delete({ where: { id: source.githubAppId } });
|
||||||
}
|
}
|
||||||
|
if (source.gitlabAppId) {
|
||||||
|
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
|
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
|
||||||
try {
|
try {
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user;
|
||||||
|
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body
|
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
|
||||||
|
|
||||||
if (customPort) customPort = Number(customPort)
|
if (customPort) customPort = Number(customPort);
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
const newId = cuid()
|
const newId = cuid();
|
||||||
await prisma.gitSource.create({
|
await prisma.gitSource.create({
|
||||||
data: {
|
data: {
|
||||||
id: newId,
|
id: newId,
|
||||||
name,
|
name,
|
||||||
htmlUrl,
|
htmlUrl,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
organization,
|
organization,
|
||||||
customPort,
|
customPort,
|
||||||
isSystemWide,
|
isSystemWide,
|
||||||
type: 'github',
|
type: 'github',
|
||||||
teams: { connect: { id: teamId } }
|
teams: { connect: { id: teamId } }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
id: newId
|
id: newId
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Wrong request.' }
|
throw { status: 500, message: 'Wrong request.' };
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
|
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params;
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user;
|
||||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
let {
|
||||||
request.body
|
type,
|
||||||
|
name,
|
||||||
|
htmlUrl,
|
||||||
|
apiUrl,
|
||||||
|
oauthId,
|
||||||
|
appId,
|
||||||
|
appSecret,
|
||||||
|
groupName,
|
||||||
|
customPort,
|
||||||
|
customUser
|
||||||
|
} = request.body;
|
||||||
|
|
||||||
if (oauthId) oauthId = Number(oauthId);
|
if (oauthId) oauthId = Number(oauthId);
|
||||||
if (customPort) customPort = Number(customPort)
|
if (customPort) customPort = Number(customPort);
|
||||||
const encryptedAppSecret = encrypt(appSecret);
|
const encryptedAppSecret = encrypt(appSecret);
|
||||||
|
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
const newId = cuid()
|
const newId = cuid();
|
||||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
await prisma.gitSource.create({
|
||||||
await prisma.gitlabApp.create({
|
data: {
|
||||||
data: {
|
id: newId,
|
||||||
teams: { connect: { id: teamId } },
|
type,
|
||||||
appId,
|
apiUrl,
|
||||||
oauthId,
|
htmlUrl,
|
||||||
groupName,
|
name,
|
||||||
appSecret: encryptedAppSecret,
|
customPort,
|
||||||
gitSource: { connect: { id: newId } }
|
customUser,
|
||||||
}
|
teams: { connect: { id: teamId } }
|
||||||
});
|
}
|
||||||
return {
|
});
|
||||||
status: 201,
|
await prisma.gitlabApp.create({
|
||||||
id: newId
|
data: {
|
||||||
}
|
teams: { connect: { id: teamId } },
|
||||||
} else {
|
appId,
|
||||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
|
oauthId,
|
||||||
await prisma.gitlabApp.update({
|
groupName,
|
||||||
where: { id },
|
appSecret: encryptedAppSecret,
|
||||||
data: {
|
gitSource: { connect: { id: newId } }
|
||||||
appId,
|
}
|
||||||
oauthId,
|
});
|
||||||
groupName,
|
return {
|
||||||
appSecret: encryptedAppSecret,
|
status: 201,
|
||||||
}
|
id: newId
|
||||||
});
|
};
|
||||||
}
|
} else {
|
||||||
return { status: 201 };
|
await prisma.gitSource.update({
|
||||||
|
where: { id },
|
||||||
} catch ({ status, message }) {
|
data: { type, apiUrl, htmlUrl, name, customPort, customUser }
|
||||||
return errorHandler({ status, message })
|
});
|
||||||
}
|
await prisma.gitlabApp.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
appId,
|
||||||
|
oauthId,
|
||||||
|
groupName,
|
||||||
|
appSecret: encryptedAppSecret
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { status: 201 };
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
|
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
|
||||||
try {
|
try {
|
||||||
const { oauthId } = request.body
|
const { oauthId } = request.body;
|
||||||
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw { status: 500, message: 'OAuthID already configured in Coolify.' }
|
throw { status: 500, message: 'OAuthID already configured in Coolify.' };
|
||||||
}
|
}
|
||||||
return {}
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
|
|||||||
appSecret: string,
|
appSecret: string,
|
||||||
groupName: string,
|
groupName: string,
|
||||||
customPort: number,
|
customPort: number,
|
||||||
|
customUser: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface CheckGitLabOAuthId extends OnlyId {
|
export interface CheckGitLabOAuthId extends OnlyId {
|
||||||
|
|||||||
@@ -1,9 +1,33 @@
|
|||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from 'fastify';
|
||||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common";
|
import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
|
||||||
import { getTemplates } from "../../../lib/services";
|
import { getTemplates } from '../../../lib/services';
|
||||||
import { OnlyId } from "../../../types";
|
import { OnlyId } from '../../../types';
|
||||||
|
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||||
|
import { hashPassword } from '../../api/v1/handlers';
|
||||||
|
|
||||||
function generateServices(serviceId, containerId, port) {
|
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||||
|
if (isHttp2) {
|
||||||
|
return {
|
||||||
|
[serviceId]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`${serviceId}-http2`]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `h2c://${containerId}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
[serviceId]: {
|
[serviceId]: {
|
||||||
loadbalancer: {
|
loadbalancer: {
|
||||||
@@ -14,43 +38,59 @@ function generateServices(serviceId, containerId, port) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) {
|
async function generateRouters({
|
||||||
let http: any = {
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
isHttp2 = false,
|
||||||
|
httpBasicAuth = null,
|
||||||
|
}) {
|
||||||
|
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||||
|
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||||
|
}`;
|
||||||
|
|
||||||
|
|
||||||
|
const http: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let https: any = {
|
const https: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
tls: {
|
tls: {
|
||||||
certresolver: 'letsencrypt'
|
certresolver: 'letsencrypt'
|
||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let httpWWW: any = {
|
const httpWWW: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let httpsWWW: any = {
|
const httpsWWW: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
tls: {
|
tls: {
|
||||||
certresolver: 'letsencrypt'
|
certresolver: 'letsencrypt'
|
||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
// 2. http + non-www only
|
// 2. http + non-www only
|
||||||
if (!isHttps && !isWWW) {
|
if (!isHttps && !isWWW) {
|
||||||
https.middlewares.push('redirect-to-http');
|
https.middlewares.push('redirect-to-http');
|
||||||
@@ -58,8 +98,12 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
|
|
||||||
httpWWW.middlewares.push('redirect-to-non-www');
|
httpWWW.middlewares.push('redirect-to-non-www');
|
||||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||||
delete https.tls
|
delete https.tls;
|
||||||
delete httpsWWW.tls
|
delete httpsWWW.tls;
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. http + www only
|
// 3. http + www only
|
||||||
@@ -69,8 +113,12 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
|
|
||||||
http.middlewares.push('redirect-to-www');
|
http.middlewares.push('redirect-to-www');
|
||||||
https.middlewares.push('redirect-to-www');
|
https.middlewares.push('redirect-to-www');
|
||||||
delete https.tls
|
delete https.tls;
|
||||||
delete httpsWWW.tls
|
delete httpsWWW.tls;
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 5. https + non-www only
|
// 5. https + non-www only
|
||||||
if (isHttps && !isWWW) {
|
if (isHttps && !isWWW) {
|
||||||
@@ -86,19 +134,23 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
} else {
|
} else {
|
||||||
https.tls = true;
|
https.tls = true;
|
||||||
delete httpsWWW.tls.certresolver
|
delete httpsWWW.tls.certresolver;
|
||||||
httpsWWW.tls.domains = {
|
httpsWWW.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isDualCerts) {
|
if (!isDualCerts) {
|
||||||
delete httpsWWW.tls.certresolver
|
delete httpsWWW.tls.certresolver;
|
||||||
httpsWWW.tls.domains = {
|
httpsWWW.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 6. https + www only
|
// 6. https + www only
|
||||||
if (isHttps && isWWW) {
|
if (isHttps && isWWW) {
|
||||||
@@ -108,34 +160,75 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
http.middlewares.push('redirect-to-www');
|
http.middlewares.push('redirect-to-www');
|
||||||
https.middlewares.push('redirect-to-www');
|
https.middlewares.push('redirect-to-www');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
|
|
||||||
if (isCustomSSL) {
|
if (isCustomSSL) {
|
||||||
if (isDualCerts) {
|
if (isDualCerts) {
|
||||||
https.tls = true;
|
https.tls = true;
|
||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
} else {
|
} else {
|
||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
delete https.tls.certresolver
|
delete https.tls.certresolver;
|
||||||
https.tls.domains = {
|
https.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isDualCerts) {
|
if (!isDualCerts) {
|
||||||
delete https.tls.certresolver
|
delete https.tls.certresolver;
|
||||||
https.tls.domains = {
|
https.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
if (isHttp2) {
|
||||||
|
const http2 = {
|
||||||
|
...http,
|
||||||
|
service: `${serviceId}-http2`,
|
||||||
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
|
};
|
||||||
|
const http2WWW = {
|
||||||
|
...httpWWW,
|
||||||
|
service: `${serviceId}-http2`,
|
||||||
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
|
};
|
||||||
|
const https2 = {
|
||||||
|
...https,
|
||||||
|
service: `${serviceId}-http2`,
|
||||||
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
|
};
|
||||||
|
|
||||||
|
const https2WWW = {
|
||||||
|
...httpsWWW,
|
||||||
|
service: `${serviceId}-http2`,
|
||||||
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||||
|
[`${serviceId}-${pathPrefix}-http2`]: { ...http2 },
|
||||||
|
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||||
|
[`${serviceId}-${pathPrefix}-secure-http2`]: { ...https2 },
|
||||||
|
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||||
|
[`${serviceId}-${pathPrefix}-www-http2`]: { ...http2WWW },
|
||||||
|
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
||||||
|
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||||
}
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
|
||||||
const traefik = {
|
const traefik = {
|
||||||
tls: {
|
tls: {
|
||||||
certificates: []
|
certificates: []
|
||||||
@@ -174,26 +267,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const coolifySettings = await prisma.setting.findFirst();
|
const coolifySettings = await prisma.setting.findFirst();
|
||||||
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
||||||
traefik.http.routers['catchall-http'] = {
|
traefik.http.routers['catchall-http'] = {
|
||||||
entrypoints: ["web"],
|
entrypoints: ['web'],
|
||||||
rule: "HostRegexp(`{catchall:.*}`)",
|
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||||
service: "noop",
|
service: 'noop',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
middlewares: ["redirect-regexp"]
|
middlewares: ['redirect-regexp']
|
||||||
}
|
};
|
||||||
traefik.http.routers['catchall-https'] = {
|
traefik.http.routers['catchall-https'] = {
|
||||||
entrypoints: ["websecure"],
|
entrypoints: ['websecure'],
|
||||||
rule: "HostRegexp(`{catchall:.*}`)",
|
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||||
service: "noop",
|
service: 'noop',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
middlewares: ["redirect-regexp"]
|
middlewares: ['redirect-regexp']
|
||||||
}
|
};
|
||||||
traefik.http.middlewares['redirect-regexp'] = {
|
traefik.http.middlewares['redirect-regexp'] = {
|
||||||
redirectregex: {
|
redirectregex: {
|
||||||
regex: '(.*)',
|
regex: '(.*)',
|
||||||
replacement: coolifySettings.proxyDefaultRedirect,
|
replacement: coolifySettings.proxyDefaultRedirect,
|
||||||
permanent: false
|
permanent: false
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
traefik.http.services['noop'] = {
|
traefik.http.services['noop'] = {
|
||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
servers: [
|
||||||
@@ -202,25 +295,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
const sslpath = '/etc/traefik/acme/custom';
|
const sslpath = '/etc/traefik/acme/custom';
|
||||||
|
|
||||||
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
let certificates = await prisma.certificate.findMany({
|
||||||
|
where: {
|
||||||
|
team: {
|
||||||
|
applications: { some: { settings: { isCustomSSL: true } } },
|
||||||
|
destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (remote) {
|
if (remote) {
|
||||||
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
certificates = await prisma.certificate.findMany({
|
||||||
|
where: {
|
||||||
|
team: {
|
||||||
|
applications: { some: { settings: { isCustomSSL: true } } },
|
||||||
|
destinationDocker: {
|
||||||
|
some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedCertificates = []
|
const parsedCertificates = [];
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
parsedCertificates.push({
|
parsedCertificates.push({
|
||||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (parsedCertificates.length > 0) {
|
if (parsedCertificates.length > 0) {
|
||||||
traefik.tls.certificates = parsedCertificates
|
traefik.tls.certificates = parsedCertificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
let applications = [];
|
let applications = [];
|
||||||
@@ -236,7 +345,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
serviceSetting: true,
|
serviceSetting: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
@@ -251,23 +360,25 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
serviceSetting: true,
|
serviceSetting: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (applications.length > 0) {
|
if (applications.length > 0) {
|
||||||
const dockerIds = new Set()
|
const dockerIds = new Set();
|
||||||
const runningContainers = {}
|
const runningContainers = {};
|
||||||
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||||
for (const dockerId of dockerIds) {
|
for (const dockerId of dockerIds) {
|
||||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
const { stdout: container } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
if (container) {
|
if (container) {
|
||||||
const containersArray = container.trim().split('\n');
|
const containersArray = container.trim().split('\n');
|
||||||
if (containersArray.length > 0) {
|
if (containersArray.length > 0) {
|
||||||
runningContainers[dockerId] = containersArray
|
runningContainers[dockerId] = containersArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
destinationDocker,
|
destinationDocker,
|
||||||
destinationDockerId,
|
destinationDockerId,
|
||||||
settings
|
settings,
|
||||||
|
basicAuthUser,
|
||||||
|
basicAuthPw,
|
||||||
|
settings: { basicAuth: isBasicAuthEnabled }
|
||||||
} = application;
|
} = application;
|
||||||
if (!destinationDockerId) {
|
if (!destinationDockerId) {
|
||||||
continue;
|
continue;
|
||||||
@@ -289,38 +403,68 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
if (
|
if (
|
||||||
!runningContainers[destinationDockerId] ||
|
!runningContainers[destinationDockerId] ||
|
||||||
runningContainers[destinationDockerId].length === 0 ||
|
runningContainers[destinationDockerId].length === 0 ||
|
||||||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id)).length === 0
|
runningContainers[destinationDockerId].filter((container) => container.startsWith(id))
|
||||||
|
.length === 0
|
||||||
) {
|
) {
|
||||||
continue
|
continue;
|
||||||
|
}
|
||||||
|
let httpBasicAuth = null;
|
||||||
|
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
|
||||||
|
httpBasicAuth = {
|
||||||
|
basicAuth: {
|
||||||
|
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (buildPack === 'compose') {
|
if (buildPack === 'compose') {
|
||||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
const [key, value] = service
|
const [key, value] = service;
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
if (!value.fqdn || !value.port) {
|
if (!value.fqdn || !value.port) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { fqdn, port } = value
|
const { fqdn, port } = value;
|
||||||
const containerId = `${id}-${key}`
|
const containerId = `${id}-${key}`;
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false;
|
const isCustomSSL = false;
|
||||||
const dualCerts = false;
|
const dualCerts = false;
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
|
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
httpBasicAuth
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, containerId, port)
|
||||||
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { previews, dualCerts, isCustomSSL } = settings;
|
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||||
const { network, id: dockerId } = destinationDocker;
|
const { network, id: dockerId } = destinationDocker;
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
@@ -329,12 +473,37 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
isHttp2,
|
||||||
|
httpBasicAuth
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||||
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
if (previews) {
|
if (previews) {
|
||||||
const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
const { stdout } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||||
|
});
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
const containers = stdout
|
const containers = stdout
|
||||||
.trim()
|
.trim()
|
||||||
@@ -343,44 +512,63 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
.map((c) => c.replace(/"/g, ''));
|
.map((c) => c.replace(/"/g, ''));
|
||||||
if (containers.length > 0) {
|
if (containers.length > 0) {
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator}${domain}`;
|
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||||
|
}${domain}`;
|
||||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const serviceId = `${container}-${port || 'default'}`
|
const serviceId = `${container}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain: previewDomain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
isHttp2: false,
|
||||||
|
httpBasicAuth
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, container, port, isHttp2)
|
||||||
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
const dockerIds = new Set()
|
const dockerIds = new Set();
|
||||||
const runningContainers = {}
|
const runningContainers = {};
|
||||||
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||||
for (const dockerId of dockerIds) {
|
for (const dockerId of dockerIds) {
|
||||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
const { stdout: container } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
if (container) {
|
if (container) {
|
||||||
const containersArray = container.trim().split('\n');
|
const containersArray = container.trim().split('\n');
|
||||||
if (containersArray.length > 0) {
|
if (containersArray.length > 0) {
|
||||||
runningContainers[dockerId] = containersArray
|
runningContainers[dockerId] = containersArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
try {
|
try {
|
||||||
let {
|
let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
destinationDockerId,
|
|
||||||
dualCerts,
|
|
||||||
serviceSetting
|
|
||||||
} = service;
|
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -392,7 +580,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
runningContainers[destinationDockerId].length === 0 ||
|
runningContainers[destinationDockerId].length === 0 ||
|
||||||
!runningContainers[destinationDockerId].includes(id)
|
!runningContainers[destinationDockerId].includes(id)
|
||||||
) {
|
) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const templates = await getTemplates();
|
const templates = await getTemplates();
|
||||||
let found = templates.find((a) => a.type === type);
|
let found = templates.find((a) => a.type === type);
|
||||||
@@ -401,88 +589,147 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
}
|
}
|
||||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||||
for (const oneService of Object.keys(found.services)) {
|
for (const oneService of Object.keys(found.services)) {
|
||||||
const isDomainConfiguration = found?.services[oneService]?.proxy?.filter(p => p.domain) ?? [];
|
const isDomainAndProxyConfiguration =
|
||||||
if (isDomainConfiguration.length > 0) {
|
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||||
const { proxy } = found.services[oneService];
|
if (isDomainAndProxyConfiguration.length > 0) {
|
||||||
for (let configuration of proxy) {
|
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||||
|
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||||
|
for (const configuration of proxy) {
|
||||||
|
if (configuration.hostPort) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (configuration.domain) {
|
if (configuration.domain) {
|
||||||
const setting = serviceSetting.find((a) => a.variableName === configuration.domain);
|
const setting = serviceSetting.find(
|
||||||
|
(a) => a.variableName === configuration.domain
|
||||||
|
);
|
||||||
if (setting) {
|
if (setting) {
|
||||||
configuration.domain = configuration.domain.replace(configuration.domain, setting.value);
|
configuration.domain = configuration.domain.replace(
|
||||||
|
configuration.domain,
|
||||||
|
setting.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
const foundPortVariable = serviceSetting.find(
|
||||||
|
(a) => a.name.toLowerCase() === 'port'
|
||||||
|
);
|
||||||
if (foundPortVariable) {
|
if (foundPortVariable) {
|
||||||
configuration.port = foundPortVariable.value
|
configuration.port = foundPortVariable.value;
|
||||||
}
|
}
|
||||||
let port, pathPrefix, customDomain;
|
let port, pathPrefix, customDomain;
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
port = configuration?.port;
|
port = configuration?.port;
|
||||||
pathPrefix = configuration?.pathPrefix || '/';
|
pathPrefix = configuration?.pathPrefix || '/';
|
||||||
customDomain = configuration?.domain
|
customDomain = configuration?.domain;
|
||||||
}
|
}
|
||||||
if (customDomain) {
|
if (customDomain) {
|
||||||
fqdn = customDomain
|
fqdn = customDomain;
|
||||||
} else {
|
} else {
|
||||||
fqdn = service.fqdn
|
fqdn = service.fqdn;
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const isCustomSSL = false;
|
const isCustomSSL = false;
|
||||||
const serviceId = `${oneService}-${port || 'default'}`
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, oneService, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
||||||
for (let [index, port] of found.services[oneService].ports.entries()) {
|
for (let [index, port] of found.services[oneService].ports.entries()) {
|
||||||
if (port == 22) continue;
|
if (port == 22) continue;
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
const foundPortVariable = serviceSetting.find(
|
||||||
|
(a) => a.name.toLowerCase() === 'port'
|
||||||
|
);
|
||||||
if (foundPortVariable) {
|
if (foundPortVariable) {
|
||||||
port = foundPortVariable.value
|
port = foundPortVariable.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false
|
const isCustomSSL = false;
|
||||||
const serviceId = `${oneService}-${port || 'default'}`
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, id, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const id = isDev ? 'host.docker.internal' : 'coolify'
|
const id = isDev ? 'host.docker.internal' : 'coolify';
|
||||||
const container = isDev ? 'host.docker.internal' : 'coolify'
|
const container = isDev ? 'host.docker.internal' : 'coolify';
|
||||||
const port = 3000
|
const port = 3000;
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false
|
const isCustomSSL = false;
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
...traefik.http.routers,
|
||||||
|
...await generateRouters({
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
})
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, container, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
if (Object.keys(traefik.http.routers).length === 0) {
|
if (Object.keys(traefik.http.routers).length === 0) {
|
||||||
traefik.http.routers = null;
|
traefik.http.routers = null;
|
||||||
@@ -496,9 +743,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
|
|
||||||
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.query
|
const { id } = request.query;
|
||||||
if (id) {
|
if (id) {
|
||||||
const { privatePort, publicPort, type, address = id } = request.query
|
const { privatePort, publicPort, type, address = id } = request.query;
|
||||||
let traefik = {};
|
let traefik = {};
|
||||||
if (publicPort && type && privatePort) {
|
if (publicPort && type && privatePort) {
|
||||||
if (type === 'tcp') {
|
if (type === 'tcp') {
|
||||||
@@ -559,18 +806,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...traefik
|
...traefik
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
|||||||
import { OtherProxyConfiguration } from './types';
|
import { OtherProxyConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
fastify.get<OnlyId>('/main.json', async (request) => proxyConfiguration(request, false));
|
||||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
fastify.get<OtherProxyConfiguration>('/other.json', async (request) =>
|
||||||
|
otherProxyConfiguration(request)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
|||||||
1072
apps/api/tags.json
1072
apps/api/tags.json
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -42,8 +42,6 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/svelte": "7.21.1",
|
|
||||||
"@sentry/tracing": "7.21.1",
|
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.48",
|
"@sveltejs/adapter-static": "1.0.0-next.48",
|
||||||
"@tailwindcss/typography": "0.5.8",
|
"@tailwindcss/typography": "0.5.8",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import * as Sentry from '@sentry/svelte';
|
|
||||||
export async function handle({ event, resolve }) {
|
export async function handle({ event, resolve }) {
|
||||||
const response = await resolve(event, { ssr: false });
|
const response = await resolve(event, { ssr: false });
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
export const handleError = ({ error, event }) => {
|
export const handleError = ({ error, event }) => {
|
||||||
Sentry.captureException(error, { event });
|
return {
|
||||||
|
message: 'Whoops!',
|
||||||
return {
|
code: error?.code ?? 'UNKNOWN'
|
||||||
message: 'Whoops!',
|
};
|
||||||
code: error?.code ?? 'UNKNOWN'
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
|
import { dashify } from './common';
|
||||||
|
|
||||||
export function getAPIUrl() {
|
export function getAPIUrl() {
|
||||||
if (GITPOD_WORKSPACE_URL) {
|
if (GITPOD_WORKSPACE_URL) {
|
||||||
@@ -72,17 +73,19 @@ async function send({
|
|||||||
...headers
|
...headers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (token && !path.startsWith('https://')) {
|
|
||||||
|
if (token && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
opts.headers = {
|
opts.headers = {
|
||||||
...opts.headers,
|
...opts.headers,
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!path.startsWith('https://')) {
|
|
||||||
|
if (!path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
path = `/api/v1${path}`;
|
path = `/api/v1${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev && !path.startsWith('https://')) {
|
if (dev && !path.startsWith('https://') && !path.startsWith('http://')) {
|
||||||
path = `${getAPIUrl()}${path}`;
|
path = `${getAPIUrl()}${path}`;
|
||||||
}
|
}
|
||||||
if (method === 'POST' && data && !opts.body) {
|
if (method === 'POST' && data && !opts.body) {
|
||||||
@@ -100,6 +103,14 @@ async function send({
|
|||||||
responseData = await response.json();
|
responseData = await response.json();
|
||||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
} else if (contentType?.indexOf('text/plain') !== -1) {
|
||||||
responseData = await response.text();
|
responseData = await response.text();
|
||||||
|
} else if (contentType?.indexOf('application/octet-stream') !== -1) {
|
||||||
|
responseData = await response.blob();
|
||||||
|
const fileName = dashify(data.id + '-' + data.name)
|
||||||
|
const fileLink = document.createElement('a');
|
||||||
|
fileLink.href = URL.createObjectURL(new Blob([responseData]))
|
||||||
|
fileLink.download = fileName + '.gz';
|
||||||
|
fileLink.click();
|
||||||
|
fileLink.remove();
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
51
apps/ui/src/lib/components/CopyVolumeField.svelte
Normal file
51
apps/ui/src/lib/components/CopyVolumeField.svelte
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script>
|
||||||
|
import { addToast } from '$lib/store';
|
||||||
|
|
||||||
|
export let value = '';
|
||||||
|
let isHttps = window.location.protocol === 'https:';
|
||||||
|
|
||||||
|
function copyToClipboard() {
|
||||||
|
if (isHttps && navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
addToast({
|
||||||
|
message: 'Copied to clipboard.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full relative box">
|
||||||
|
<p class="text-white p-2">{value}</p>
|
||||||
|
<div
|
||||||
|
class="absolute top-0 right-0 flex justify-center items-center h-full cursor-pointer text-stone-600 mr-3"
|
||||||
|
>
|
||||||
|
{#if isHttps}
|
||||||
|
<div on:click={copyToClipboard}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-6 w-6"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="8" y="8" width="12" height="12" rx="2" />
|
||||||
|
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
position: relative;
|
||||||
|
border: 1px dashed #202020;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,23 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let type: string;
|
export let type: string;
|
||||||
export let isAbsolute = false;
|
export let isAbsolute = false;
|
||||||
|
let githubRawIconUrl =
|
||||||
|
'https://raw.githubusercontent.com/coollabsio/coolify-community-templates/main/services/icons';
|
||||||
|
|
||||||
let fallback = '/icons/default.png';
|
let fallback = '/icons/default.png';
|
||||||
const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback);
|
|
||||||
let extension = 'png';
|
const handleError = (ev: { target: { src: string } }) => {
|
||||||
let svgs = [
|
ev.target.src = fallback;
|
||||||
'pocketbase',
|
};
|
||||||
'gitea',
|
|
||||||
'languagetool',
|
|
||||||
'meilisearch',
|
|
||||||
'n8n',
|
|
||||||
'glitchtip',
|
|
||||||
'searxng',
|
|
||||||
'umami',
|
|
||||||
'uptimekuma',
|
|
||||||
'vaultwarden',
|
|
||||||
'weblate',
|
|
||||||
'wordpress'
|
|
||||||
];
|
|
||||||
|
|
||||||
const name: any =
|
const name: any =
|
||||||
type &&
|
type &&
|
||||||
@@ -27,32 +18,15 @@
|
|||||||
.split('-')[0]
|
.split('-')[0]
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
if (svgs.includes(name)) {
|
|
||||||
extension = 'svg';
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateClass() {
|
function generateClass() {
|
||||||
switch (name) {
|
return isAbsolute ? 'w-10 h-10 absolute -m-4 -mt-9 left-0' : 'w-10 h-10';
|
||||||
case 'n8n':
|
|
||||||
if (isAbsolute) {
|
|
||||||
return 'w-12 h-12 absolute -m-9 -mt-12';
|
|
||||||
}
|
|
||||||
return 'w-12 h-12 -mt-3';
|
|
||||||
case 'weblate':
|
|
||||||
if (isAbsolute) {
|
|
||||||
return 'w-12 h-12 absolute -m-9 -mt-12';
|
|
||||||
}
|
|
||||||
return 'w-12 h-12 -mt-3';
|
|
||||||
default:
|
|
||||||
return isAbsolute ? 'w-10 h-10 absolute -m-4 -mt-9 left-0' : 'w-10 h-10';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if name}
|
{#if name}
|
||||||
<img
|
<img
|
||||||
class={generateClass()}
|
class={generateClass()}
|
||||||
src={`/icons/${name}.${extension}`}
|
src={`${githubRawIconUrl}/${name}.png`}
|
||||||
on:error={handleError}
|
on:error={handleError}
|
||||||
alt={`Icon of ${name}`}
|
alt={`Icon of ${name}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -196,6 +196,9 @@
|
|||||||
"domain_fqdn": "Domain (FQDN)",
|
"domain_fqdn": "Domain (FQDN)",
|
||||||
"https_explainer": "If you specify <span class='text-settings '>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-settings '>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white '>You must set your DNS to point to the server IP in advance.</span>",
|
"https_explainer": "If you specify <span class='text-settings '>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-settings '>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application.<br><br><span class='text-white '>You must set your DNS to point to the server IP in advance.</span>",
|
||||||
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
"ssl_www_and_non_www": "Generate SSL for www and non-www?",
|
||||||
|
"basic_auth": "Basic Auth",
|
||||||
|
"basic_auth_user": "User",
|
||||||
|
"basic_auth_pw": "Password",
|
||||||
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class=' text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
"ssl_explainer": "It will generate certificates for both www and non-www. <br>You need to have <span class=' text-settings'>both DNS entries</span> set in advance.<br><br>Useful if you expect to have visitors on both.",
|
||||||
"install_command": "Install Command",
|
"install_command": "Install Command",
|
||||||
"build_command": "Build Command",
|
"build_command": "Build Command",
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ interface AppSession {
|
|||||||
github: string | null,
|
github: string | null,
|
||||||
gitlab: string | null,
|
gitlab: string | null,
|
||||||
},
|
},
|
||||||
pendingInvitations: Array<any>
|
pendingInvitations: Array<any>,
|
||||||
|
isARM: boolean
|
||||||
}
|
}
|
||||||
interface AddToast {
|
interface AddToast {
|
||||||
type?: "info" | "success" | "error",
|
type?: "info" | "success" | "error",
|
||||||
@@ -52,15 +53,15 @@ export const appSession: Writable<AppSession> = writable({
|
|||||||
github: null,
|
github: null,
|
||||||
gitlab: null
|
gitlab: null
|
||||||
},
|
},
|
||||||
pendingInvitations: []
|
pendingInvitations: [],
|
||||||
|
isARM: false
|
||||||
});
|
});
|
||||||
export const disabledButton: Writable<boolean> = writable(false);
|
export const disabledButton: Writable<boolean> = writable(false);
|
||||||
export const isDeploymentEnabled: Writable<boolean> = writable(false);
|
export const isDeploymentEnabled: Writable<boolean> = writable(false);
|
||||||
export function checkIfDeploymentEnabledApplications(isAdmin: boolean, application: any) {
|
export function checkIfDeploymentEnabledApplications(application: any) {
|
||||||
return !!(
|
return !!(
|
||||||
isAdmin &&
|
|
||||||
(application.buildPack === 'compose') ||
|
(application.buildPack === 'compose') ||
|
||||||
(application.fqdn || application.settings.isBot) &&
|
(application.fqdn || application.settings?.isBot) &&
|
||||||
((application.gitSource &&
|
((application.gitSource &&
|
||||||
application.repository &&
|
application.repository &&
|
||||||
application.buildPack) || application.simpleDockerfile) &&
|
application.buildPack) || application.simpleDockerfile) &&
|
||||||
@@ -68,9 +69,8 @@ export function checkIfDeploymentEnabledApplications(isAdmin: boolean, applicati
|
|||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export function checkIfDeploymentEnabledServices(isAdmin: boolean, service: any) {
|
export function checkIfDeploymentEnabledServices( service: any) {
|
||||||
return (
|
return (
|
||||||
isAdmin &&
|
|
||||||
service.fqdn &&
|
service.fqdn &&
|
||||||
service.destinationDocker &&
|
service.destinationDocker &&
|
||||||
service.version &&
|
service.version &&
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
export let sentryDSN: any;
|
|
||||||
export let baseSettings: any;
|
export let baseSettings: any;
|
||||||
export let pendingInvitations: any = 0;
|
export let pendingInvitations: any = 0;
|
||||||
|
|
||||||
@@ -75,6 +74,7 @@
|
|||||||
$appSession.version = baseSettings.version;
|
$appSession.version = baseSettings.version;
|
||||||
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
$appSession.whiteLabeled = baseSettings.whiteLabeled;
|
||||||
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
$appSession.whiteLabeledDetails.icon = baseSettings.whiteLabeledIcon;
|
||||||
|
$appSession.isARM = baseSettings.isARM;
|
||||||
|
|
||||||
$appSession.pendingInvitations = pendingInvitations;
|
$appSession.pendingInvitations = pendingInvitations;
|
||||||
|
|
||||||
@@ -97,10 +97,6 @@
|
|||||||
import Toasts from '$lib/components/Toasts.svelte';
|
import Toasts from '$lib/components/Toasts.svelte';
|
||||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import LocalePicker from '$lib/components/LocalePicker.svelte';
|
|
||||||
import * as Sentry from '@sentry/svelte';
|
|
||||||
import { BrowserTracing } from '@sentry/tracing';
|
|
||||||
import { dev } from '$app/env';
|
|
||||||
|
|
||||||
if (userId) $appSession.userId = userId;
|
if (userId) $appSession.userId = userId;
|
||||||
if (teamId) $appSession.teamId = teamId;
|
if (teamId) $appSession.teamId = teamId;
|
||||||
@@ -135,6 +131,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let sidedrawerToggler: HTMLInputElement;
|
||||||
|
|
||||||
|
const closeDrawer = () => (sidedrawerToggler.checked = false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -154,9 +154,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="drawer">
|
<div class="drawer">
|
||||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
<input id="main-drawer" type="checkbox" class="drawer-toggle" bind:this={sidedrawerToggler} />
|
||||||
<div class="drawer-content">
|
<div class="drawer-content">
|
||||||
{#if $appSession.userId}
|
{#if $appSession.userId}
|
||||||
|
<Tooltip triggeredBy="#dashboard" placement="right" color="bg-pink-500">Dashboard</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#servers" placement="right" color="bg-sky-500">Servers</Tooltip>
|
||||||
<Tooltip triggeredBy="#iam" placement="right" color="bg-iam">IAM</Tooltip>
|
<Tooltip triggeredBy="#iam" placement="right" color="bg-iam">IAM</Tooltip>
|
||||||
<Tooltip triggeredBy="#settings" placement="right" color="bg-settings text-black"
|
<Tooltip triggeredBy="#settings" placement="right" color="bg-settings text-black"
|
||||||
>Settings</Tooltip
|
>Settings</Tooltip
|
||||||
@@ -176,7 +178,6 @@
|
|||||||
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
||||||
<a
|
<a
|
||||||
id="dashboard"
|
id="dashboard"
|
||||||
sveltekit:prefetch
|
|
||||||
href="/"
|
href="/"
|
||||||
class="icons hover:text-pink-500"
|
class="icons hover:text-pink-500"
|
||||||
class:text-pink-500={$page.url.pathname === '/'}
|
class:text-pink-500={$page.url.pathname === '/'}
|
||||||
@@ -203,7 +204,6 @@
|
|||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<a
|
<a
|
||||||
id="servers"
|
id="servers"
|
||||||
sveltekit:prefetch
|
|
||||||
href="/servers"
|
href="/servers"
|
||||||
class="icons hover:text-sky-500"
|
class="icons hover:text-sky-500"
|
||||||
class:text-sky-500={$page.url.pathname === '/servers'}
|
class:text-sky-500={$page.url.pathname === '/servers'}
|
||||||
@@ -229,8 +229,6 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Tooltip triggeredBy="#dashboard" placement="right">Dashboard</Tooltip>
|
|
||||||
<Tooltip triggeredBy="#servers" placement="right">Servers</Tooltip>
|
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<div class="lg:block hidden">
|
<div class="lg:block hidden">
|
||||||
<UpdateAvailable />
|
<UpdateAvailable />
|
||||||
@@ -238,7 +236,6 @@
|
|||||||
<div class="flex flex-col space-y-2 py-2">
|
<div class="flex flex-col space-y-2 py-2">
|
||||||
<a
|
<a
|
||||||
id="iam"
|
id="iam"
|
||||||
sveltekit:prefetch
|
|
||||||
href={$appSession.pendingInvitations.length > 0 ? '/iam/pending' : '/iam'}
|
href={$appSession.pendingInvitations.length > 0 ? '/iam/pending' : '/iam'}
|
||||||
class="icons hover:text-iam indicator"
|
class="icons hover:text-iam indicator"
|
||||||
class:text-iam={$page.url.pathname.startsWith('/iam')}
|
class:text-iam={$page.url.pathname.startsWith('/iam')}
|
||||||
@@ -267,7 +264,6 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
id="settings"
|
id="settings"
|
||||||
sveltekit:prefetch
|
|
||||||
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/docker'}
|
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/docker'}
|
||||||
class="icons hover:text-settings"
|
class="icons hover:text-settings"
|
||||||
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
||||||
@@ -290,6 +286,28 @@
|
|||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
id="documentation"
|
||||||
|
href="https://docs.coollabs.io/coolify-v3/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer external"
|
||||||
|
class="icons hover:text-info"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-9 h-9"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div
|
<div
|
||||||
@@ -361,13 +379,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="drawer-side">
|
<div class="drawer-side">
|
||||||
<label for="main-drawer" class="drawer-overlay w-full" />
|
<label for="main-drawer" class="drawer-overlay w-full" />
|
||||||
<ul class="menu bg-coolgray-200 w-60 p-2 space-y-3 pt-4 ">
|
<ul class="menu bg-coolgray-200 w-60 p-2 space-y-3 pt-4">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="no-underline icons hover:text-white hover:bg-pink-500"
|
class="no-underline icons hover:text-white hover:bg-pink-500"
|
||||||
sveltekit:prefetch
|
|
||||||
href="/"
|
href="/"
|
||||||
class:bg-pink-500={$page.url.pathname === '/'}
|
class:bg-pink-500={$page.url.pathname === '/'}
|
||||||
|
on:click={closeDrawer}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -393,9 +411,9 @@
|
|||||||
<a
|
<a
|
||||||
id="servers"
|
id="servers"
|
||||||
class="no-underline icons hover:text-white hover:bg-sky-500"
|
class="no-underline icons hover:text-white hover:bg-sky-500"
|
||||||
sveltekit:prefetch
|
|
||||||
href="/servers"
|
href="/servers"
|
||||||
class:bg-sky-500={$page.url.pathname.startsWith('/servers')}
|
class:bg-sky-500={$page.url.pathname.startsWith('/servers')}
|
||||||
|
on:click={closeDrawer}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -421,6 +439,7 @@
|
|||||||
class="no-underline icons hover:text-white hover:bg-iam"
|
class="no-underline icons hover:text-white hover:bg-iam"
|
||||||
href="/iam"
|
href="/iam"
|
||||||
class:bg-iam={$page.url.pathname.startsWith('/iam')}
|
class:bg-iam={$page.url.pathname.startsWith('/iam')}
|
||||||
|
on:click={closeDrawer}
|
||||||
><svg
|
><svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -450,6 +469,7 @@
|
|||||||
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
|
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
|
||||||
class:bg-settings={$page.url.pathname.startsWith('/settings')}
|
class:bg-settings={$page.url.pathname.startsWith('/settings')}
|
||||||
class:text-black={$page.url.pathname.startsWith('/settings')}
|
class:text-black={$page.url.pathname.startsWith('/settings')}
|
||||||
|
on:click={closeDrawer}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -470,6 +490,30 @@
|
|||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="no-underline icons hover:text-white hover:bg-info"
|
||||||
|
href="https://docs.coollabs.io/coolify-v3/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer external"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-8 h-8"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="flex-1 bg-transparent" />
|
<li class="flex-1 bg-transparent" />
|
||||||
<div class="block lg:hidden">
|
<div class="block lg:hidden">
|
||||||
<UpdateAvailable />
|
<UpdateAvailable />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let application: any;
|
export let application: any;
|
||||||
import { status } from '$lib/store';
|
import { appSession, status } from '$lib/store';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
<li class="menu-title">
|
<li class="menu-title">
|
||||||
<span>Advanced</span>
|
<span>Advanced</span>
|
||||||
</li>
|
</li>
|
||||||
{#if application.gitSourceId}
|
{#if application.gitSourceId && $appSession.isAdmin}
|
||||||
<li
|
<li
|
||||||
class="rounded"
|
class="rounded"
|
||||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
|
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
|
||||||
@@ -295,6 +295,7 @@
|
|||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if $appSession.isAdmin}
|
||||||
<li
|
<li
|
||||||
class="rounded"
|
class="rounded"
|
||||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/danger`}
|
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/danger`}
|
||||||
@@ -318,4 +319,5 @@
|
|||||||
</svg>Danger Zone</a
|
</svg>Danger Zone</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import { del, post, put } from '$lib/api';
|
import { del, post, put } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
<label for="name" class="pb-5 uppercase lg:block hidden font-bold" />
|
<label for="name" class="pb-5 uppercase lg:block hidden font-bold" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if $appSession.isAdmin}
|
||||||
<div class="flex justify-center h-full items-center pt-3">
|
<div class="flex justify-center h-full items-center pt-3">
|
||||||
<div class="flex flex-row justify-center space-x-2">
|
<div class="flex flex-row justify-center space-x-2">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
@@ -127,5 +128,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import { del, post, put } from '$lib/api';
|
import { del, post, put } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
@@ -124,8 +124,9 @@
|
|||||||
<div class="flex justify-center h-full items-center pt-0 lg:pt-0 pl-4 lg:pl-0">
|
<div class="flex justify-center h-full items-center pt-0 lg:pt-0 pl-4 lg:pl-0">
|
||||||
<button
|
<button
|
||||||
on:click={() => updateSecret({ changeIsBuildSecret: true })}
|
on:click={() => updateSecret({ changeIsBuildSecret: true })}
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out "
|
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||||
class:bg-green-600={isBuildSecret}
|
class:bg-green-600={isBuildSecret}
|
||||||
class:bg-stone-700={!isBuildSecret}
|
class:bg-stone-700={!isBuildSecret}
|
||||||
>
|
>
|
||||||
@@ -177,7 +178,7 @@
|
|||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-sm btn-primary" on:click={addNewSecret}>Add</button>
|
<button class="btn btn-sm btn-primary" on:click={addNewSecret}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else if $appSession.isAdmin}
|
||||||
<div class="flex flex-row justify-center space-x-2">
|
<div class="flex flex-row justify-center space-x-2">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-sm btn-primary" on:click={() => updateSecret()}>Set</button>
|
<button class="btn btn-sm btn-primary" on:click={() => updateSecret()}>Set</button>
|
||||||
|
|||||||
@@ -7,12 +7,24 @@
|
|||||||
import { del, post } from '$lib/api';
|
import { del, post } from '$lib/api';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { browser } from '$app/env';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast } from '$lib/store';
|
||||||
|
import CopyVolumeField from '$lib/components/CopyVolumeField.svelte';
|
||||||
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
let isHttps = browser && window.location.protocol === 'https:';
|
||||||
|
export let value: string;
|
||||||
|
function copyToClipboard() {
|
||||||
|
if (isHttps && navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
addToast({
|
||||||
|
message: 'Copied to clipboard.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
async function saveStorage(newStorage = false) {
|
async function saveStorage(newStorage = false) {
|
||||||
try {
|
try {
|
||||||
@@ -22,11 +34,13 @@
|
|||||||
storage.path.replace(/\/\//g, '/');
|
storage.path.replace(/\/\//g, '/');
|
||||||
await post(`/applications/${id}/storages`, {
|
await post(`/applications/${id}/storages`, {
|
||||||
path: storage.path,
|
path: storage.path,
|
||||||
|
hostPath: storage.hostPath,
|
||||||
storageId: storage.id,
|
storageId: storage.id,
|
||||||
newStorage
|
newStorage
|
||||||
});
|
});
|
||||||
dispatch('refresh');
|
dispatch('refresh');
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
storage.hostPath = null;
|
||||||
storage.path = null;
|
storage.path = null;
|
||||||
storage.id = null;
|
storage.id = null;
|
||||||
}
|
}
|
||||||
@@ -69,31 +83,42 @@
|
|||||||
<div class="flex gap-4 pb-2" class:pt-8={isNew}>
|
<div class="flex gap-4 pb-2" class:pt-8={isNew}>
|
||||||
{#if storage.applicationId}
|
{#if storage.applicationId}
|
||||||
{#if storage.oldPath}
|
{#if storage.oldPath}
|
||||||
<input
|
<CopyVolumeField
|
||||||
disabled
|
|
||||||
readonly
|
|
||||||
class="w-full"
|
|
||||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else if !storage.hostPath}
|
||||||
<input
|
<CopyVolumeField
|
||||||
disabled
|
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
||||||
readonly
|
|
||||||
class="w-full"
|
|
||||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-')}"
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if isNew}
|
||||||
|
<div class="w-full">
|
||||||
|
<input
|
||||||
|
disabled={!isNew}
|
||||||
|
readonly={!isNew}
|
||||||
|
bind:value={storage.hostPath}
|
||||||
|
placeholder="Host path, example: ~/.directory"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SimpleExplainer
|
||||||
|
text="You can mount <span class='text-yellow-400 font-bold'>host paths</span> from the operating system.<br>Leave it empty to define a volume based volume."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if storage.hostPath}
|
||||||
|
<input disabled readonly value={storage.hostPath} />
|
||||||
|
{/if}
|
||||||
<input
|
<input
|
||||||
disabled={!isNew}
|
disabled={!isNew}
|
||||||
readonly={!isNew}
|
readonly={!isNew}
|
||||||
class="w-full"
|
class="w-full"
|
||||||
bind:value={storage.path}
|
bind:value={storage.path}
|
||||||
required
|
required
|
||||||
placeholder="eg: /data"
|
placeholder="Mount point inside the container, example: /data"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-start justify-center">
|
||||||
{#if isNew}
|
{#if isNew}
|
||||||
<div class="w-full lg:w-64">
|
<div class="w-full lg:w-64">
|
||||||
<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)}
|
<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)}
|
||||||
|
|||||||
@@ -83,13 +83,13 @@
|
|||||||
let forceDelete = false;
|
let forceDelete = false;
|
||||||
let stopping = false;
|
let stopping = false;
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
|
|
||||||
async function deleteApplication(name: string, force: boolean) {
|
async function deleteApplication(name: string, force: boolean) {
|
||||||
const sure = confirm($t('application.confirm_to_delete', { name }));
|
const sure = confirm($t('application.confirm_to_delete', { name }));
|
||||||
if (sure) {
|
if (sure) {
|
||||||
try {
|
try {
|
||||||
await del(`/applications/${id}`, { id, force });
|
await del(`/applications/${id}`, {});
|
||||||
return await goto('/');
|
return await goto('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid`)) {
|
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid`)) {
|
||||||
@@ -292,7 +292,6 @@
|
|||||||
<a
|
<a
|
||||||
href={$isDeploymentEnabled ? `/applications/${id}/logs` : null}
|
href={$isDeploymentEnabled ? `/applications/${id}/logs` : null}
|
||||||
class="btn btn-sm text-sm gap-2"
|
class="btn btn-sm text-sm gap-2"
|
||||||
sveltekit:prefetch
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -363,7 +362,7 @@
|
|||||||
<button
|
<button
|
||||||
on:click={restartApplication}
|
on:click={restartApplication}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$isDeploymentEnabled}
|
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-sm gap-2"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -383,7 +382,7 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
disabled={!$isDeploymentEnabled}
|
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-sm gap-2"
|
||||||
on:click={() => handleDeploySubmit(true)}
|
on:click={() => handleDeploySubmit(true)}
|
||||||
>
|
>
|
||||||
@@ -409,7 +408,7 @@
|
|||||||
<button
|
<button
|
||||||
on:click={stopApplication}
|
on:click={stopApplication}
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!$isDeploymentEnabled}
|
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-sm gap-2"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -428,32 +427,9 @@
|
|||||||
</svg> Stop
|
</svg> Stop
|
||||||
</button>
|
</button>
|
||||||
{:else if $isDeploymentEnabled && !$page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
{:else if $isDeploymentEnabled && !$page.url.pathname.startsWith(`/applications/${id}/configuration/`)}
|
||||||
{#if $status.application.overallStatus === 'degraded'}
|
|
||||||
<button
|
|
||||||
on:click={stopApplication}
|
|
||||||
type="submit"
|
|
||||||
disabled={!$isDeploymentEnabled}
|
|
||||||
class="btn btn-sm gap-2"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="w-6 h-6 text-error"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
||||||
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
||||||
</svg> Stop
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm gap-2"
|
class="btn btn-sm gap-2"
|
||||||
disabled={!$isDeploymentEnabled}
|
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||||
on:click={() => handleDeploySubmit(true)}
|
on:click={() => handleDeploySubmit(true)}
|
||||||
>
|
>
|
||||||
{#if $status.application.overallStatus !== 'degraded'}
|
{#if $status.application.overallStatus !== 'degraded'}
|
||||||
@@ -494,6 +470,29 @@
|
|||||||
: 'Redeploy Stack'
|
: 'Redeploy Stack'
|
||||||
: 'Deploy'}
|
: 'Deploy'}
|
||||||
</button>
|
</button>
|
||||||
|
{#if $status.application.overallStatus === 'degraded'}
|
||||||
|
<button
|
||||||
|
on:click={stopApplication}
|
||||||
|
type="submit"
|
||||||
|
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||||
|
class="btn btn-sm gap-2"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-6 h-6 text-error"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="6" y="5" width="4" height="14" rx="1" />
|
||||||
|
<rect x="14" y="5" width="4" height="14" rx="1" />
|
||||||
|
</svg> Stop
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if $location && $status.application.overallStatus === 'healthy'}
|
{#if $location && $status.application.overallStatus === 'healthy'}
|
||||||
<a href={$location} target="_blank noreferrer" class="btn btn-sm gap-2 text-sm bg-primary"
|
<a href={$location} target="_blank noreferrer" class="btn btn-sm gap-2 text-sm bg-primary"
|
||||||
|
|||||||
@@ -28,12 +28,15 @@
|
|||||||
delete tempBuildPack.fancyName;
|
delete tempBuildPack.fancyName;
|
||||||
delete tempBuildPack.color;
|
delete tempBuildPack.color;
|
||||||
delete tempBuildPack.hoverColor;
|
delete tempBuildPack.hoverColor;
|
||||||
let composeConfiguration: any = {}
|
let composeConfiguration: any = {};
|
||||||
if (!dockerComposeConfiguration && dockerComposeFile) {
|
if (!dockerComposeConfiguration && dockerComposeFile && buildPack.name === 'compose') {
|
||||||
for (const [name, _] of Object.entries(JSON.parse(dockerComposeFile).services)) {
|
const parsed = JSON.parse(dockerComposeFile);
|
||||||
|
if (!parsed?.services) {
|
||||||
|
throw new Error('No services found in docker-compose file. <br>Choose a different buildpack.');
|
||||||
|
}
|
||||||
|
for (const [name, _] of Object.entries(parsed.services)) {
|
||||||
composeConfiguration[name] = {};
|
composeConfiguration[name] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
await post(`/applications/${id}`, {
|
await post(`/applications/${id}`, {
|
||||||
...tempBuildPack,
|
...tempBuildPack,
|
||||||
|
|||||||
@@ -125,6 +125,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if repositories.length === 0 && loading.repositories === false}
|
{#if repositories.length === 0 && loading.repositories === false}
|
||||||
<div class="flex-col text-center">
|
<div class="flex-col text-center">
|
||||||
<div class="pb-4">{$t('application.configuration.no_repositories_configured')}</div>
|
<div class="pb-4">{$t('application.configuration.no_repositories_configured')}</div>
|
||||||
@@ -134,43 +135,45 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<form on:submit|preventDefault={handleSubmit} class="px-10">
|
<form on:submit|preventDefault={handleSubmit} class="px-10">
|
||||||
<div class="flex lg:flex-row flex-col lg:space-y-0 space-y-2 space-x-0 lg:space-x-2 items-center lg:justify-center">
|
<div
|
||||||
<div class="custom-select-wrapper w-full"><label for="repository" class="pb-1">Repository</label>
|
class="flex lg:flex-row flex-col lg:space-y-0 space-y-2 space-x-0 lg:space-x-2 items-center lg:justify-center"
|
||||||
<Select
|
>
|
||||||
placeholder={loading.repositories
|
<div class="custom-select-wrapper w-full">
|
||||||
? $t('application.configuration.loading_repositories')
|
<label for="repository" class="pb-1">Repository</label>
|
||||||
: $t('application.configuration.select_a_repository')}
|
<Select
|
||||||
id="repository"
|
placeholder={loading.repositories
|
||||||
showIndicator={!loading.repositories}
|
? $t('application.configuration.loading_repositories')
|
||||||
isWaiting={loading.repositories}
|
: $t('application.configuration.select_a_repository')}
|
||||||
on:select={loadBranches}
|
id="repository"
|
||||||
items={reposSelectOptions}
|
showIndicator={!loading.repositories}
|
||||||
isDisabled={loading.repositories}
|
isWaiting={loading.repositories}
|
||||||
isClearable={false}
|
on:select={loadBranches}
|
||||||
/>
|
items={reposSelectOptions}
|
||||||
</div>
|
isDisabled={loading.repositories}
|
||||||
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
isClearable={false}
|
||||||
<div class="custom-select-wrapper w-full"><label for="repository" class="pb-1">Branch</label>
|
/>
|
||||||
<Select
|
</div>
|
||||||
placeholder={loading.branches
|
<input class="hidden" bind:value={selected.projectId} name="projectId" />
|
||||||
? $t('application.configuration.loading_branches')
|
<div class="custom-select-wrapper w-full">
|
||||||
: !selected.repository
|
<label for="repository" class="pb-1">Branch</label>
|
||||||
? $t('application.configuration.select_a_repository_first')
|
<Select
|
||||||
: $t('application.configuration.select_a_branch')}
|
placeholder={loading.branches
|
||||||
isWaiting={loading.branches}
|
? $t('application.configuration.loading_branches')
|
||||||
showIndicator={selected.repository && !loading.branches}
|
: !selected.repository
|
||||||
id="branches"
|
? $t('application.configuration.select_a_repository_first')
|
||||||
on:select={selectBranch}
|
: $t('application.configuration.select_a_branch')}
|
||||||
items={branchSelectOptions}
|
isWaiting={loading.branches}
|
||||||
isDisabled={loading.branches || !selected.repository}
|
showIndicator={selected.repository && !loading.branches}
|
||||||
isClearable={false}
|
id="branches"
|
||||||
/>
|
on:select={selectBranch}
|
||||||
</div></div>
|
items={branchSelectOptions}
|
||||||
|
isDisabled={loading.branches || !selected.repository}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="pt-5 flex-col flex justify-center items-center space-y-4">
|
<div class="pt-5 flex-col flex justify-center items-center space-y-4">
|
||||||
<button
|
<button class="btn btn-wide btn-primary" type="submit" disabled={!showSave}
|
||||||
class="btn btn-wide btn-primary"
|
|
||||||
type="submit"
|
|
||||||
disabled={!showSave}
|
|
||||||
>{$t('forms.save')}</button
|
>{$t('forms.save')}</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,6 +56,10 @@
|
|||||||
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
Authorization: `Bearer ${$appSession.tokens.gitlab}`
|
||||||
});
|
});
|
||||||
username = user.username;
|
username = user.username;
|
||||||
|
groups.push({
|
||||||
|
full_name: username,
|
||||||
|
name: username
|
||||||
|
});
|
||||||
await loadGroups();
|
await loadGroups();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loading.base = false;
|
loading.base = false;
|
||||||
@@ -402,7 +406,7 @@
|
|||||||
>
|
>
|
||||||
{#if tryAgain}
|
{#if tryAgain}
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
An error occured during authenticating with GitLab. Please check your GitLab Source
|
An error occurred during authenticating with GitLab. Please check your GitLab Source
|
||||||
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
configuration <a href={`/sources/${application.gitSource.id}`}>here.</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -72,7 +72,6 @@
|
|||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a
|
<a
|
||||||
href={`/destinations/new?from=/applications/${id}/configuration/destination`}
|
href={`/destinations/new?from=/applications/${id}/configuration/destination`}
|
||||||
sveltekit:prefetch
|
|
||||||
class="add-icon bg-sky-600 hover:bg-sky-500"
|
class="add-icon bg-sky-600 hover:bg-sky-500"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
{#if ownSources.length > 0 || otherSources.length > 0}
|
{#if ownSources.length > 0 || otherSources.length > 0}
|
||||||
<div class="title pb-8">Integrated with Git App</div>
|
<div class="title pb-8">Integrated with Git App</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if ownSources.length > 0}
|
{#if ownSources.length > 0 || otherSources.length > 0}
|
||||||
<div class="flex flex-wrap justify-center">
|
<div class="flex flex-wrap justify-center">
|
||||||
<div class="flex flex-col lg:flex-row lg:flex-wrap justify-center">
|
<div class="flex flex-col lg:flex-row lg:flex-wrap justify-center">
|
||||||
{#each ownSources as source}
|
{#each ownSources as source}
|
||||||
@@ -255,12 +255,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<div class="title py-4 pr-4">Public Repository from Git</div>
|
<div class="title py-4 pr-4">Public Repository from Git</div>
|
||||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" />
|
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#public-repository-from-git" />
|
||||||
</div>
|
</div>
|
||||||
<PublicRepository />
|
<PublicRepository />
|
||||||
<div class="flex flex-row items-center pt-10">
|
<div class="flex flex-row items-center pt-10">
|
||||||
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
|
||||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#dockerfile" />
|
<DocLink url="https://docs.coollabs.io/coolify-v3/applications/#simple-dockerfile" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-screen-2xl">
|
<div class="mx-auto max-w-screen-2xl">
|
||||||
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
if (sure) {
|
if (sure) {
|
||||||
$status.application.initialLoading = true;
|
$status.application.initialLoading = true;
|
||||||
try {
|
try {
|
||||||
await del(`/applications/${id}`, { id, force });
|
await del(`/applications/${id}`,{});
|
||||||
return await goto('/')
|
return await goto('/')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid`)) {
|
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid`)) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
let isDBBranching = application.settings.isDBBranching;
|
let isDBBranching = application.settings.isDBBranching;
|
||||||
|
|
||||||
async function changeSettings(name: any) {
|
async function changeSettings(name: any) {
|
||||||
|
if (!$appSession.isAdmin) return
|
||||||
if (name === 'previews') {
|
if (name === 'previews') {
|
||||||
previews = !previews;
|
previews = !previews;
|
||||||
}
|
}
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
}
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -119,6 +120,7 @@
|
|||||||
id="autodeploy"
|
id="autodeploy"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={autodeploy}
|
bind:setting={autodeploy}
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
on:click={() => changeSettings('autodeploy')}
|
on:click={() => changeSettings('autodeploy')}
|
||||||
title={$t('application.enable_automatic_deployment')}
|
title={$t('application.enable_automatic_deployment')}
|
||||||
description={$t('application.enable_auto_deploy_webhooks')}
|
description={$t('application.enable_auto_deploy_webhooks')}
|
||||||
@@ -130,6 +132,7 @@
|
|||||||
id="previews"
|
id="previews"
|
||||||
isCenter={false}
|
isCenter={false}
|
||||||
bind:setting={previews}
|
bind:setting={previews}
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
on:click={() => changeSettings('previews')}
|
on:click={() => changeSettings('previews')}
|
||||||
title={$t('application.enable_mr_pr_previews')}
|
title={$t('application.enable_mr_pr_previews')}
|
||||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||||
|
|||||||
@@ -29,27 +29,28 @@
|
|||||||
export let application: any;
|
export let application: any;
|
||||||
export let settings: any;
|
export let settings: any;
|
||||||
|
|
||||||
import yaml from 'js-yaml';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import Select from 'svelte-select';
|
|
||||||
import { get, getAPIUrl, post } from '$lib/api';
|
import { get, getAPIUrl, post } from '$lib/api';
|
||||||
import cuid from 'cuid';
|
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
||||||
|
import Beta from '$lib/components/Beta.svelte';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
import Setting from '$lib/components/Setting.svelte';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import {
|
import {
|
||||||
addToast,
|
addToast,
|
||||||
appSession,
|
appSession,
|
||||||
checkIfDeploymentEnabledApplications,
|
checkIfDeploymentEnabledApplications,
|
||||||
setLocation,
|
features,
|
||||||
status,
|
|
||||||
isDeploymentEnabled,
|
isDeploymentEnabled,
|
||||||
features
|
setLocation,
|
||||||
|
status
|
||||||
} from '$lib/store';
|
} from '$lib/store';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
|
import cuid from 'cuid';
|
||||||
import Setting from '$lib/components/Setting.svelte';
|
import yaml from 'js-yaml';
|
||||||
import Explainer from '$lib/components/Explainer.svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import Select from 'svelte-select';
|
||||||
import Beta from '$lib/components/Beta.svelte';
|
|
||||||
import { saveForm } from './utils';
|
import { saveForm } from './utils';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
$status.application.overallStatus === 'degraded' ||
|
$status.application.overallStatus === 'degraded' ||
|
||||||
$status.application.overallStatus === 'healthy' ||
|
$status.application.overallStatus === 'healthy' ||
|
||||||
$status.application.initialLoading;
|
$status.application.initialLoading;
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
let statues: any = {};
|
let statues: any = {};
|
||||||
let loading = {
|
let loading = {
|
||||||
save: false,
|
save: false,
|
||||||
@@ -77,8 +78,10 @@
|
|||||||
let isCustomSSL = application.settings?.isCustomSSL;
|
let isCustomSSL = application.settings?.isCustomSSL;
|
||||||
let autodeploy = application.settings?.autodeploy;
|
let autodeploy = application.settings?.autodeploy;
|
||||||
let isBot = application.settings?.isBot;
|
let isBot = application.settings?.isBot;
|
||||||
|
let basicAuth = application.settings?.basicAuth;
|
||||||
let isDBBranching = application.settings?.isDBBranching;
|
let isDBBranching = application.settings?.isDBBranching;
|
||||||
let htmlUrl = application.gitSource?.htmlUrl;
|
let htmlUrl = application.gitSource?.htmlUrl;
|
||||||
|
let isHttp2 = application.settings.isHttp2;
|
||||||
|
|
||||||
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
|
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
|
||||||
let dockerComposeServices: any[] = [];
|
let dockerComposeServices: any[] = [];
|
||||||
@@ -185,6 +188,9 @@
|
|||||||
if (name === 'isCustomSSL') {
|
if (name === 'isCustomSSL') {
|
||||||
isCustomSSL = !isCustomSSL;
|
isCustomSSL = !isCustomSSL;
|
||||||
}
|
}
|
||||||
|
if (name === 'basicAuth') {
|
||||||
|
basicAuth = !basicAuth;
|
||||||
|
}
|
||||||
if (name === 'isBot') {
|
if (name === 'isBot') {
|
||||||
if ($status.application.overallStatus !== 'stopped') return;
|
if ($status.application.overallStatus !== 'stopped') return;
|
||||||
isBot = !isBot;
|
isBot = !isBot;
|
||||||
@@ -195,6 +201,9 @@
|
|||||||
if (name === 'isDBBranching') {
|
if (name === 'isDBBranching') {
|
||||||
isDBBranching = !isDBBranching;
|
isDBBranching = !isDBBranching;
|
||||||
}
|
}
|
||||||
|
if (name === 'isHttp2') {
|
||||||
|
isHttp2 = !isHttp2;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/settings`, {
|
await post(`/applications/${id}/settings`, {
|
||||||
previews,
|
previews,
|
||||||
@@ -204,8 +213,10 @@
|
|||||||
autodeploy,
|
autodeploy,
|
||||||
isDBBranching,
|
isDBBranching,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
|
isHttp2,
|
||||||
branch: application.branch,
|
branch: application.branch,
|
||||||
projectId: application.projectId
|
projectId: application.projectId,
|
||||||
|
basicAuth
|
||||||
});
|
});
|
||||||
return addToast({
|
return addToast({
|
||||||
message: $t('application.settings_saved'),
|
message: $t('application.settings_saved'),
|
||||||
@@ -227,6 +238,9 @@
|
|||||||
if (name === 'isBot') {
|
if (name === 'isBot') {
|
||||||
isBot = !isBot;
|
isBot = !isBot;
|
||||||
}
|
}
|
||||||
|
if (name === 'basicAuth') {
|
||||||
|
basicAuth = !basicAuth;
|
||||||
|
}
|
||||||
if (name === 'isDBBranching') {
|
if (name === 'isDBBranching') {
|
||||||
isDBBranching = !isDBBranching;
|
isDBBranching = !isDBBranching;
|
||||||
}
|
}
|
||||||
@@ -235,7 +249,7 @@
|
|||||||
}
|
}
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
} finally {
|
} finally {
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function handleSubmit(toast: boolean = true) {
|
async function handleSubmit(toast: boolean = true) {
|
||||||
@@ -267,9 +281,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(application);
|
||||||
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
||||||
setLocation(application, settings);
|
setLocation(application, settings);
|
||||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||||
|
|
||||||
forceSave = false;
|
forceSave = false;
|
||||||
if (toast) {
|
if (toast) {
|
||||||
@@ -366,11 +381,16 @@
|
|||||||
async function reloadCompose() {
|
async function reloadCompose() {
|
||||||
if (loading.reloadCompose) return;
|
if (loading.reloadCompose) return;
|
||||||
loading.reloadCompose = true;
|
loading.reloadCompose = true;
|
||||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
if (!$appSession.tokens.github && !isPublicRepository) {
|
||||||
? application.dockerComposeFileLocation
|
const { token } = await get(`/applications/${id}/configuration/githubToken`);
|
||||||
: `/${application.dockerComposeFileLocation}`;
|
$appSession.tokens.github = token;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (application.gitSource.type === 'github') {
|
if (application.gitSource.type === 'github') {
|
||||||
|
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||||
|
? application.dockerComposeFileLocation
|
||||||
|
: `/${application.dockerComposeFileLocation}`;
|
||||||
|
|
||||||
const headers = isPublicRepository
|
const headers = isPublicRepository
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@@ -397,6 +417,17 @@
|
|||||||
if (!$appSession.tokens.gitlab) {
|
if (!$appSession.tokens.gitlab) {
|
||||||
await getGitlabToken();
|
await getGitlabToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||||
|
? application.dockerComposeFileLocation.substring(1) // Remove the '/' from the start
|
||||||
|
: application.dockerComposeFileLocation;
|
||||||
|
|
||||||
|
// If the file is in a subdirectory, lastIndex will be > 0
|
||||||
|
// Otherwise it will be -1 and path will be an empty string
|
||||||
|
const lastIndex = composeLocation.lastIndexOf('/') + 1;
|
||||||
|
const path = composeLocation.substring(0, lastIndex);
|
||||||
|
const fileName = composeLocation.substring(lastIndex);
|
||||||
|
|
||||||
const headers = isPublicRepository
|
const headers = isPublicRepository
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@@ -404,13 +435,12 @@
|
|||||||
};
|
};
|
||||||
const url = isPublicRepository
|
const url = isPublicRepository
|
||||||
? ``
|
? ``
|
||||||
: `/v4/projects/${application.projectId}/repository/tree`;
|
: `/v4/projects/${application.projectId}/repository/tree?path=${path}`;
|
||||||
const files = await get(`${apiUrl}${url}`, {
|
const files = await get(`${apiUrl}${url}`, {
|
||||||
...headers
|
...headers
|
||||||
});
|
});
|
||||||
const dockerComposeFileYml = files.find(
|
const dockerComposeFileYml = files.find(
|
||||||
(file: { name: string; type: string }) =>
|
(file: { name: string; type: string }) => file.name === fileName && file.type === 'blob'
|
||||||
file.name === composeLocation && file.type === 'blob'
|
|
||||||
);
|
);
|
||||||
const id = dockerComposeFileYml.id;
|
const id = dockerComposeFileYml.id;
|
||||||
|
|
||||||
@@ -478,7 +508,7 @@
|
|||||||
<div class="title font-bold pb-3">General</div>
|
<div class="title font-bold pb-3">General</div>
|
||||||
{#if $appSession.isAdmin}
|
{#if $appSession.isAdmin}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary"
|
||||||
type="submit"
|
type="submit"
|
||||||
class:loading={loading.save}
|
class:loading={loading.save}
|
||||||
class:bg-orange-600={forceSave}
|
class:bg-orange-600={forceSave}
|
||||||
@@ -490,7 +520,14 @@
|
|||||||
<div class="grid grid-flow-row gap-2 px-4">
|
<div class="grid grid-flow-row gap-2 px-4">
|
||||||
<div class="mt-2 grid grid-cols-2 items-center">
|
<div class="mt-2 grid grid-cols-2 items-center">
|
||||||
<label for="name">{$t('forms.name')}</label>
|
<label for="name">{$t('forms.name')}</label>
|
||||||
<input name="name" id="name" class="w-full" bind:value={application.name} required />
|
<input
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
class="w-full"
|
||||||
|
bind:value={application.name}
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if !isSimpleDockerfile}
|
{#if !isSimpleDockerfile}
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
@@ -712,7 +749,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<Setting
|
<Setting
|
||||||
id="dualCerts"
|
id="dualCerts"
|
||||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||||
@@ -724,6 +761,7 @@
|
|||||||
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
on:click={() => !isDisabled && changeSettings('dualCerts')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if isHttps && application.buildPack !== 'compose'}
|
{#if isHttps && application.buildPack !== 'compose'}
|
||||||
<div class="grid grid-cols-2 items-center pb-4">
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
<Setting
|
<Setting
|
||||||
@@ -736,6 +774,56 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="grid grid-cols-2 items-center pb-4">
|
||||||
|
<Setting
|
||||||
|
id="isHttp2"
|
||||||
|
isCenter={false}
|
||||||
|
bind:setting={isHttp2}
|
||||||
|
title="Enable HTTP/2 protocol?"
|
||||||
|
description="Enable HTTP/2 protocol. <br><br>HTTP/2 is a major revision of the HTTP network protocol used by the World Wide Web that allows faster web page loading by reducing the number of requests needed to load a web page.<br><br>Useful for gRPC and other HTTP/2 based services."
|
||||||
|
on:click={() => changeSettings('isHttp2')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<Setting
|
||||||
|
id="basicAuth"
|
||||||
|
isCenter={false}
|
||||||
|
bind:setting={basicAuth}
|
||||||
|
title={$t('application.basic_auth')}
|
||||||
|
description="Activate basic authentication for your application. <br>Useful if you want to protect your application with a password. <br><br>Use the <span class='font-bold text-settings'>username</span> and <span class='font-bold text-settings'>password</span> fields to set the credentials."
|
||||||
|
on:click={() => changeSettings('basicAuth')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if basicAuth}
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="basicAuthUser">{$t('application.basic_auth_user')}</label>
|
||||||
|
<input
|
||||||
|
bind:this={fqdnEl}
|
||||||
|
class="w-full"
|
||||||
|
required={!application.settings?.basicAuth}
|
||||||
|
name="basicAuthUser"
|
||||||
|
id="basicAuthUser"
|
||||||
|
class:border={!application.settings?.basicAuth && !application.basicAuthUser}
|
||||||
|
class:border-red-500={!application.settings?.basicAuth &&
|
||||||
|
!application.basicAuthUser}
|
||||||
|
bind:value={application.basicAuthUser}
|
||||||
|
placeholder="eg: admin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 items-center">
|
||||||
|
<label for="basicAuthPw">{$t('application.basic_auth_pw')}</label>
|
||||||
|
<CopyPasswordField
|
||||||
|
bind:this={fqdnEl}
|
||||||
|
isPasswordField={true}
|
||||||
|
required={!application.settings?.basicAuth}
|
||||||
|
name="basicAuthPw"
|
||||||
|
id="basicAuthPw"
|
||||||
|
bind:value={application.basicAuthPw}
|
||||||
|
placeholder="**********"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if isSimpleDockerfile}
|
{#if isSimpleDockerfile}
|
||||||
@@ -744,7 +832,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
<div class="grid grid-flow-row gap-2 px-4 pr-5">
|
||||||
<div class="grid grid-cols-2 items-center pt-4">
|
<div class="grid grid-cols-2 items-center pt-4">
|
||||||
<label for="simpleDockerfile">Dockerfile</label>
|
<label for="simpleDockerfile">Dockerfile</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const response = await get(`/applications/${id}`);
|
const response = await get(`/applications/${id}`);
|
||||||
application = response.application;
|
application = response.application;
|
||||||
if (response.application.dockerComposeFile) {
|
if (response.application.dockerComposeFile && application.buildPack === 'compose') {
|
||||||
services = normalizeDockerServices(
|
services = normalizeDockerServices(
|
||||||
JSON.parse(response.application.dockerComposeFile).services
|
JSON.parse(response.application.dockerComposeFile).services
|
||||||
);
|
);
|
||||||
@@ -51,16 +51,22 @@
|
|||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
if (logsLoading) return;
|
if (logsLoading) return;
|
||||||
try {
|
try {
|
||||||
const newLogs: any = await get(
|
const since = lastLog?.split(' ')[0] || 0;
|
||||||
`/applications/${id}/logs/${selectedService}?since=${lastLog?.split(' ')[0] || 0}`
|
const newLogs: any = await get(`/applications/${id}/logs/${selectedService}?since=${since}`);
|
||||||
);
|
|
||||||
if (newLogs.noContainer) {
|
if (newLogs.noContainer) {
|
||||||
noContainer = true;
|
noContainer = true;
|
||||||
} else {
|
} else {
|
||||||
noContainer = false;
|
noContainer = false;
|
||||||
}
|
}
|
||||||
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
if (newLogs?.logs && newLogs.logs[newLogs.logs.length - 1] !== logs[logs.length - 1]) {
|
||||||
logs = logs.concat(newLogs.logs);
|
if (since === 0) {
|
||||||
|
logs = logs.concat(newLogs.logs);
|
||||||
|
} else {
|
||||||
|
const newParsedLogs = newLogs.logs.filter((log: any) => {
|
||||||
|
return log !== logs[logs.length - 1];
|
||||||
|
});
|
||||||
|
logs = logs.concat(newParsedLogs);
|
||||||
|
}
|
||||||
lastLog = newLogs.logs[newLogs.logs.length - 1];
|
lastLog = newLogs.logs[newLogs.logs.length - 1];
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -111,7 +117,7 @@
|
|||||||
<div class="title font-bold pb-3">Application Logs</div>
|
<div class="title font-bold pb-3">Application Logs</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 lg:gap-8 pb-4">
|
<div class="grid grid-cols-3 gap-2 lg:gap-8 pb-4">
|
||||||
{#each services as service}
|
{#each services as service}
|
||||||
<button
|
<button
|
||||||
on:click={() => selectService(service, true)}
|
on:click={() => selectService(service, true)}
|
||||||
@@ -135,7 +141,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<div class="flex justify-start sticky space-x-2 pb-2">
|
<div class="flex justify-start sticky space-x-2 pb-2">
|
||||||
<button on:click={followBuild} class="btn btn-sm " class:bg-coollabs={followingLogs}>
|
<button on:click={followBuild} class="btn btn-sm" class:bg-coollabs={followingLogs}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-6 h-6 mr-2"
|
class="w-6 h-6 mr-2"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { asyncSleep, errorNotification, getRndInteger } from '$lib/common';
|
import { asyncSleep, errorNotification, getRndInteger } from '$lib/common';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast, appSession } from '$lib/store';
|
||||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||||
|
|
||||||
@@ -264,6 +264,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
id="restart"
|
id="restart"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
on:click={() => restartPreview(preview)}
|
on:click={() => restartPreview(preview)}
|
||||||
type="submit"
|
type="submit"
|
||||||
class="icons bg-transparent text-sm flex items-center space-x-2"
|
class="icons bg-transparent text-sm flex items-center space-x-2"
|
||||||
@@ -286,7 +287,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Tooltip triggeredBy="#restart">Restart (useful to change secrets)</Tooltip>
|
<Tooltip triggeredBy="#restart">Restart (useful to change secrets)</Tooltip>
|
||||||
<button id="forceredeploypreview" class="icons" on:click={() => redeploy(preview)}>
|
<button
|
||||||
|
id="forceredeploypreview"
|
||||||
|
class="icons"
|
||||||
|
disabled={!$appSession.isAdmin}
|
||||||
|
on:click={() => redeploy(preview)}
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-6 h-6"
|
class="w-6 h-6"
|
||||||
@@ -310,7 +316,7 @@
|
|||||||
id="deletepreview"
|
id="deletepreview"
|
||||||
class="icons"
|
class="icons"
|
||||||
class:hover:text-error={!loading.removing}
|
class:hover:text-error={!loading.removing}
|
||||||
disabled={loading.removing}
|
disabled={loading.removing || !$appSession.isAdmin}
|
||||||
on:click={() => removeApplication(preview)}
|
on:click={() => removeApplication(preview)}
|
||||||
><DeleteIcon />
|
><DeleteIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -158,7 +158,7 @@
|
|||||||
id="dockerImage"
|
id="dockerImage"
|
||||||
name="dockerImage"
|
name="dockerImage"
|
||||||
required
|
required
|
||||||
placeholder="coollabsio/coolify:0.0.1"
|
placeholder="ghcr.io/coollabsio/coolify:0.0.1"
|
||||||
bind:value={remoteImage}
|
bind:value={remoteImage}
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-sm btn-primary" type="submit">Revert Now</button>
|
<button class="btn btn-sm btn-primary" type="submit">Revert Now</button>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user