mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 04:59:32 +00:00
Compare commits
345 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 | ||
|
|
0d51b04d79 | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 |
3
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
3
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -2,9 +2,6 @@ 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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
.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
|
||||||
@@ -11,9 +12,10 @@ dist
|
|||||||
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
|
||||||
|
|||||||
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
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -16,31 +16,29 @@
|
|||||||
},
|
},
|
||||||
"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",
|
|
||||||
"axe": "11.0.0",
|
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.2",
|
"bree": "9.1.3",
|
||||||
"cabin": "11.0.1",
|
"cabin": "11.1.1",
|
||||||
"compare-versions": "5.0.1",
|
"compare-versions": "5.0.1",
|
||||||
"csv-parse": "5.3.2",
|
"csv-parse": "5.3.3",
|
||||||
"csvtojson": "2.0.10",
|
"csvtojson": "2.0.10",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.6",
|
"dayjs": "1.11.7",
|
||||||
"dockerode": "3.3.4",
|
"dockerode": "3.3.4",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"execa": "6.1.0",
|
"execa": "6.1.0",
|
||||||
"fastify": "4.10.2",
|
"fastify": "4.11.0",
|
||||||
"fastify-plugin": "4.3.0",
|
"fastify-plugin": "4.3.0",
|
||||||
"fastify-socket.io": "4.0.0",
|
"fastify-socket.io": "4.0.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
@@ -48,36 +46,36 @@
|
|||||||
"is-ip": "5.0.0",
|
"is-ip": "5.0.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"is-port-reachable": "4.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "9.0.0",
|
||||||
"minimist": "^1.2.7",
|
"minimist": "^1.2.7",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"node-os-utils": "1.3.7",
|
"node-os-utils": "1.3.7",
|
||||||
"p-all": "4.0.0",
|
"p-all": "4.0.0",
|
||||||
"p-throttle": "5.0.0",
|
"p-throttle": "5.0.0",
|
||||||
"prisma": "4.6.1",
|
"prisma": "4.8.1",
|
||||||
"public-ip": "6.0.1",
|
"public-ip": "6.0.1",
|
||||||
"pump": "3.0.0",
|
"pump": "3.0.0",
|
||||||
"shell-quote": "^1.7.4",
|
"shell-quote": "^1.7.4",
|
||||||
"socket.io": "4.5.3",
|
"socket.io": "4.5.4",
|
||||||
"ssh-config": "4.1.6",
|
"ssh-config": "4.2.0",
|
||||||
"strip-ansi": "7.0.1",
|
"strip-ansi": "7.0.1",
|
||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.11.9",
|
"@types/node": "18.11.18",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||||
"@typescript-eslint/parser": "5.44.0",
|
"@typescript-eslint/parser": "5.48.1",
|
||||||
"esbuild": "0.15.15",
|
"esbuild": "0.16.16",
|
||||||
"eslint": "8.28.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.6.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.20",
|
"nodemon": "2.0.20",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.8.2",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"tsconfig-paths": "4.1.0",
|
"tsconfig-paths": "4.1.2",
|
||||||
"types-fastify-socket.io": "0.0.1",
|
"types-fastify-socket.io": "0.0.1",
|
||||||
"typescript": "4.9.3"
|
"typescript": "4.9.4"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node prisma/seed.js"
|
"seed": "node prisma/seed.js"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ CREATE TABLE "new_GitSource" (
|
|||||||
"apiUrl" TEXT,
|
"apiUrl" TEXT,
|
||||||
"htmlUrl" TEXT,
|
"htmlUrl" TEXT,
|
||||||
"customPort" INTEGER NOT NULL DEFAULT 22,
|
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||||
|
"customUser" TEXT NOT NULL DEFAULT 'git',
|
||||||
"organization" TEXT,
|
"organization" TEXT,
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" DATETIME NOT NULL,
|
"updatedAt" DATETIME NOT NULL,
|
||||||
@@ -17,7 +18,7 @@ CREATE TABLE "new_GitSource" (
|
|||||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
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
|
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", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
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";
|
DROP TABLE "GitSource";
|
||||||
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||||
@@ -10,11 +10,13 @@ CREATE TABLE "new_ApplicationSettings" (
|
|||||||
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"isDBBranching" 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,
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" DATETIME NOT NULL,
|
"updatedAt" DATETIME NOT NULL,
|
||||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
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", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
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";
|
DROP TABLE "ApplicationSettings";
|
||||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT;
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
|
||||||
|
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
|
||||||
|
|
||||||
-- RedefineTables
|
-- RedefineTables
|
||||||
PRAGMA foreign_keys=OFF;
|
PRAGMA foreign_keys=OFF;
|
||||||
CREATE TABLE "new_ApplicationSettings" (
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
@@ -11,11 +15,13 @@ CREATE TABLE "new_ApplicationSettings" (
|
|||||||
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"isCustomSSL" 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,
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"updatedAt" DATETIME NOT NULL,
|
"updatedAt" DATETIME NOT NULL,
|
||||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
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", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
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";
|
DROP TABLE "ApplicationSettings";
|
||||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
decryptApplication,
|
decryptApplication,
|
||||||
isDev,
|
isDev,
|
||||||
pushToRegistry,
|
pushToRegistry,
|
||||||
executeCommand
|
executeCommand,
|
||||||
|
generateSecrets
|
||||||
} from '../lib/common';
|
} 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';
|
||||||
@@ -68,7 +69,15 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
teams: true
|
teams: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (!application) {
|
||||||
|
await prisma.build.update({
|
||||||
|
where: { id: queueBuild.id },
|
||||||
|
data: {
|
||||||
|
status: 'failed'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
throw new Error('Application not found');
|
||||||
|
}
|
||||||
let {
|
let {
|
||||||
id: buildId,
|
id: buildId,
|
||||||
type,
|
type,
|
||||||
@@ -109,6 +118,9 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
.replace(/\//gi, '-')
|
.replace(/\//gi, '-')
|
||||||
.replace('-app', '')}:${storage.path}`;
|
.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}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@@ -140,33 +152,13 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [`PORT='${port}'`];
|
let envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
(s) => s.name === secret.name && s.isPRMRSecret
|
];
|
||||||
);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry;
|
const { url, username, password } = dockerRegistry;
|
||||||
@@ -179,13 +171,24 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
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: {
|
||||||
@@ -197,7 +200,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
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}`] } : {}),
|
||||||
@@ -215,7 +218,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug: true,
|
debug: true,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
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) {
|
||||||
@@ -252,7 +255,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
applicationId: application.id
|
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 {
|
||||||
@@ -275,7 +278,7 @@ 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({
|
await prisma.build.update({
|
||||||
where: { id: buildId },
|
where: { id: buildId },
|
||||||
data: { status: 'success' }
|
data: { status: 'success' }
|
||||||
@@ -400,6 +403,9 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
.replace(/\//gi, '-')
|
.replace(/\//gi, '-')
|
||||||
.replace('-app', '')}:${storage.path}`;
|
.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}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@@ -425,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;
|
||||||
@@ -438,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,
|
||||||
@@ -619,6 +626,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -648,7 +656,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
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({
|
await prisma.build.update({
|
||||||
@@ -698,45 +706,36 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [`PORT='${port}'`];
|
let envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
(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'));
|
|
||||||
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 {
|
try {
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
const composeVolumes = volumes
|
||||||
} catch (error) {
|
.filter((v) => {
|
||||||
//
|
if (
|
||||||
}
|
!v.startsWith('.') &&
|
||||||
try {
|
!v.startsWith('..') &&
|
||||||
const composeVolumes = volumes.map((volume) => {
|
!v.startsWith('/') &&
|
||||||
return {
|
!v.startsWith('~')
|
||||||
[`${volume.split(':')[0]}`]: {
|
) {
|
||||||
name: volume.split(':')[0]
|
return v;
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
});
|
.map((volume) => {
|
||||||
|
return {
|
||||||
|
[`${volume.split(':')[0]}`]: {
|
||||||
|
name: volume.split(':')[0]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
const composeFile = {
|
const composeFile = {
|
||||||
version: '3.8',
|
version: '3.8',
|
||||||
services: {
|
services: {
|
||||||
@@ -744,7 +743,7 @@ 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],
|
||||||
@@ -763,7 +762,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug,
|
debug,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
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) {
|
||||||
@@ -807,7 +806,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
applicationId: application.id
|
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 {
|
||||||
@@ -828,7 +827,7 @@ 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' } });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
decrypt,
|
decrypt,
|
||||||
encrypt,
|
encrypt,
|
||||||
executeCommand,
|
executeCommand,
|
||||||
|
generateSecrets,
|
||||||
generateTimestamp,
|
generateTimestamp,
|
||||||
getDomain,
|
getDomain,
|
||||||
isARM,
|
isARM,
|
||||||
@@ -340,8 +341,8 @@ export function setDefaultBaseImage(
|
|||||||
};
|
};
|
||||||
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)
|
payload.baseImages = isARM()
|
||||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
: staticVersions;
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
@@ -354,8 +355,8 @@ export function setDefaultBaseImage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
payload.baseImages = isARM()
|
||||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
: staticVersions;
|
: staticVersions;
|
||||||
payload.baseBuildImage = 'node:lts';
|
payload.baseBuildImage = 'node:lts';
|
||||||
@@ -375,18 +376,18 @@ export function setDefaultBaseImage(
|
|||||||
payload.baseImage = 'denoland/deno:latest';
|
payload.baseImage = 'denoland/deno:latest';
|
||||||
}
|
}
|
||||||
if (buildPack === 'php') {
|
if (buildPack === 'php') {
|
||||||
payload.baseImage = isARM(process.arch)
|
payload.baseImage = isARM()
|
||||||
? 'php:8.1-fpm-alpine'
|
? 'php:8.1-fpm-alpine'
|
||||||
: 'webdevops/php-apache:8.2-alpine';
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
payload.baseImages = isARM(process.arch)
|
payload.baseImages = isARM()
|
||||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
: phpVersions;
|
: phpVersions;
|
||||||
}
|
}
|
||||||
if (buildPack === 'laravel') {
|
if (buildPack === 'laravel') {
|
||||||
payload.baseImage = isARM(process.arch)
|
payload.baseImage = isARM()
|
||||||
? 'php:8.1-fpm-alpine'
|
? 'php:8.1-fpm-alpine'
|
||||||
: 'webdevops/php-apache:8.2-alpine';
|
: 'webdevops/php-apache:8.2-alpine';
|
||||||
payload.baseImages = isARM(process.arch)
|
payload.baseImages = isARM()
|
||||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||||
: phpVersions;
|
: phpVersions;
|
||||||
payload.baseBuildImage = 'node:18';
|
payload.baseBuildImage = 'node:18';
|
||||||
@@ -428,7 +429,12 @@ 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 !== '/')
|
if (baseDirectory.endsWith('/') && baseDirectory !== '/')
|
||||||
@@ -653,7 +659,7 @@ 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: {
|
||||||
@@ -701,9 +707,8 @@ export async function buildImage({
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker ${location ? `--config ${location}` : ''} build ${
|
command: `docker ${location ? `--config ${location}` : ''} build ${forceRebuild ? '--no-cache' : ''
|
||||||
forceRebuild ? '--no-cache' : ''
|
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||||
} --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 } });
|
||||||
@@ -787,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) {
|
||||||
@@ -811,39 +803,26 @@ 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 });
|
||||||
@@ -865,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,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';
|
||||||
|
|
||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
@@ -13,19 +14,12 @@ export default async function (data) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
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(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
|
|||||||
@@ -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.2';
|
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
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export async function getTemplates() {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
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(process.arch)
|
isARM: isARM()
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
|
|||||||
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
@@ -1,13 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Ignore files for PNPM, NPM and YARN
|
|
||||||
pnpm-lock.yaml
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
|
||||||
plugins: ['svelte3', '@typescript-eslint'],
|
|
||||||
ignorePatterns: ['*.cjs'],
|
|
||||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
|
||||||
settings: {
|
|
||||||
'svelte3/typescript': () => require('typescript')
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaVersion: 2020
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2017: true,
|
|
||||||
node: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
10
apps/client/.gitignore
vendored
10
apps/client/.gitignore
vendored
@@ -1,10 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
vite.config.js.timestamp-*
|
|
||||||
vite.config.ts.timestamp-*
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
engine-strict=true
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Ignore files for PNPM, NPM and YARN
|
|
||||||
pnpm-lock.yaml
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"useTabs": true,
|
|
||||||
"singleQuote": true,
|
|
||||||
"trailingComma": "none",
|
|
||||||
"printWidth": 100,
|
|
||||||
"plugins": ["prettier-plugin-svelte"],
|
|
||||||
"pluginSearchDirs": ["."],
|
|
||||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# SvelteKit Static site
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "client",
|
|
||||||
"description": "Coolify's SvelteKit UI",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite dev",
|
|
||||||
"build": "vite build && cp -Pr build/ ../../build/public",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"test": "playwright test",
|
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
|
||||||
"format": "prettier --plugin-search-dir . --write ."
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@playwright/test": "1.28.1",
|
|
||||||
"@sveltejs/adapter-static": "1.0.0-next.48",
|
|
||||||
"@sveltejs/kit": "1.0.0-next.572",
|
|
||||||
"@types/js-cookie": "3.0.2",
|
|
||||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
|
||||||
"@typescript-eslint/parser": "5.44.0",
|
|
||||||
"autoprefixer": "10.4.13",
|
|
||||||
"eslint": "8.28.0",
|
|
||||||
"eslint-config-prettier": "8.5.0",
|
|
||||||
"eslint-plugin-svelte3": "4.0.0",
|
|
||||||
"postcss": "8.4.19",
|
|
||||||
"postcss-load-config": "4.0.1",
|
|
||||||
"prettier": "2.8.0",
|
|
||||||
"prettier-plugin-svelte": "2.8.1",
|
|
||||||
"svelte": "3.53.1",
|
|
||||||
"svelte-check": "2.9.2",
|
|
||||||
"svelte-preprocess": "^4.10.7",
|
|
||||||
"tailwindcss": "3.2.4",
|
|
||||||
"tslib": "2.4.1",
|
|
||||||
"typescript": "4.9.3",
|
|
||||||
"vite": "3.2.4"
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"@trpc/client": "10.1.0",
|
|
||||||
"@trpc/server": "10.1.0",
|
|
||||||
"cuid": "2.1.8",
|
|
||||||
"daisyui": "2.41.0",
|
|
||||||
"flowbite-svelte": "0.28.0",
|
|
||||||
"js-cookie": "3.0.1",
|
|
||||||
"server": "workspace:*",
|
|
||||||
"superjson": "1.11.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
|
||||||
|
|
||||||
const config: PlaywrightTestConfig = {
|
|
||||||
webServer: {
|
|
||||||
command: 'npm run build && npm run preview',
|
|
||||||
port: 4173
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
1793
apps/client/pnpm-lock.yaml
generated
1793
apps/client/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
const tailwindcss = require('tailwindcss');
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
plugins: [
|
|
||||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
|
||||||
tailwindcss(),
|
|
||||||
//But others, like autoprefixer, need to run after,
|
|
||||||
autoprefixer
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
||||||
9
apps/client/src/app.d.ts
vendored
9
apps/client/src/app.d.ts
vendored
@@ -1,9 +0,0 @@
|
|||||||
// See https://kit.svelte.dev/docs/types#app
|
|
||||||
// for information about these interfaces
|
|
||||||
// and what to do when importing types
|
|
||||||
declare namespace App {
|
|
||||||
// interface Locals {}
|
|
||||||
// interface PageData {}
|
|
||||||
// interface Error {}
|
|
||||||
// interface Platform {}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
|
||||||
%sveltekit.head%
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="h-screen">%sveltekit.body%</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
/* Write your global styles here, in PostCSS syntax */
|
|
||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-regular.woff2') format('woff2'),
|
|
||||||
url('/poppins-v19-latin-ext_latin_devanagari-regular.woff') format('woff');
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-500.woff2') format('woff2'),
|
|
||||||
url('/poppins-v19-latin-ext_latin_devanagari-500.woff') format('woff');
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@apply text-sm !important;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
@apply h-full min-h-full overflow-y-scroll;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply min-h-screen overflow-x-hidden bg-coolblack text-sm text-white scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
.input {
|
|
||||||
@apply h-12 w-96 rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent disabled:bg-coolblack md:text-sm;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
@apply min-w-[14rem] rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent md:text-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
#svelte .custom-select-wrapper .selectContainer.disabled input {
|
|
||||||
@apply placeholder:text-stone-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
#svelte .custom-select-wrapper .selectContainer input {
|
|
||||||
@apply text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#svelte .custom-select-wrapper .selectContainer {
|
|
||||||
@apply h-12 rounded bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
#svelte .listContainer {
|
|
||||||
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
|
|
||||||
}
|
|
||||||
#svelte .selectedItem {
|
|
||||||
@apply pl-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#svelte .item.hover {
|
|
||||||
@apply bg-coollabs text-white !important;
|
|
||||||
}
|
|
||||||
#svelte .item.active {
|
|
||||||
@apply bg-coolgray-100 text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
|
|
||||||
}
|
|
||||||
.custom-select-wrapper {
|
|
||||||
--background: rgb(32 32 32);
|
|
||||||
--inputColor: white;
|
|
||||||
--multiItemPadding: 0;
|
|
||||||
--multiSelectPadding: 0 0.5rem 0 0.5rem;
|
|
||||||
--border: none;
|
|
||||||
--placeholderColor: rgb(87 83 78);
|
|
||||||
--listBackground: rgb(32 32 32);
|
|
||||||
--itemColor: white;
|
|
||||||
--itemHoverBG: rgb(107 22 237);
|
|
||||||
--multiItemBG: rgb(32 32 32);
|
|
||||||
--multiClearHoverBG: transparent;
|
|
||||||
--multiClearHoverFill: rgb(239 68 68);
|
|
||||||
--multiItemActiveBG: transparent;
|
|
||||||
--multiClearBG: transparent;
|
|
||||||
--clearSelectFocusColor: white;
|
|
||||||
--clearSelectHoverColor: rgb(239 68 68);
|
|
||||||
--multiItemBorderRadius: 0.25rem;
|
|
||||||
--listShadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
@apply inline-block;
|
|
||||||
}
|
|
||||||
.btn {
|
|
||||||
@apply text-white text-base min-w-fit no-animation;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
@apply underline hover:text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
@apply p-2 px-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@apply text-lg lg:text-2xl font-bold;
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
@apply text-lg lg:text-xl font-bold text-indigo-300;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
@apply text-sm leading-6 font-semibold text-sky-500 dark:text-sky-400;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
@apply border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2 sticky top-4 mb-2 items-center;
|
|
||||||
}
|
|
||||||
.icon-holder {
|
|
||||||
overflow: hidden;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 8px;
|
|
||||||
background: linear-gradient(0deg, #999, #ddd);
|
|
||||||
}
|
|
||||||
.instance-status-running {
|
|
||||||
box-shadow: 1px 4px 5px #3df721;
|
|
||||||
}
|
|
||||||
.instance-status-stopped {
|
|
||||||
box-shadow: 1px 4px 5px rgb(110, 191, 225);
|
|
||||||
}
|
|
||||||
.instance-status-error {
|
|
||||||
box-shadow: 1px 4px 5px #fb00ff;
|
|
||||||
}
|
|
||||||
.instance-status-degraded {
|
|
||||||
box-shadow: 1px 4px 5px #f7b121;
|
|
||||||
}
|
|
||||||
.badge-status-healthy,
|
|
||||||
.badge-status-running {
|
|
||||||
@apply text-green-500;
|
|
||||||
}
|
|
||||||
.badge-status-degraded {
|
|
||||||
@apply text-green-500;
|
|
||||||
}
|
|
||||||
.badge-status-stopped {
|
|
||||||
@apply text-sky-500;
|
|
||||||
}
|
|
||||||
.delete-button {
|
|
||||||
@apply bg-red-600;
|
|
||||||
}
|
|
||||||
.delete-button:hover {
|
|
||||||
@apply bg-red-500;
|
|
||||||
}
|
|
||||||
/* Interchange menu position */
|
|
||||||
.menu-left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.menu-left .menu-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.menu-left .menu-bar > * {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.menu-top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.menu-top .menu-bar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.menu-top .menu-bar > * {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-main {
|
|
||||||
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-hidden border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-side {
|
|
||||||
@apply absolute right-0 top-0 z-50 m-5 flex flex-wrap items-center justify-end space-x-2 bg-coolblack/40 text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon {
|
|
||||||
@apply rounded p-1 transition duration-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icons {
|
|
||||||
@apply rounded p-2 transition duration-200 hover:bg-coolgray-500 disabled:bg-coolblack disabled:text-coolgray-500 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-right-applications {
|
|
||||||
@apply -ml-6 px-2 font-bold text-green-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gradient {
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
-o-border-image: linear-gradient(
|
|
||||||
0.25turn,
|
|
||||||
rgba(255, 249, 34),
|
|
||||||
rgba(255, 0, 128),
|
|
||||||
rgba(56, 2, 155, 0)
|
|
||||||
);
|
|
||||||
border-image: linear-gradient(
|
|
||||||
0.25turn,
|
|
||||||
rgba(255, 249, 34),
|
|
||||||
rgba(255, 0, 128),
|
|
||||||
rgba(56, 2, 155, 0)
|
|
||||||
);
|
|
||||||
border-image-slice: 1;
|
|
||||||
}
|
|
||||||
.border-gradient-full {
|
|
||||||
border: 4px solid transparent;
|
|
||||||
-o-border-image: linear-gradient(
|
|
||||||
0.25turn,
|
|
||||||
rgba(255, 249, 34),
|
|
||||||
rgba(255, 0, 128),
|
|
||||||
rgba(56, 2, 155, 0)
|
|
||||||
);
|
|
||||||
border-image: linear-gradient(
|
|
||||||
0.25turn,
|
|
||||||
rgba(255, 249, 34),
|
|
||||||
rgba(255, 0, 128),
|
|
||||||
rgba(56, 2, 155, 0)
|
|
||||||
);
|
|
||||||
border-image-slice: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box-selection {
|
|
||||||
@apply min-w-[16rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lds-heart {
|
|
||||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
|
||||||
}
|
|
||||||
@keyframes lds-heart {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
5% {
|
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
39% {
|
|
||||||
transform: scale(0.85);
|
|
||||||
}
|
|
||||||
45% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
60% {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-menu {
|
|
||||||
@apply w-48 text-base font-bold hover:bg-coolgray-500 rounded p-2 hover:text-white text-stone-200 cursor-pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-menu-active {
|
|
||||||
@apply bg-coolgray-500 text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table tbody td,
|
|
||||||
.table tbody th,
|
|
||||||
.table thead th {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.table * {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@apply flex flex-row z-10 w-full py-5 px-5;
|
|
||||||
}
|
|
||||||
.burger {
|
|
||||||
@apply block m-[2px] h-[3px] w-5 rounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-coollabs-gradient {
|
|
||||||
@apply bg-gradient-to-r from-purple-500 via-pink-500 to-red-500;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { addToast } from './store';
|
|
||||||
|
|
||||||
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
|
|
||||||
export function errorNotification(error: any | { message: string }): void {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
addToast({
|
|
||||||
message: error.message,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addToast({
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function getRndInteger(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
export let type = 'info';
|
|
||||||
function success() {
|
|
||||||
if (type === 'success') {
|
|
||||||
return 'bg-dark lg:bg-primary';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
on:click={() => dispatch('click')}
|
|
||||||
on:mouseover={() => dispatch('pause')}
|
|
||||||
on:focus={() => dispatch('pause')}
|
|
||||||
on:mouseout={() => dispatch('resume')}
|
|
||||||
on:blur={() => dispatch('resume')}
|
|
||||||
class={` flex flex-row justify-center alert shadow-lg text-white hover:scale-105 transition-all duration-100 cursor-pointer rounded ${success()}`}
|
|
||||||
class:alert-error={type === 'error'}
|
|
||||||
class:alert-info={type === 'info'}
|
|
||||||
>
|
|
||||||
{#if type === 'success'}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
{:else if type === 'error'}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
{:else if type === 'info'}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="stroke-current flex-shrink-0 w-6 h-6"
|
|
||||||
><path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Toast from './Toast.svelte';
|
|
||||||
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $toasts.length > 0}
|
|
||||||
<section>
|
|
||||||
<article class="toast toast-top toast-center rounded-none w-2/3 lg:w-[20rem]" role="alert">
|
|
||||||
{#each $toasts as toast (toast.id)}
|
|
||||||
<Toast
|
|
||||||
type={toast.type}
|
|
||||||
on:resume={() => resumeToast(toast.id)}
|
|
||||||
on:pause={() => pauseToast(toast.id)}
|
|
||||||
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
|
|
||||||
>
|
|
||||||
{/each}
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
section {
|
|
||||||
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Tooltip } from 'flowbite-svelte';
|
|
||||||
export let placement = 'bottom';
|
|
||||||
export let color = 'bg-coollabs';
|
|
||||||
export let triggeredBy = '#tooltip-default';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Tooltip {triggeredBy} {placement} arrow={false} defaultClass={color + ' font-thin text-xs text-left border-none p-2'} style="custom"
|
|
||||||
><slot /></Tooltip
|
|
||||||
>
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { dev } from '$app/environment';
|
|
||||||
import {
|
|
||||||
addToast,
|
|
||||||
appSession,
|
|
||||||
features,
|
|
||||||
updateLoading,
|
|
||||||
isUpdateAvailable,
|
|
||||||
latestVersion
|
|
||||||
} from '$lib/store';
|
|
||||||
import { asyncSleep, errorNotification } from '$lib/common';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import Tooltip from './Tooltip.svelte';
|
|
||||||
|
|
||||||
let updateStatus: any = {
|
|
||||||
found: false,
|
|
||||||
loading: false,
|
|
||||||
success: null
|
|
||||||
};
|
|
||||||
async function update() {
|
|
||||||
updateStatus.loading = true;
|
|
||||||
try {
|
|
||||||
if (dev) {
|
|
||||||
localStorage.setItem('lastVersion', $appSession.version);
|
|
||||||
await asyncSleep(1000);
|
|
||||||
updateStatus.loading = false;
|
|
||||||
return window.location.reload();
|
|
||||||
} else {
|
|
||||||
localStorage.setItem('lastVersion', $appSession.version);
|
|
||||||
// await post(`/update`, { type: 'update', latestVersion: $latestVersion });
|
|
||||||
addToast({
|
|
||||||
message: 'Update completed.<br><br>Waiting for the new version to start...',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
|
|
||||||
let reachable = false;
|
|
||||||
let tries = 0;
|
|
||||||
do {
|
|
||||||
await asyncSleep(4000);
|
|
||||||
try {
|
|
||||||
// await get(`/undead`);
|
|
||||||
reachable = true;
|
|
||||||
} catch (error) {
|
|
||||||
reachable = false;
|
|
||||||
}
|
|
||||||
if (reachable) break;
|
|
||||||
tries++;
|
|
||||||
} while (!reachable || tries < 120);
|
|
||||||
addToast({
|
|
||||||
message: 'New version reachable. Reloading...',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
updateStatus.loading = false;
|
|
||||||
updateStatus.success = true;
|
|
||||||
await asyncSleep(3000);
|
|
||||||
return window.location.reload();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
updateStatus.success = false;
|
|
||||||
updateStatus.loading = false;
|
|
||||||
return errorNotification(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onMount(async () => {
|
|
||||||
if ($appSession.userId) {
|
|
||||||
const overrideVersion = $features.latestVersion;
|
|
||||||
if ($appSession.teamId === '0') {
|
|
||||||
if ($updateLoading === true) return;
|
|
||||||
try {
|
|
||||||
$updateLoading = true;
|
|
||||||
// const data = await get(`/update`);
|
|
||||||
if (overrideVersion || data?.isUpdateAvailable) {
|
|
||||||
$latestVersion = overrideVersion || data.latestVersion;
|
|
||||||
if (overrideVersion) {
|
|
||||||
$isUpdateAvailable = true;
|
|
||||||
} else {
|
|
||||||
$isUpdateAvailable = data.isUpdateAvailable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return errorNotification(error);
|
|
||||||
} finally {
|
|
||||||
$updateLoading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="py-0 lg:py-2">
|
|
||||||
{#if $appSession.teamId === '0'}
|
|
||||||
{#if $isUpdateAvailable}
|
|
||||||
<button
|
|
||||||
id="update"
|
|
||||||
disabled={updateStatus.success === false}
|
|
||||||
on:click={update}
|
|
||||||
class="icons bg-coollabs-gradient text-white duration-75 hover:scale-105 w-full"
|
|
||||||
>
|
|
||||||
{#if updateStatus.loading}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="lds-heart h-8 w-8 mx-auto"
|
|
||||||
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" />
|
|
||||||
<path
|
|
||||||
d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else if updateStatus.success === null}
|
|
||||||
<div class="flex items-center justify-center space-x-2">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-8 w-8"
|
|
||||||
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" />
|
|
||||||
<circle cx="12" cy="12" r="9" />
|
|
||||||
<line x1="12" y1="8" x2="8" y2="12" />
|
|
||||||
<line x1="12" y1="8" x2="12" y2="16" />
|
|
||||||
<line x1="16" y1="12" x2="12" y2="8" />
|
|
||||||
</svg>
|
|
||||||
<span class="flex lg:hidden">Update available</span>
|
|
||||||
</div>
|
|
||||||
{:else if updateStatus.success}
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-8 w-8"
|
|
||||||
><path
|
|
||||||
fill="#DD2E44"
|
|
||||||
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
|
||||||
/><path
|
|
||||||
fill="#EA596E"
|
|
||||||
d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"
|
|
||||||
/><path
|
|
||||||
fill="#A0041E"
|
|
||||||
d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"
|
|
||||||
/><path
|
|
||||||
fill="#AA8DD8"
|
|
||||||
d="M18.59 13.609c-.199.161-.459.245-.734.215-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532-.549-.06-.947-.552-.886-1.102.059-.549.55-.946 1.101-.886 2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532.548.06.947.553.886 1.102-.028.274-.167.511-.366.671z"
|
|
||||||
/><path
|
|
||||||
fill="#77B255"
|
|
||||||
d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095.53-.149 1.084.16 1.233.691.148.532-.161 1.084-.693 1.234-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096-.532.15-1.083-.16-1.233-.691-.149-.53.161-1.082.693-1.232z"
|
|
||||||
/><path
|
|
||||||
fill="#AA8DD8"
|
|
||||||
d="M23.001 20.16c-.294 0-.584-.129-.782-.375-.345-.432-.274-1.061.156-1.406.218-.175 5.418-4.259 12.767-3.208.547.078.927.584.849 1.131-.078.546-.58.93-1.132.848-6.493-.922-11.187 2.754-11.233 2.791-.186.148-.406.219-.625.219z"
|
|
||||||
/><path
|
|
||||||
fill="#77B255"
|
|
||||||
d="M5.754 16c-.095 0-.192-.014-.288-.042-.529-.159-.829-.716-.67-1.245 1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071.042.551-.372 1.031-.922 1.072-.559.034-1.031-.372-1.072-.923-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192-.13.433-.528.712-.958.712z"
|
|
||||||
/><circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5" /><circle
|
|
||||||
fill="#9266CC"
|
|
||||||
cx="2"
|
|
||||||
cy="18"
|
|
||||||
r="2"
|
|
||||||
/><circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5" /><circle
|
|
||||||
fill="#5C913B"
|
|
||||||
cx="23.5"
|
|
||||||
cy="31.5"
|
|
||||||
r="1.5"
|
|
||||||
/><circle fill="#FFCC4D" cx="28" cy="4" r="2" /><circle
|
|
||||||
fill="#FFCC4D"
|
|
||||||
cx="32.5"
|
|
||||||
cy="8.5"
|
|
||||||
r="1.5"
|
|
||||||
/><circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5" /><circle
|
|
||||||
fill="#FFCC4D"
|
|
||||||
cx="7.5"
|
|
||||||
cy="23.5"
|
|
||||||
r="1.5"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
|
||||||
><path
|
|
||||||
fill="#FFCC4D"
|
|
||||||
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
|
||||||
/><path
|
|
||||||
fill="#664500"
|
|
||||||
d="M22 27c0 2.763-1.791 3-4 3-2.21 0-4-.237-4-3 0-2.761 1.79-6 4-6 2.209 0 4 3.239 4 6zm8-12c-.124 0-.25-.023-.371-.072-5.229-2.091-7.372-5.241-7.461-5.374-.307-.46-.183-1.081.277-1.387.459-.306 1.077-.184 1.385.274.019.027 1.93 2.785 6.541 4.629.513.206.763.787.558 1.3-.157.392-.533.63-.929.63zM6 15c-.397 0-.772-.238-.929-.629-.205-.513.044-1.095.557-1.3 4.612-1.844 6.523-4.602 6.542-4.629.308-.456.929-.577 1.387-.27.457.308.581.925.275 1.383-.089.133-2.232 3.283-7.46 5.374C6.25 14.977 6.124 15 6 15z"
|
|
||||||
/><path fill="#5DADEC" d="M24 16h4v19l-4-.046V16zM8 35l4-.046V16H8v19z" /><path
|
|
||||||
fill="#664500"
|
|
||||||
d="M14.999 18c-.15 0-.303-.034-.446-.105-3.512-1.756-7.07-.018-7.105 0-.495.249-1.095.046-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.498-2.197 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552zm14 0c-.15 0-.303-.034-.446-.105-3.513-1.756-7.07-.018-7.105 0-.494.248-1.094.047-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.501-2.196 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552z"
|
|
||||||
/><ellipse fill="#5DADEC" cx="18" cy="34" rx="18" ry="2" /><ellipse
|
|
||||||
fill="#E75A70"
|
|
||||||
cx="18"
|
|
||||||
cy="27"
|
|
||||||
rx="3"
|
|
||||||
ry="2"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
<Tooltip triggeredBy="#update" placement="right" color="bg-coolgray-200 text-white"
|
|
||||||
>New Version Available!</Tooltip
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<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" />
|
|
||||||
<line x1="4" y1="7" x2="20" y2="7" />
|
|
||||||
<line x1="10" y1="11" x2="10" y2="17" />
|
|
||||||
<line x1="14" y1="11" x2="14" y2="17" />
|
|
||||||
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
|
|
||||||
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 486 B |
@@ -1,10 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-3 h-3 text-white"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 262 B |
@@ -1,47 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import * as Icons from '$lib/components/icons/applications';
|
|
||||||
export let application: any;
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if application.buildPack?.toLowerCase() === 'rust'}
|
|
||||||
<Icons.Rust {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'node'}
|
|
||||||
<Icons.Nodejs {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'react'}
|
|
||||||
<Icons.React {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'svelte'}
|
|
||||||
<Icons.Svelte {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'vuejs'}
|
|
||||||
<Icons.Vuejs {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'php'}
|
|
||||||
<Icons.Php {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'python'}
|
|
||||||
<Icons.Python {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'static'}
|
|
||||||
<Icons.Static {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'nestjs'}
|
|
||||||
<Icons.Nestjs {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'nuxtjs'}
|
|
||||||
<Icons.Nuxtjs {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'nextjs'}
|
|
||||||
<Icons.Nextjs {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'gatsby'}
|
|
||||||
<Icons.Gatsby {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'docker'}
|
|
||||||
<Icons.Docker {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'astro'}
|
|
||||||
<Icons.Astro {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'eleventy'}
|
|
||||||
<Icons.Eleventy {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'deno'}
|
|
||||||
<Icons.Deno {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'laravel'}
|
|
||||||
<Icons.Laravel {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'heroku'}
|
|
||||||
<Icons.Heroku {isAbsolute} />
|
|
||||||
{:else if application.buildPack?.toLowerCase() === 'compose'}
|
|
||||||
<Icons.Compose {isAbsolute} />
|
|
||||||
{:else if application.simpleDockerfile}
|
|
||||||
<Icons.Docker {isAbsolute} />
|
|
||||||
{/if}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
|
||||||
viewBox="0 0 256 256"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
id="a"
|
|
||||||
fill="#302649"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
id="flame"
|
|
||||||
fill="#EF661E"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<img
|
|
||||||
alt="docker compose logo"
|
|
||||||
class={isAbsolute ? 'w-16 h-16 absolute top-0 left-0 -m-8' : 'w-8 h-8 mx-auto'}
|
|
||||||
src="/icons/compose.png"
|
|
||||||
/>
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<svg viewBox="0 0 128 128" class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12' : 'mx-auto w-10 h-10'}>
|
|
||||||
<path d="M124.8 52.1c-4.3-2.5-10-2.8-14.8-1.4-.6-5.2-4-9.7-8-12.9l-1.6-1.3-1.4 1.6c-2.7 3.1-3.5 8.3-3.1 12.3.3 2.9 1.2 5.9 3 8.3-1.4.8-2.9 1.9-4.3 2.4-2.8 1-5.9 2-8.9 2H79V49H66V24H51v12H26v13H13v14H1.8l-.2 1.5c-.5 6.4.3 12.6 3 18.5l1.1 2.2.1.2c7.9 13.4 21.7 19 36.8 19 29.2 0 53.3-13.1 64.3-40.6 7.4.4 15-1.8 18.6-8.9l.9-1.8-1.6-1zM28 39h10v11H28V39zm13.1 44.2c0 1.7-1.4 3.1-3.1 3.1-1.7 0-3.1-1.4-3.1-3.1 0-1.7 1.4-3.1 3.1-3.1 1.7.1 3.1 1.4 3.1 3.1zM28 52h10v11H28V52zm-13 0h11v11H15V52zm27.7 50.2c-15.8-.1-24.3-5.4-31.3-12.4 2.1.1 4.1.2 5.9.2 1.6 0 3.2 0 4.7-.1 3.9-.2 7.3-.7 10.1-1.5 2.3 5.3 6.5 10.2 14 13.8h-3.4zM51 63H40V52h11v11zm0-13H40V39h11v11zm13 13H53V52h11v11zm0-13H53V39h11v11zm0-13H53V26h11v11zm13 26H66V52h11v11zM38.8 81.2c-.2-.1-.5-.2-.8-.2-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2s2.2-1 2.2-2.2c0-.3-.1-.6-.2-.8-.2.3-.4.5-.8.5-.5 0-.9-.4-.9-.9.1-.4.3-.7.5-.8z" fill="#019BC6"></path>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 128 128"
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
|
|
||||||
>
|
|
||||||
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
|
||||||
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
|
||||||
fill="#FFF"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
|
||||||
viewBox="0 0 128 128"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="#64328B"
|
|
||||||
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
|
|
||||||
viewBox="0 0 72 80"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="#430098"
|
|
||||||
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
|
||||||
viewBox="0 0 50 52"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
><title>Logomark</title><path
|
|
||||||
d="M49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1-.402.694l-9.209 5.302V39.25c0 .286-.152.55-.4.694L20.42 51.01c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1-.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054L.402 39.944A.801.801 0 0 1 0 39.25V6.334c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216l17.62-10.144zM1.602 7.719v31.068L19.22 48.93v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-.002-21.481L4.965 9.654 1.602 7.72zm8.81-5.994L2.405 6.334l8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764l4.645-2.674V7.719l-3.363 1.936-4.646 2.675v20.096l3.364-1.937zM39.243 7.164l-8.006 4.609 8.006 4.609 8.005-4.61-8.005-4.608zm-.801 10.605l-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937v-9.124zM20.02 38.33l11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833 7.993 4.524z"
|
|
||||||
fill="#FF2D20"
|
|
||||||
fill-rule="evenodd"
|
|
||||||
/></svg
|
|
||||||
>
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,14 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
class={isAbsolute
|
|
||||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
|
|
||||||
: 'mx-auto w-8 h-8 fill-current text-blue-500'}
|
|
||||||
viewBox="0 0 128 128"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let isAbsolute = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svg
|
|
||||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
aria-hidden="true"
|
|
||||||
focusable="false"
|
|
||||||
data-prefix="fab"
|
|
||||||
data-icon="node-js"
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
><path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M224 508c-6.7 0-13.5-1.8-19.4-5.2l-61.7-36.5c-9.2-5.2-4.7-7-1.7-8 12.3-4.3 14.8-5.2 27.9-12.7 1.4-.8 3.2-.5 4.6.4l47.4 28.1c1.7 1 4.1 1 5.7 0l184.7-106.6c1.7-1 2.8-3 2.8-5V149.3c0-2.1-1.1-4-2.9-5.1L226.8 37.7c-1.7-1-4-1-5.7 0L36.6 144.3c-1.8 1-2.9 3-2.9 5.1v213.1c0 2 1.1 4 2.9 4.9l50.6 29.2c27.5 13.7 44.3-2.4 44.3-18.7V167.5c0-3 2.4-5.3 5.4-5.3h23.4c2.9 0 5.4 2.3 5.4 5.3V378c0 36.6-20 57.6-54.7 57.6-10.7 0-19.1 0-42.5-11.6l-48.4-27.9C8.1 389.2.7 376.3.7 362.4V149.3c0-13.8 7.4-26.8 19.4-33.7L204.6 9c11.7-6.6 27.2-6.6 38.8 0l184.7 106.7c12 6.9 19.4 19.8 19.4 33.7v213.1c0 13.8-7.4 26.7-19.4 33.7L243.4 502.8c-5.9 3.4-12.6 5.2-19.4 5.2zm149.1-210.1c0-39.9-27-50.5-83.7-58-57.4-7.6-63.2-11.5-63.2-24.9 0-11.1 4.9-25.9 47.4-25.9 37.9 0 51.9 8.2 57.7 33.8.5 2.4 2.7 4.2 5.2 4.2h24c1.5 0 2.9-.6 3.9-1.7s1.5-2.6 1.4-4.1c-3.7-44.1-33-64.6-92.2-64.6-52.7 0-84.1 22.2-84.1 59.5 0 40.4 31.3 51.6 81.8 56.6 60.5 5.9 65.2 14.8 65.2 26.7 0 20.6-16.6 29.4-55.5 29.4-48.9 0-59.6-12.3-63.2-36.6-.4-2.6-2.6-4.5-5.3-4.5h-23.9c-3 0-5.3 2.4-5.3 5.3 0 31.1 16.9 68.2 97.8 68.2 58.4-.1 92-23.2 92-63.4z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user