mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 12:33:35 +00:00
Compare commits
223 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 |
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
|
|
||||||
91
.github/workflows/pocketbase-release.yml
vendored
91
.github/workflows/pocketbase-release.yml
vendored
@@ -1,91 +0,0 @@
|
|||||||
name: pocketbase-release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- "others/pocketbase/*"
|
|
||||||
- ".github/workflows/pocketbase-release.yml"
|
|
||||||
|
|
||||||
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.11.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.11.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.11.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.11.0 --amend coollabsio/pocketbase:0.11.0-amd64 --amend coollabsio/pocketbase:0.11.0-arm64 --amend coollabsio/pocketbase:0.11.0-aarch64
|
|
||||||
docker manifest push coollabsio/pocketbase:0.11.0
|
|
||||||
108
.github/workflows/production-release.yml
vendored
108
.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,20 +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 create coollabsio/coolify:latest --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
|
||||||
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
|
||||||
docker manifest push coollabsio/coolify:latest
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
90
.github/workflows/release-candidate.yml
vendored
90
.github/workflows/release-candidate.yml
vendored
@@ -1,90 +0,0 @@
|
|||||||
name: release-candidate
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [prereleased]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
arm64-making-something-cool:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{github.event.release.name}}-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64,mode=max
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
amd64-making-something-cool:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:${{github.event.release.name}}-amd64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64,mode=max
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
|
||||||
merge-manifest-to-be-cool:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [arm64-making-something-cool, amd64-making-something-cool]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Create & publish manifest
|
|
||||||
run: |
|
|
||||||
docker manifest create coollabsio/coolify:${{github.event.release.name}} --amend coollabsio/coolify:${{github.event.release.name}}-amd64 --amend coollabsio/coolify:${{github.event.release.name}}-arm64
|
|
||||||
docker manifest push coollabsio/coolify:${{github.event.release.name}}
|
|
||||||
|
|
||||||
117
.github/workflows/staging-release.yml
vendored
117
.github/workflows/staging-release.yml
vendored
@@ -1,76 +1,76 @@
|
|||||||
name: staging-release
|
name: Staging Release to ghcr.io
|
||||||
|
concurrency:
|
||||||
|
group: staging_environment
|
||||||
|
cancel-in-progress: true
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
|
||||||
- "**"
|
|
||||||
- "!others/fluentbit"
|
|
||||||
- "!others/pocketbase"
|
|
||||||
- "!.github/workflows/fluent-bit-release.yml"
|
|
||||||
- "!.github/workflows/pocketbase-release.yml"
|
|
||||||
branches:
|
branches:
|
||||||
- next
|
- "v3"
|
||||||
|
env:
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
IMAGE_NAME: "coollabsio/coolify"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
arm64:
|
|
||||||
runs-on: [self-hosted, arm64]
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: "next"
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Get current package version
|
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
|
||||||
id: package-version
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: coollabsio/coolify:next-arm64
|
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
|
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
|
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "next"
|
ref: "v3"
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
- name: Get current package version
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
uses: martinbeentjes/npm-get-version-action@v1.2.3
|
- name: Extract metadata (tags, labels)
|
||||||
id: package-version
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:next-amd64
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
aarch64:
|
||||||
|
runs-on:
|
||||||
|
group: aarch-runners
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "v3"
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract metadata (tags, labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/aarch64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [arm64, amd64]
|
needs: [amd64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -78,15 +78,20 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
- name: Login to DockerHub
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
registry: ${{ env.REGISTRY }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Extract metadata (tags, labels)
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
|
||||||
docker manifest push coollabsio/coolify:next
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.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
|
||||||
@@ -45,4 +45,4 @@ pnpm dev
|
|||||||
|
|
||||||
You should be able to access `http://localhost:3000`.
|
You should be able to access `http://localhost:3000`.
|
||||||
|
|
||||||
1. Click `Register` and setup your first user.
|
1. Click `Register` and setup your first user.
|
||||||
|
|||||||
@@ -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 .
|
||||||
@@ -50,4 +50,4 @@ RUN pnpm install -p
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
ENV CHECKPOINT_DISABLE=1
|
ENV CHECKPOINT_DISABLE=1
|
||||||
CMD pnpm start
|
CMD pnpm start
|
||||||
|
|||||||
@@ -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}
|
||||||
@@ -28,4 +28,4 @@ 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
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
ENV CHECKPOINT_DISABLE=1
|
ENV CHECKPOINT_DISABLE=1
|
||||||
|
|||||||
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)
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -21,13 +21,11 @@
|
|||||||
"@fastify/cors": "8.2.0",
|
"@fastify/cors": "8.2.0",
|
||||||
"@fastify/env": "4.2.0",
|
"@fastify/env": "4.2.0",
|
||||||
"@fastify/jwt": "6.5.0",
|
"@fastify/jwt": "6.5.0",
|
||||||
"@fastify/multipart": "7.3.0",
|
"@fastify/multipart": "7.4.1",
|
||||||
"@fastify/static": "6.6.0",
|
"@fastify/static": "6.6.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@ladjs/graceful": "3.2.1",
|
"@ladjs/graceful": "3.2.1",
|
||||||
"@prisma/client": "4.8.1",
|
"@prisma/client": "4.8.1",
|
||||||
"@sentry/node": "7.30.0",
|
|
||||||
"@sentry/tracing": "7.30.0",
|
|
||||||
"axe": "11.2.1",
|
"axe": "11.2.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.3",
|
"bree": "9.1.3",
|
||||||
|
|||||||
@@ -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" (
|
||||||
@@ -12,11 +16,12 @@ CREATE TABLE "new_ApplicationSettings" (
|
|||||||
"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,
|
"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", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "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[]
|
||||||
@@ -187,6 +189,7 @@ model ApplicationSettings {
|
|||||||
isDBBranching Boolean @default(false)
|
isDBBranching Boolean @default(false)
|
||||||
isCustomSSL Boolean @default(false)
|
isCustomSSL Boolean @default(false)
|
||||||
isHttp2 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])
|
||||||
@@ -195,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())
|
||||||
|
|||||||
@@ -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,14 +1,19 @@
|
|||||||
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 cookie from '@fastify/cookie';
|
||||||
|
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 socketIO from 'fastify-socket.io';
|
||||||
|
import path, { join } from 'path';
|
||||||
import socketIOServer from './realtime';
|
import socketIOServer from './realtime';
|
||||||
|
|
||||||
|
import Graceful from '@ladjs/graceful';
|
||||||
|
import { compareVersions } from 'compare-versions';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
||||||
import {
|
import {
|
||||||
cleanupDockerStorage,
|
cleanupDockerStorage,
|
||||||
createRemoteEngineConfiguration,
|
createRemoteEngineConfiguration,
|
||||||
@@ -18,26 +23,20 @@ import {
|
|||||||
isDev,
|
isDev,
|
||||||
listSettings,
|
listSettings,
|
||||||
prisma,
|
prisma,
|
||||||
sentryDSN,
|
|
||||||
startTraefikProxy,
|
startTraefikProxy,
|
||||||
startTraefikTCPProxy,
|
startTraefikTCPProxy,
|
||||||
version
|
version
|
||||||
} from './lib/common';
|
} from './lib/common';
|
||||||
import { scheduler } from './lib/scheduler';
|
|
||||||
import { compareVersions } from 'compare-versions';
|
|
||||||
import Graceful from '@ladjs/graceful';
|
|
||||||
import yaml from 'js-yaml';
|
|
||||||
import fs from 'fs/promises';
|
|
||||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
|
||||||
import { checkContainer } from './lib/docker';
|
import { checkContainer } from './lib/docker';
|
||||||
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
|
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_SECRET_KEY_BETTER: string | null;
|
||||||
COOLIFY_DATABASE_URL: string;
|
COOLIFY_DATABASE_URL: string;
|
||||||
COOLIFY_IS_ON: string;
|
COOLIFY_IS_ON: string;
|
||||||
COOLIFY_WHITE_LABELED: string;
|
COOLIFY_WHITE_LABELED: string;
|
||||||
@@ -67,6 +66,10 @@ const host = '0.0.0.0';
|
|||||||
COOLIFY_SECRET_KEY: {
|
COOLIFY_SECRET_KEY: {
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
|
COOLIFY_SECRET_KEY_BETTER: {
|
||||||
|
type: 'string',
|
||||||
|
default: null
|
||||||
|
},
|
||||||
COOLIFY_DATABASE_URL: {
|
COOLIFY_DATABASE_URL: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'file:../db/dev.db'
|
default: 'file:../db/dev.db'
|
||||||
@@ -164,13 +167,18 @@ const host = '0.0.0.0';
|
|||||||
// 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 * 15);
|
}, 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();
|
||||||
@@ -180,24 +188,32 @@ const host = '0.0.0.0';
|
|||||||
// 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(
|
setInterval(
|
||||||
async () => {
|
async () => {
|
||||||
await migrateServicesToNewTemplate();
|
await migrateServicesToNewTemplate();
|
||||||
},
|
},
|
||||||
isDev ? 10000 : 60000
|
isDev ? 10000 : 60000 * 10
|
||||||
);
|
);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await copySSLCertificates();
|
await copySSLCertificates();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
|
|
||||||
await Promise.all([getTagsTemplates(), getArch(), getIPAddress(), configureRemoteDockers()]);
|
await Promise.all([
|
||||||
|
getTagsTemplates(),
|
||||||
|
getArch(),
|
||||||
|
getIPAddress(),
|
||||||
|
configureRemoteDockers(),
|
||||||
|
refreshTemplates(),
|
||||||
|
refreshTags()
|
||||||
|
// cleanupStuckedContainers()
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -219,7 +235,7 @@ async function getIPAddress() {
|
|||||||
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');
|
||||||
@@ -231,7 +247,7 @@ async function getTagsTemplates() {
|
|||||||
if (await fs.stat('./testTemplate.yaml')) {
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
try {
|
try {
|
||||||
if (await fs.stat('./testTags.json')) {
|
if (await fs.stat('./testTags.json')) {
|
||||||
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||||
@@ -239,7 +255,7 @@ async function getTagsTemplates() {
|
|||||||
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
|
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
||||||
await fs.writeFile('./tags.json', tags);
|
await fs.writeFile('./tags.json', tags);
|
||||||
@@ -265,9 +281,6 @@ async function initServer() {
|
|||||||
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,
|
||||||
@@ -282,7 +295,7 @@ async function initServer() {
|
|||||||
try {
|
try {
|
||||||
console.log(`[001] Initializing server...`);
|
console.log(`[001] Initializing server...`);
|
||||||
await executeCommand({ command: `docker network create --attachable coolify` });
|
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
try {
|
try {
|
||||||
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);
|
||||||
@@ -292,10 +305,10 @@ async function initServer() {
|
|||||||
data: { status: 'failed' }
|
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);
|
||||||
}
|
}
|
||||||
@@ -308,9 +321,52 @@ async function getArch() {
|
|||||||
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({
|
||||||
@@ -348,14 +404,21 @@ async function autoUpdater() {
|
|||||||
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: `docker pull ${image}` });
|
||||||
|
} 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({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
});
|
});
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
shell: true,
|
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"`
|
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 {
|
||||||
@@ -421,7 +484,7 @@ async function checkProxies() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await createRemoteEngineConfiguration(docker.id);
|
await createRemoteEngineConfiguration(docker.id);
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TCP Proxies
|
// TCP Proxies
|
||||||
@@ -460,7 +523,7 @@ 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() {
|
||||||
@@ -492,7 +555,11 @@ async function copySSLCertificates() {
|
|||||||
} 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,54 +610,61 @@ async function cleanupStorage() {
|
|||||||
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);
|
||||||
let force = false;
|
}
|
||||||
let lowDiskSpace = false;
|
if (destination.remoteIpAddress) {
|
||||||
try {
|
if (!destination.remoteVerified) continue;
|
||||||
let stdout = null;
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
if (!isDev) {
|
}
|
||||||
const output = await executeCommand({
|
await cleanupDockerStorage(destination.id);
|
||||||
dockerId: destination.id,
|
// let lowDiskSpace = false;
|
||||||
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
// try {
|
||||||
shell: true
|
// let stdout = null;
|
||||||
});
|
// if (!isDev) {
|
||||||
stdout = output.stdout;
|
// const output = await executeCommand({
|
||||||
} else {
|
// dockerId: destination.id,
|
||||||
const output = await executeCommand({
|
// command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||||
command: `df -kPT /`
|
// shell: true
|
||||||
});
|
// });
|
||||||
stdout = output.stdout;
|
// stdout = output.stdout;
|
||||||
}
|
// } else {
|
||||||
let lines = stdout.trim().split('\n');
|
// const output = await executeCommand({
|
||||||
let header = lines[0];
|
// command: `df -kPT /`
|
||||||
let regex =
|
// });
|
||||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
// stdout = output.stdout;
|
||||||
const boundaries = [];
|
// }
|
||||||
let match;
|
// 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))) {
|
// while ((match = regex.exec(header))) {
|
||||||
boundaries.push(match[0].length);
|
// boundaries.push(match[0].length);
|
||||||
}
|
// }
|
||||||
|
|
||||||
boundaries[boundaries.length - 1] = -1;
|
// boundaries[boundaries.length - 1] = -1;
|
||||||
const data = lines.slice(1).map((line) => {
|
// const data = lines.slice(1).map((line) => {
|
||||||
const cl = boundaries.map((boundary) => {
|
// const cl = boundaries.map((boundary) => {
|
||||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||||
line = line.slice(boundary);
|
// line = line.slice(boundary);
|
||||||
return column.trim();
|
// return column.trim();
|
||||||
});
|
// });
|
||||||
return {
|
// return {
|
||||||
capacity: Number.parseInt(cl[5], 10) / 100
|
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||||
};
|
// };
|
||||||
});
|
// });
|
||||||
if (data.length > 0) {
|
// if (data.length > 0) {
|
||||||
const { capacity } = data[0];
|
// const { capacity } = data[0];
|
||||||
if (capacity > 0.8) {
|
// if (capacity > 0.8) {
|
||||||
lowDiskSpace = true;
|
// lowDiskSpace = true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} catch (error) {}
|
// } catch (error) {}
|
||||||
await cleanupDockerStorage(destination.id, lowDiskSpace, force);
|
// if (lowDiskSpace) {
|
||||||
|
// await cleanupDockerStorage(destination.id);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,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,
|
||||||
@@ -110,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}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@@ -160,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: {
|
||||||
@@ -233,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 {
|
||||||
@@ -256,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' }
|
||||||
@@ -381,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}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@@ -406,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;
|
||||||
@@ -693,13 +718,24 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
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: {
|
||||||
@@ -770,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 {
|
||||||
@@ -791,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 || '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,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';
|
||||||
@@ -355,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';
|
||||||
@@ -376,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';
|
||||||
@@ -429,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 !== '/')
|
||||||
@@ -702,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 } });
|
||||||
@@ -800,6 +804,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
Dockerfile.push(`RUN ${installCommand}`);
|
Dockerfile.push(`RUN ${installCommand}`);
|
||||||
}
|
}
|
||||||
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 });
|
||||||
}
|
}
|
||||||
@@ -817,6 +822,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
|
|||||||
}
|
}
|
||||||
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 });
|
||||||
@@ -838,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 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ 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) {
|
||||||
@@ -31,61 +33,114 @@ export default async function (data) {
|
|||||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||||
}
|
}
|
||||||
|
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}`;
|
||||||
|
|
||||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
if (value['env_file']) {
|
||||||
if (Object.keys(environment).length > 0) {
|
delete value['env_file'];
|
||||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
|
||||||
}
|
}
|
||||||
value['environment'] = [...environment, ...envs];
|
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'];
|
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||||
if (typeof build === 'string') {
|
if (typeof build === 'string') {
|
||||||
build = { context: build };
|
build = { context: build };
|
||||||
}
|
}
|
||||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||||
let finalArgs = [...buildEnvs];
|
let finalBuildArgs = [...buildEnvs];
|
||||||
if (Object.keys(buildArgs).length > 0) {
|
if (Object.keys(buildArgs).length > 0) {
|
||||||
for (const arg of buildArgs) {
|
for (const arg of Object.keys(buildArgs)) {
|
||||||
const [key, _] = arg.split('=');
|
const [key, _] = arg.split('=');
|
||||||
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
|
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||||
finalArgs.push(arg);
|
finalBuildArgs.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value['build'] = {
|
if (build.length > 0 || buildArgs.length > 0) {
|
||||||
...build,
|
value['build'] = {
|
||||||
args: finalArgs
|
...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) {
|
||||||
@@ -93,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,
|
||||||
@@ -116,7 +178,6 @@ export default async function (data) {
|
|||||||
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));
|
||||||
console.log(yaml.dump(dockerComposeYaml));
|
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug,
|
debug,
|
||||||
buildId,
|
buildId,
|
||||||
|
|||||||
@@ -36,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'));
|
||||||
|
|||||||
@@ -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'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,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'));
|
||||||
|
|||||||
@@ -36,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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,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'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,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'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,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'));
|
||||||
|
|||||||
@@ -31,13 +31,14 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
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.13';
|
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,8 +760,8 @@ 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"' : ''} \
|
${isDev ? '-p "8080:8080"' : ''} \
|
||||||
--name coolify-proxy \
|
--name coolify-proxy \
|
||||||
-d ${defaultTraefikImage} \
|
-d ${defaultTraefikImage} \
|
||||||
@@ -797,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({
|
||||||
@@ -820,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,
|
||||||
@@ -929,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;
|
||||||
@@ -947,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;
|
||||||
@@ -962,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
|
||||||
@@ -983,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,
|
||||||
@@ -1007,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') {
|
||||||
@@ -1025,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;
|
||||||
@@ -1045,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;
|
||||||
@@ -1062,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;
|
||||||
@@ -1095,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;
|
||||||
@@ -1171,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}` });
|
||||||
}
|
}
|
||||||
@@ -1631,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 };
|
||||||
}
|
}
|
||||||
@@ -1695,7 +1743,7 @@ export async function stopBuild(buildId, applicationId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1714,77 +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 (force) {
|
|
||||||
deleteImage.push(image2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
|
|
||||||
deleteImage.push(image2);
|
|
||||||
} else {
|
|
||||||
keepImage.push(image2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const image of deleteImage) {
|
|
||||||
try {
|
|
||||||
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1948,3 +1947,49 @@ export function generateSecrets(
|
|||||||
}
|
}
|
||||||
return envs;
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export async function getImages(request: FastifyRequest<GetImages>) {
|
|||||||
export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) {
|
export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
let applications = await prisma.application.findMany({
|
const 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 }
|
||||||
});
|
});
|
||||||
@@ -167,7 +167,7 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { teamId } = request.user;
|
const { teamId } = request.user;
|
||||||
let payload = [];
|
const payload = [];
|
||||||
const application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
if (application.buildPack === 'compose') {
|
if (application.buildPack === 'compose') {
|
||||||
@@ -398,7 +398,9 @@ export async function saveApplication(
|
|||||||
dockerComposeFileLocation,
|
dockerComposeFileLocation,
|
||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
dockerRegistryImageName
|
dockerRegistryImageName,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
} = request.body;
|
} = request.body;
|
||||||
if (port) port = Number(port);
|
if (port) port = Number(port);
|
||||||
if (exposePort) {
|
if (exposePort) {
|
||||||
@@ -453,6 +455,8 @@ export async function saveApplication(
|
|||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
dockerRegistryImageName,
|
dockerRegistryImageName,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
...defaultConfiguration,
|
...defaultConfiguration,
|
||||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||||
}
|
}
|
||||||
@@ -476,6 +480,8 @@ export async function saveApplication(
|
|||||||
dockerComposeFileLocation,
|
dockerComposeFileLocation,
|
||||||
dockerComposeConfiguration,
|
dockerComposeConfiguration,
|
||||||
simpleDockerfile,
|
simpleDockerfile,
|
||||||
|
basicAuthPw,
|
||||||
|
basicAuthUser,
|
||||||
dockerRegistryImageName,
|
dockerRegistryImageName,
|
||||||
...defaultConfiguration
|
...defaultConfiguration
|
||||||
}
|
}
|
||||||
@@ -499,12 +505,11 @@ export async function saveApplicationSettings(
|
|||||||
previews,
|
previews,
|
||||||
dualCerts,
|
dualCerts,
|
||||||
autodeploy,
|
autodeploy,
|
||||||
branch,
|
|
||||||
projectId,
|
|
||||||
isBot,
|
isBot,
|
||||||
isDBBranching,
|
isDBBranching,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
|
basicAuth,
|
||||||
} = request.body;
|
} = request.body;
|
||||||
await prisma.application.update({
|
await prisma.application.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
|
|||||||
isBot,
|
isBot,
|
||||||
isDBBranching,
|
isDBBranching,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
|
basicAuth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -565,7 +571,7 @@ export async function restartApplication(
|
|||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { imageId = null } = request.body;
|
const { imageId = null } = request.body;
|
||||||
const { teamId } = request.user;
|
const { teamId } = request.user;
|
||||||
let application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
const { id: dockerId, network } = application.destinationDocker;
|
const { id: dockerId, network } = application.destinationDocker;
|
||||||
@@ -640,9 +646,7 @@ export async function restartApplication(
|
|||||||
|
|
||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
buildPack !== 'docker' ? '/app' : ''
|
|
||||||
}${storage.path}`;
|
|
||||||
}) || [];
|
}) || [];
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.map((volume) => {
|
||||||
return {
|
return {
|
||||||
@@ -732,14 +736,15 @@ export async function deleteApplication(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { force } = request.body;
|
|
||||||
|
|
||||||
const { teamId } = request.user;
|
const { teamId } = request.user;
|
||||||
const application = await prisma.application.findUnique({
|
const application = await prisma.application.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { destinationDocker: true }
|
include: { destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
|
if (teamId !== '0' && !application.teams.some((team) => team.id === teamId)) {
|
||||||
|
throw { status: 403, message: 'You are not allowed to delete this application.' };
|
||||||
|
}
|
||||||
|
if (application?.destinationDocker?.id && application.destinationDocker?.network) {
|
||||||
const { stdout: containers } = await executeCommand({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: application.destinationDocker.id,
|
dockerId: application.destinationDocker.id,
|
||||||
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||||
@@ -1340,16 +1345,16 @@ export async function getStorages(request: FastifyRequest<OnlyId>) {
|
|||||||
export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
|
export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { path, newStorage, storageId } = request.body;
|
const { hostPath, path, newStorage, storageId } = request.body;
|
||||||
|
|
||||||
if (newStorage) {
|
if (newStorage) {
|
||||||
await prisma.applicationPersistentStorage.create({
|
await prisma.applicationPersistentStorage.create({
|
||||||
data: { path, application: { connect: { id } } }
|
data: { hostPath, path, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.applicationPersistentStorage.update({
|
await prisma.applicationPersistentStorage.update({
|
||||||
where: { id: storageId },
|
where: { id: storageId },
|
||||||
data: { path }
|
data: { hostPath, path }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
@@ -1376,7 +1381,7 @@ export async function restartPreview(
|
|||||||
try {
|
try {
|
||||||
const { id, pullmergeRequestId } = request.params;
|
const { id, pullmergeRequestId } = request.params;
|
||||||
const { teamId } = request.user;
|
const { teamId } = request.user;
|
||||||
let application: any = await getApplicationFromDB(id, teamId);
|
const application: any = await getApplicationFromDB(id, teamId);
|
||||||
if (application?.destinationDockerId) {
|
if (application?.destinationDockerId) {
|
||||||
const buildId = cuid();
|
const buildId = cuid();
|
||||||
const { id: dockerId, network } = application.destinationDocker;
|
const { id: dockerId, network } = application.destinationDocker;
|
||||||
@@ -1427,9 +1432,8 @@ export async function restartPreview(
|
|||||||
|
|
||||||
const volumes =
|
const volumes =
|
||||||
persistentStorage?.map((storage) => {
|
persistentStorage?.map((storage) => {
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||||
buildPack !== 'docker' ? '/app' : ''
|
}${storage.path}`;
|
||||||
}${storage.path}`;
|
|
||||||
}) || [];
|
}) || [];
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.map((volume) => {
|
||||||
return {
|
return {
|
||||||
@@ -1691,7 +1695,7 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
|||||||
try {
|
try {
|
||||||
await fs.stat(file);
|
await fs.stat(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let logs = await prisma.buildLog.findMany({
|
const logs = await prisma.buildLog.findMany({
|
||||||
where: { buildId, time: { gt: sequence } },
|
where: { buildId, time: { gt: sequence } },
|
||||||
orderBy: { time: 'asc' }
|
orderBy: { time: 'asc' }
|
||||||
});
|
});
|
||||||
@@ -1707,9 +1711,9 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
|||||||
status: data?.status || 'queued'
|
status: data?.status || 'queued'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let fileLogs = (await fs.readFile(file)).toString();
|
const fileLogs = (await fs.readFile(file)).toString();
|
||||||
let decryptedLogs = await csv({ noheader: true }).fromString(fileLogs);
|
const decryptedLogs = await csv({ noheader: true }).fromString(fileLogs);
|
||||||
let logs = decryptedLogs
|
const logs = decryptedLogs
|
||||||
.map((log) => {
|
.map((log) => {
|
||||||
const parsed = {
|
const parsed = {
|
||||||
time: log['field1'],
|
time: log['field1'],
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface SaveApplication extends OnlyId {
|
|||||||
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 {
|
||||||
@@ -43,6 +45,7 @@ export interface SaveApplicationSettings extends OnlyId {
|
|||||||
isDBBranching: boolean;
|
isDBBranching: boolean;
|
||||||
isCustomSSL: boolean;
|
isCustomSSL: boolean;
|
||||||
isHttp2: boolean;
|
isHttp2: boolean;
|
||||||
|
basicAuth: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
@@ -96,6 +99,7 @@ export interface DeleteSecret extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface SaveStorage extends OnlyId {
|
export interface SaveStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
|
hostPath?: string;
|
||||||
path: string;
|
path: string;
|
||||||
newStorage: boolean;
|
newStorage: boolean;
|
||||||
storageId: string;
|
storageId: string;
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import {
|
import {
|
||||||
ComposeFile,
|
ComposeFile,
|
||||||
|
backupDatabaseNow,
|
||||||
createDirectories,
|
createDirectories,
|
||||||
decrypt,
|
decrypt,
|
||||||
defaultComposeConfiguration,
|
defaultComposeConfiguration,
|
||||||
@@ -302,7 +303,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
databaseSecret
|
databaseSecret
|
||||||
} = database;
|
} = database;
|
||||||
const { privatePort, command, environmentVariables, image, volume, ulimits } =
|
const { privatePort, command, environmentVariables, image, volume, ulimits } =
|
||||||
generateDatabaseConfiguration(database, arch);
|
generateDatabaseConfiguration(database);
|
||||||
|
|
||||||
const network = destinationDockerId && destinationDocker.network;
|
const network = destinationDockerId && destinationDocker.network;
|
||||||
const volumeName = volume.split(':')[0];
|
const volumeName = volume.split(':')[0];
|
||||||
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
|||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export async function backupDatabase(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id } = request.params;
|
||||||
|
const database = await prisma.database.findFirst({
|
||||||
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
|
return await backupDatabaseNow(database, reply);
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
export async function stopDatabase(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
@@ -427,19 +443,15 @@ export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
|
|||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { force } = request.body;
|
|
||||||
const database = await prisma.database.findFirst({
|
const database = await prisma.database.findFirst({
|
||||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, settings: true }
|
include: { destinationDocker: true, settings: true }
|
||||||
});
|
});
|
||||||
if (!force) {
|
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
if (database.destinationDockerId) {
|
||||||
if (database.destinationDockerId) {
|
const everStarted = await stopDatabaseContainer(database);
|
||||||
const everStarted = await stopDatabaseContainer(database);
|
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||||
if (everStarted)
|
|
||||||
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
|
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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';
|
||||||
@@ -20,8 +19,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
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 });
|
||||||
@@ -78,7 +76,7 @@ export async function refreshTags() {
|
|||||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
await fs.writeFile('./tags.json', tags);
|
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();
|
||||||
@@ -103,7 +101,7 @@ export async function refreshTemplates() {
|
|||||||
if (await fs.stat('./testTemplate.yaml')) {
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||||
} else {
|
} else {
|
||||||
@@ -156,14 +154,21 @@ 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: `docker pull ${image}` });
|
||||||
|
} 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({
|
await executeCommand({
|
||||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||||
});
|
});
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
shell: true,
|
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"`
|
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 {
|
||||||
@@ -445,7 +450,6 @@ export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fa
|
|||||||
});
|
});
|
||||||
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
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import type {
|
|||||||
SetWordpressSettings
|
SetWordpressSettings
|
||||||
} from './types';
|
} from './types';
|
||||||
import type { OnlyId } from '../../../../types';
|
import type { OnlyId } from '../../../../types';
|
||||||
|
import { refreshTags, refreshTemplates } from '../handlers';
|
||||||
|
|
||||||
export async function listServices(request: FastifyRequest) {
|
export async function listServices(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
@@ -412,22 +413,23 @@ export async function saveServiceType(
|
|||||||
if (foundTemplate.variables) {
|
if (foundTemplate.variables) {
|
||||||
if (foundTemplate.variables.length > 0) {
|
if (foundTemplate.variables.length > 0) {
|
||||||
for (const variable of foundTemplate.variables) {
|
for (const variable of foundTemplate.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 || '';
|
||||||
}
|
}
|
||||||
const foundVariableSomewhereElse = foundTemplate.variables.find((v) =>
|
const foundVariableSomewhereElse = foundTemplate.variables.find((v) =>
|
||||||
v.defaultValue.includes(variable.id)
|
v.defaultValue.toString().includes(variable.id)
|
||||||
);
|
);
|
||||||
if (foundVariableSomewhereElse) {
|
if (foundVariableSomewhereElse) {
|
||||||
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(
|
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(
|
||||||
@@ -475,7 +477,7 @@ export async function saveServiceType(
|
|||||||
const [volumeName, path] = volume.split(':');
|
const [volumeName, path] = volume.split(':');
|
||||||
if (!volumeName.startsWith('/')) {
|
if (!volumeName.startsWith('/')) {
|
||||||
const found = await prisma.servicePersistentStorage.findFirst({
|
const found = await prisma.servicePersistentStorage.findFirst({
|
||||||
where: { volumeName, serviceId: id }
|
where: { volumeName, serviceId: id, path }
|
||||||
});
|
});
|
||||||
if (!found) {
|
if (!found) {
|
||||||
await prisma.servicePersistentStorage.create({
|
await prisma.servicePersistentStorage.create({
|
||||||
@@ -617,6 +619,29 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
|||||||
export async function deleteService(request: FastifyRequest<OnlyId>) {
|
export async function deleteService(request: FastifyRequest<OnlyId>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { destinationDockerId } = await getServiceFromDB({ id, teamId });
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}`
|
||||||
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containerArray = containers.split('\n');
|
||||||
|
if (containerArray.length > 0) {
|
||||||
|
for (const container of containerArray) {
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker stop -t 0 ${container}`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDockerId,
|
||||||
|
command: `docker rm --force ${container}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
await removeService({ id });
|
await removeService({ id });
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -723,7 +748,10 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
|
|||||||
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting;
|
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting;
|
||||||
if (value) {
|
if (value) {
|
||||||
if (changed) {
|
if (changed) {
|
||||||
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } });
|
await prisma.serviceSetting.update({
|
||||||
|
where: { id: settingId },
|
||||||
|
data: { value: value.replace(/\n/, '\\n') }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
if (!variableName) {
|
if (!variableName) {
|
||||||
@@ -958,11 +986,22 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
|
|||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
await executeCommand({
|
const logTables = await executeCommand({
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`,
|
command: `docker exec ${id}-clickhouse clickhouse-client -q "SELECT name FROM system.tables;"`,
|
||||||
shell: true
|
shell: false
|
||||||
});
|
});
|
||||||
|
if (logTables.stdout !== '') {
|
||||||
|
const tables = logTables.stdout.split('\n').filter((t) => t.includes('_log'));
|
||||||
|
for (const table of tables) {
|
||||||
|
console.log(`Truncating table ${table}`)
|
||||||
|
await executeCommand({
|
||||||
|
dockerId: destinationDocker.id,
|
||||||
|
command: `docker exec ${id}-clickhouse clickhouse-client -q "TRUNCATE TABLE system.${table};"`,
|
||||||
|
shell: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return await reply.code(201).send();
|
return await reply.code(201).send();
|
||||||
}
|
}
|
||||||
throw { status: 500, message: 'Could cleanup logs.' };
|
throw { status: 500, message: 'Could cleanup logs.' };
|
||||||
@@ -1078,17 +1117,14 @@ export async function activateWordpressFtp(
|
|||||||
shell: true
|
shell: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
const volumes = [
|
const volumes = [
|
||||||
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
|
`${id}-wordpress-data:/home/${ftpUser}/wordpress`,
|
||||||
`${
|
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
|
||||||
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
|
||||||
`${
|
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
|
||||||
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
|
||||||
`${
|
`${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
||||||
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
|
|
||||||
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
}/${id}.sh:/etc/sftp.d/chmod.sh`
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1158,6 +1194,6 @@ export async function activateWordpressFtp(
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const teamId = request.user.teamId;
|
||||||
|
const { id } = request.body;
|
||||||
|
await prisma.application.updateMany({
|
||||||
|
where: { dockerRegistryId: id },
|
||||||
|
data: { dockerRegistryId: null }
|
||||||
|
});
|
||||||
|
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
|
||||||
|
return reply.code(201).send();
|
||||||
|
} catch ({ status, message }) {
|
||||||
|
return errorHandler({ status, message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
|
||||||
try {
|
|
||||||
const teamId = request.user.teamId;
|
|
||||||
const { id } = request.body;
|
|
||||||
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
|
|
||||||
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
|
|
||||||
return reply.code(201).send()
|
|
||||||
} catch ({ status, message }) {
|
|
||||||
return errorHandler({ status, message })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,191 +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, customUser, 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, customUser, 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',
|
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, customUser } =
|
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, customUser, 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, customUser } });
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
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, isHttp2 = false, isHttps = false) {
|
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||||
if (isHttp2) {
|
if (isHttp2) {
|
||||||
@@ -38,7 +40,7 @@ function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function generateRouters(
|
async function generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
@@ -47,17 +49,22 @@ function generateRouters(
|
|||||||
isWWW,
|
isWWW,
|
||||||
isDualCerts,
|
isDualCerts,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2 = false
|
isHttp2 = false,
|
||||||
) {
|
httpBasicAuth = null,
|
||||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
}) {
|
||||||
let http: any = {
|
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||||
|
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||||
|
}`;
|
||||||
|
|
||||||
|
|
||||||
|
const http: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let https: any = {
|
const https: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
@@ -67,16 +74,16 @@ function generateRouters(
|
|||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let httpWWW: any = {
|
const httpWWW: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
};
|
};
|
||||||
let httpsWWW: any = {
|
const httpsWWW: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
tls: {
|
tls: {
|
||||||
@@ -93,6 +100,10 @@ function generateRouters(
|
|||||||
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
|
||||||
@@ -104,6 +115,10 @@ function generateRouters(
|
|||||||
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) {
|
||||||
@@ -132,6 +147,10 @@ function generateRouters(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 6. https + www only
|
// 6. https + www only
|
||||||
if (isHttps && isWWW) {
|
if (isHttps && isWWW) {
|
||||||
@@ -141,6 +160,11 @@ function generateRouters(
|
|||||||
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;
|
||||||
@@ -162,23 +186,23 @@ function generateRouters(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isHttp2) {
|
if (isHttp2) {
|
||||||
let http2 = {
|
const http2 = {
|
||||||
...http,
|
...http,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
let http2WWW = {
|
const http2WWW = {
|
||||||
...httpWWW,
|
...httpWWW,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
let https2 = {
|
const https2 = {
|
||||||
...https,
|
...https,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
};
|
};
|
||||||
|
|
||||||
let https2WWW = {
|
const https2WWW = {
|
||||||
...httpsWWW,
|
...httpsWWW,
|
||||||
service: `${serviceId}-http2`,
|
service: `${serviceId}-http2`,
|
||||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||||
@@ -194,14 +218,17 @@ function generateRouters(
|
|||||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
|
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: []
|
||||||
@@ -294,7 +321,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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`,
|
||||||
@@ -365,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;
|
||||||
@@ -378,6 +408,14 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
) {
|
) {
|
||||||
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) {
|
||||||
@@ -400,27 +438,33 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
|
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, containerId, port)
|
...generateServices(serviceId, containerId, port)
|
||||||
};
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||||
const { network, id: dockerId } = destinationDocker;
|
const { network, id: dockerId } = destinationDocker;
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
@@ -433,22 +477,28 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${id}-${port || 'default'}`;
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL,
|
isCustomSSL,
|
||||||
isHttp2
|
isHttp2,
|
||||||
)
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||||
};
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
if (previews) {
|
if (previews) {
|
||||||
const { stdout } = await executeCommand({
|
const { stdout } = await executeCommand({
|
||||||
dockerId,
|
dockerId,
|
||||||
@@ -462,29 +512,35 @@ 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]}${
|
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||||
coolifySettings.previewSeparator
|
}${domain}`;
|
||||||
}${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 = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
previewDomain,
|
domain: previewDomain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
isHttp2: false,
|
||||||
|
httpBasicAuth
|
||||||
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
...generateServices(serviceId, container, port, isHttp2)
|
...generateServices(serviceId, container, port, isHttp2)
|
||||||
};
|
};
|
||||||
|
if (httpBasicAuth) {
|
||||||
|
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||||
|
...httpBasicAuth
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,11 +589,15 @@ 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 =
|
const isDomainAndProxyConfiguration =
|
||||||
found?.services[oneService]?.proxy?.filter((p) => p.domain) ?? [];
|
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||||
if (isDomainConfiguration.length > 0) {
|
if (isDomainAndProxyConfiguration.length > 0) {
|
||||||
const { proxy } = found.services[oneService];
|
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||||
for (let configuration of proxy) {
|
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(
|
const setting = serviceSetting.find(
|
||||||
(a) => a.variableName === configuration.domain
|
(a) => a.variableName === configuration.domain
|
||||||
@@ -574,16 +634,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${oneService}-${port || 'default'}`;
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL,
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
@@ -611,16 +671,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${oneService}-${port || 'default'}`;
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
@@ -652,16 +712,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const serviceId = `${id}-${port || 'default'}`;
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = {
|
traefik.http.routers = {
|
||||||
...traefik.http.routers,
|
...traefik.http.routers,
|
||||||
...generateRouters(
|
...await generateRouters({
|
||||||
serviceId,
|
serviceId,
|
||||||
domain,
|
domain,
|
||||||
nakedDomain,
|
nakedDomain,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
isHttps,
|
isHttps,
|
||||||
isWWW,
|
isWWW,
|
||||||
dualCerts,
|
isDualCerts: dualCerts,
|
||||||
isCustomSSL
|
isCustomSSL
|
||||||
)
|
})
|
||||||
};
|
};
|
||||||
traefik.http.services = {
|
traefik.http.services = {
|
||||||
...traefik.http.services,
|
...traefik.http.services,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
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,54 +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",
|
|
||||||
"dayjs": "1.11.6",
|
|
||||||
"flowbite-svelte": "0.28.0",
|
|
||||||
"js-cookie": "3.0.1",
|
|
||||||
"js-yaml": "4.1.0",
|
|
||||||
"p-limit": "4.0.0",
|
|
||||||
"server": "workspace:*",
|
|
||||||
"superjson": "1.11.0",
|
|
||||||
"svelte-select": "4.4.7"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
12
apps/client/src/app.d.ts
vendored
12
apps/client/src/app.d.ts
vendored
@@ -1,12 +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 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const GITPOD_WORKSPACE_URL: string;
|
|
||||||
declare const CODESANDBOX_HOST: string;
|
|
||||||
@@ -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,210 +0,0 @@
|
|||||||
import { dev } from '$app/environment';
|
|
||||||
import { addToast } from './store';
|
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
|
|
||||||
export function dashify(str: string, options?: any): string {
|
|
||||||
if (typeof str !== 'string') return str;
|
|
||||||
return str
|
|
||||||
.trim()
|
|
||||||
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
|
|
||||||
.replace(/^-+|-+$/g, '')
|
|
||||||
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
export function errorNotification(error: any | { message: string }): void {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
console.error(error.message)
|
|
||||||
addToast({
|
|
||||||
message: error.message,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(error)
|
|
||||||
addToast({
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function getRndInteger(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDomain(domain: string) {
|
|
||||||
return domain?.replace('https://', '').replace('http://', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku'];
|
|
||||||
export const staticDeployments = [
|
|
||||||
'react',
|
|
||||||
'vuejs',
|
|
||||||
'static',
|
|
||||||
'svelte',
|
|
||||||
'gatsby',
|
|
||||||
'php',
|
|
||||||
'astro',
|
|
||||||
'eleventy'
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getAPIUrl() {
|
|
||||||
if (GITPOD_WORKSPACE_URL) {
|
|
||||||
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
|
||||||
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '');
|
|
||||||
return newURL;
|
|
||||||
}
|
|
||||||
if (CODESANDBOX_HOST) {
|
|
||||||
return `https://${CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
|
|
||||||
}
|
|
||||||
return dev ? `http://${window.location.hostname}:3001` : 'http://localhost:3000';
|
|
||||||
}
|
|
||||||
export function getWebhookUrl(type: string) {
|
|
||||||
if (GITPOD_WORKSPACE_URL) {
|
|
||||||
const { href } = new URL(GITPOD_WORKSPACE_URL);
|
|
||||||
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '');
|
|
||||||
if (type === 'github') {
|
|
||||||
return `${newURL}/webhooks/github/events`;
|
|
||||||
}
|
|
||||||
if (type === 'gitlab') {
|
|
||||||
return `${newURL}/webhooks/gitlab/events`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (CODESANDBOX_HOST) {
|
|
||||||
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
|
|
||||||
if (type === 'github') {
|
|
||||||
return `${newURL}/webhooks/github/events`;
|
|
||||||
}
|
|
||||||
if (type === 'gitlab') {
|
|
||||||
return `${newURL}/webhooks/gitlab/events`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `https://webhook.site/0e5beb2c-4e9b-40e2-a89e-32295e570c21/events`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function send({
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
data = null,
|
|
||||||
headers,
|
|
||||||
timeout = 120000
|
|
||||||
}: {
|
|
||||||
method: string;
|
|
||||||
path: string;
|
|
||||||
data?: any;
|
|
||||||
headers?: any;
|
|
||||||
timeout?: number;
|
|
||||||
}): Promise<Record<string, unknown>> {
|
|
||||||
const token = Cookies.get('token');
|
|
||||||
const controller = new AbortController();
|
|
||||||
const id = setTimeout(() => controller.abort(), timeout);
|
|
||||||
const opts: any = { method, headers: {}, body: null, signal: controller.signal };
|
|
||||||
if (data && Object.keys(data).length > 0) {
|
|
||||||
const parsedData = data;
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
if (value === '') {
|
|
||||||
parsedData[key] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parsedData) {
|
|
||||||
opts.headers['Content-Type'] = 'application/json';
|
|
||||||
opts.body = JSON.stringify(parsedData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headers) {
|
|
||||||
opts.headers = {
|
|
||||||
...opts.headers,
|
|
||||||
...headers
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (token && !path.startsWith('https://')) {
|
|
||||||
opts.headers = {
|
|
||||||
...opts.headers,
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!path.startsWith('https://')) {
|
|
||||||
path = `/api/v1${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dev && !path.startsWith('https://')) {
|
|
||||||
path = `${getAPIUrl()}${path}`;
|
|
||||||
}
|
|
||||||
if (method === 'POST' && data && !opts.body) {
|
|
||||||
opts.body = data;
|
|
||||||
}
|
|
||||||
const response = await fetch(`${path}`, opts);
|
|
||||||
|
|
||||||
clearTimeout(id);
|
|
||||||
|
|
||||||
const contentType = response.headers.get('content-type');
|
|
||||||
|
|
||||||
let responseData = {};
|
|
||||||
if (contentType) {
|
|
||||||
if (contentType?.indexOf('application/json') !== -1) {
|
|
||||||
responseData = await response.json();
|
|
||||||
} else if (contentType?.indexOf('text/plain') !== -1) {
|
|
||||||
responseData = await response.text();
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (!response.ok) {
|
|
||||||
if (
|
|
||||||
response.status === 401 &&
|
|
||||||
!path.startsWith('https://api.github') &&
|
|
||||||
!path.includes('/v4/')
|
|
||||||
) {
|
|
||||||
Cookies.remove('token');
|
|
||||||
}
|
|
||||||
|
|
||||||
throw responseData;
|
|
||||||
}
|
|
||||||
return responseData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function get(path: string, headers?: Record<string, unknown>): Promise<Record<string, any>> {
|
|
||||||
return send({ method: 'GET', path, headers });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function del(
|
|
||||||
path: string,
|
|
||||||
data: Record<string, unknown>,
|
|
||||||
headers?: Record<string, unknown>
|
|
||||||
): Promise<Record<string, any>> {
|
|
||||||
return send({ method: 'DELETE', path, data, headers });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function post(
|
|
||||||
path: string,
|
|
||||||
data: Record<string, unknown> | FormData,
|
|
||||||
headers?: Record<string, unknown>
|
|
||||||
): Promise<Record<string, any>> {
|
|
||||||
return send({ method: 'POST', path, data, headers });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function put(
|
|
||||||
path: string,
|
|
||||||
data: Record<string, unknown>,
|
|
||||||
headers?: Record<string, unknown>
|
|
||||||
): Promise<Record<string, any>> {
|
|
||||||
return send({ method: 'PUT', path, data, headers });
|
|
||||||
}
|
|
||||||
export function changeQueryParams(buildId: string) {
|
|
||||||
const queryParams = new URLSearchParams(window.location.search);
|
|
||||||
queryParams.set('buildId', buildId);
|
|
||||||
// @ts-ignore
|
|
||||||
return history.pushState(null, null, '?' + queryParams.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dateOptions: any = {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
hour12: false
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<span class="badge bg-coollabs-gradient rounded text-white font-normal"> BETA </span>
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { addToast } from '$lib/store';
|
|
||||||
let showPassword = false;
|
|
||||||
|
|
||||||
export let value: string;
|
|
||||||
export let disabled = false;
|
|
||||||
export let isPasswordField = false;
|
|
||||||
export let readonly = false;
|
|
||||||
export let textarea = false;
|
|
||||||
export let required = false;
|
|
||||||
export let pattern: string | null | undefined = null;
|
|
||||||
export let id: string;
|
|
||||||
export let name: string;
|
|
||||||
export let placeholder = '';
|
|
||||||
export let inputStyle = '';
|
|
||||||
|
|
||||||
let disabledClass = 'input input-primary bg-coolback disabled:bg-coolblack w-full';
|
|
||||||
let isHttps = browser && window.location.protocol === 'https:';
|
|
||||||
|
|
||||||
function copyToClipboard() {
|
|
||||||
if (isHttps && navigator.clipboard) {
|
|
||||||
navigator.clipboard.writeText(value);
|
|
||||||
addToast({
|
|
||||||
message: 'Copied to clipboard.',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="relative">
|
|
||||||
{#if !isPasswordField || showPassword}
|
|
||||||
{#if textarea}
|
|
||||||
<textarea
|
|
||||||
style={inputStyle}
|
|
||||||
rows="5"
|
|
||||||
class={disabledClass}
|
|
||||||
class:pr-10={true}
|
|
||||||
class:pr-20={value && isHttps}
|
|
||||||
class:border={required && !value}
|
|
||||||
class:border-red-500={required && !value}
|
|
||||||
{placeholder}
|
|
||||||
type="text"
|
|
||||||
{id}
|
|
||||||
{pattern}
|
|
||||||
{required}
|
|
||||||
{readonly}
|
|
||||||
{disabled}
|
|
||||||
{name}>{value}</textarea
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
style={inputStyle}
|
|
||||||
class={disabledClass}
|
|
||||||
type="text"
|
|
||||||
class:pr-10={true}
|
|
||||||
class:pr-20={value && isHttps}
|
|
||||||
class:border={required && !value}
|
|
||||||
class:border-red-500={required && !value}
|
|
||||||
{id}
|
|
||||||
{name}
|
|
||||||
{required}
|
|
||||||
{pattern}
|
|
||||||
{readonly}
|
|
||||||
bind:value
|
|
||||||
{disabled}
|
|
||||||
{placeholder}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<input
|
|
||||||
style={inputStyle}
|
|
||||||
class={disabledClass}
|
|
||||||
class:pr-10={true}
|
|
||||||
class:pr-20={value && isHttps}
|
|
||||||
class:border={required && !value}
|
|
||||||
class:border-red-500={required && !value}
|
|
||||||
type="password"
|
|
||||||
{id}
|
|
||||||
{name}
|
|
||||||
{readonly}
|
|
||||||
{pattern}
|
|
||||||
{required}
|
|
||||||
bind:value
|
|
||||||
{disabled}
|
|
||||||
{placeholder}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="absolute top-0 right-0 flex justify-center items-center h-full cursor-pointer text-stone-600 hover:text-white mr-3">
|
|
||||||
<div class="flex space-x-2">
|
|
||||||
{#if isPasswordField}
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div on:click={() => (showPassword = !showPassword)}>
|
|
||||||
{#if showPassword}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{:else}
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if value && isHttps}
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div on:click={copyToClipboard}>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
class="h-6 w-6"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
fill="none"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<rect x="8" y="8" width="12" height="12" rx="2" />
|
|
||||||
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import ExternalLink from './ExternalLink.svelte';
|
|
||||||
import Tooltip from './Tooltip.svelte';
|
|
||||||
export let url = 'https://docs.coollabs.io';
|
|
||||||
export let text: any = '';
|
|
||||||
export let isExternal = false;
|
|
||||||
let id =
|
|
||||||
'cool-' +
|
|
||||||
url
|
|
||||||
.split('')
|
|
||||||
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
|
|
||||||
.join('')
|
|
||||||
.slice(-16);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<a
|
|
||||||
{id}
|
|
||||||
href={url}
|
|
||||||
target="_blank noreferrer"
|
|
||||||
class="flex no-underline inline-block cursor-pointer"
|
|
||||||
class:icons={!text}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5"
|
|
||||||
stroke="currentColor"
|
|
||||||
class="w-6 h-6"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{text}
|
|
||||||
{#if isExternal}
|
|
||||||
<ExternalLink />
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
{#if !text}
|
|
||||||
<Tooltip triggeredBy={`#${id}`}>See details in the documentation</Tooltip>
|
|
||||||
{/if}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
// import Tooltip from './Tooltip.svelte';
|
|
||||||
export let explanation = '';
|
|
||||||
export let position = 'dropdown-right';
|
|
||||||
// let id: any;
|
|
||||||
// let self: any;
|
|
||||||
// onMount(() => {
|
|
||||||
// id = `info-${self.offsetLeft}-${self.offsetTop}`;
|
|
||||||
// });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={`dropdown dropdown-end ${position}`}>
|
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<label tabindex="0" class="btn btn-circle btn-ghost btn-xs text-sky-500">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
class="w-4 h-4 stroke-current"
|
|
||||||
><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
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<div tabindex="0" class="card compact dropdown-content shadow bg-coolgray-400 rounded w-64">
|
|
||||||
<div class="card-body">
|
|
||||||
<!-- <h2 class="card-title">You needed more info?</h2> -->
|
|
||||||
<p class="text-xs font-normal">{@html explanation}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -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: 261 B |
@@ -1,87 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Beta from './Beta.svelte';
|
|
||||||
import Explaner from './Explainer.svelte';
|
|
||||||
import Tooltip from './Tooltip.svelte';
|
|
||||||
|
|
||||||
export let id: any;
|
|
||||||
export let customClass: any = null;
|
|
||||||
export let setting: any;
|
|
||||||
export let title: any;
|
|
||||||
export let isBeta: any = false;
|
|
||||||
export let description: any = null;
|
|
||||||
export let isCenter = true;
|
|
||||||
export let disabled = false;
|
|
||||||
export let dataTooltip: any = null;
|
|
||||||
export let loading = false;
|
|
||||||
let triggeredBy = `#${id}`;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex items-center py-4 pr-8">
|
|
||||||
<div class="flex w-96 flex-col">
|
|
||||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
|
||||||
<label>
|
|
||||||
{title}
|
|
||||||
{#if isBeta}
|
|
||||||
<Beta />
|
|
||||||
{/if}
|
|
||||||
{#if description && description !== ''}
|
|
||||||
<Explaner explanation={description} />
|
|
||||||
{/if}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class:text-center={isCenter} class={`flex justify-center ${customClass}`}>
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<div
|
|
||||||
on:click
|
|
||||||
aria-pressed="false"
|
|
||||||
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
|
||||||
class:opacity-50={disabled || loading}
|
|
||||||
class:bg-green-600={!loading && setting}
|
|
||||||
class:bg-stone-700={!loading && !setting}
|
|
||||||
class:bg-yellow-500={loading}
|
|
||||||
{id}
|
|
||||||
>
|
|
||||||
<span class="sr-only">Use setting</span>
|
|
||||||
<span
|
|
||||||
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
|
|
||||||
class:translate-x-5={setting}
|
|
||||||
class:translate-x-0={!setting}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
|
|
||||||
class:opacity-0={setting}
|
|
||||||
class:opacity-100={!setting}
|
|
||||||
class:animate-spin={loading}
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
|
|
||||||
<path
|
|
||||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
|
|
||||||
aria-hidden="true"
|
|
||||||
class:opacity-100={setting}
|
|
||||||
class:opacity-0={!setting}
|
|
||||||
class:animate-spin={loading}
|
|
||||||
>
|
|
||||||
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
|
||||||
<path
|
|
||||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if dataTooltip}
|
|
||||||
<Tooltip {triggeredBy} placement="top">{dataTooltip}</Tooltip>
|
|
||||||
{/if}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
export let text: string;
|
|
||||||
export let customClass = 'max-w-[24rem]';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>
|
|
||||||
@@ -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