mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 04:59:32 +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
|
||||
title: "[Bug]: "
|
||||
labels: [Bug]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -2,9 +2,6 @@ name: 🛠️ Feature request
|
||||
description: Suggest an idea to improve coolify
|
||||
title: '[Feature]: '
|
||||
labels: [Enhancement]
|
||||
assignees:
|
||||
- andrasbacsai
|
||||
- vasani-arpit
|
||||
body:
|
||||
- type: markdown
|
||||
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:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "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
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "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
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}-aarch64
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-aarch64,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}-aarch64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [amd64, arm64, aarch64]
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -93,20 +83,22 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
- name: Create & publish manifest
|
||||
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 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
|
||||
docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
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:
|
||||
push:
|
||||
paths:
|
||||
- "**"
|
||||
- "!others/fluentbit"
|
||||
- "!others/pocketbase"
|
||||
- "!.github/workflows/fluent-bit-release.yml"
|
||||
- "!.github/workflows/pocketbase-release.yml"
|
||||
branches:
|
||||
- next
|
||||
- "v3"
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: "coollabsio/coolify"
|
||||
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: "next"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
ref: "v3"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
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
|
||||
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/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next-amd64
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [arm64, amd64]
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
@@ -78,15 +78,20 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to DockerHub
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
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: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
||||
docker manifest push coollabsio/coolify:next
|
||||
docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -18,5 +18,14 @@
|
||||
"ts",
|
||||
"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
|
||||
|
||||
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recomended*
|
||||
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recommended*
|
||||
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
|
||||
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.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 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/)
|
||||
|
||||
## 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`
|
||||
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
|
||||
- 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 dev` start coding.
|
||||
|
||||
```sh
|
||||
# Or... Copy and paste commands bellow:
|
||||
# Or... Copy and paste commands below:
|
||||
cp apps/api/.env.example apps/api/.env
|
||||
pnpm install
|
||||
pnpm db:push
|
||||
@@ -45,4 +45,4 @@ pnpm dev
|
||||
|
||||
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.
|
||||
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||
# 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 npm --no-update-notifier --no-fund --global install pnpm@${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
|
||||
|
||||
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/api/prisma/ ./prisma
|
||||
COPY --from=build /app/apps/api/package.json .
|
||||
@@ -50,4 +50,4 @@ RUN pnpm install -p
|
||||
|
||||
EXPOSE 3000
|
||||
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.
|
||||
ARG DOCKER_COMPOSE_VERSION=2.6.1
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=v0.27.0
|
||||
ARG PACK_VERSION=0.27.0
|
||||
WORKDIR /app
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
@@ -79,9 +79,9 @@ Deploy your resource to:
|
||||
### Services
|
||||
|
||||
- [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)
|
||||
- [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)
|
||||
- [VSCode Server](https://github.com/cdr/code-server)
|
||||
- [MinIO](https://min.io)
|
||||
@@ -100,7 +100,7 @@ Deploy your resource to:
|
||||
|
||||
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@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)
|
||||
- Discord: [Invitation](https://coollabs.io/discord)
|
||||
|
||||
@@ -119,7 +119,7 @@ Learn how to contribute to Coolify as as ...
|
||||
|
||||
<!--
|
||||
→ 🧑🏽🎨 Designer
|
||||
→ 🙋♀️ Community Managemer
|
||||
→ 🙋♀️ Community Manager
|
||||
→ 🧙🏻♂️ Text 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)]
|
||||
|
||||
### Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
### 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.
|
||||
|
||||
<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/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>
|
||||
|
||||
### 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/env": "4.2.0",
|
||||
"@fastify/jwt": "6.5.0",
|
||||
"@fastify/multipart": "7.3.0",
|
||||
"@fastify/multipart": "7.4.1",
|
||||
"@fastify/static": "6.6.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@ladjs/graceful": "3.2.1",
|
||||
"@prisma/client": "4.8.1",
|
||||
"@sentry/node": "7.30.0",
|
||||
"@sentry/tracing": "7.30.0",
|
||||
"axe": "11.2.1",
|
||||
"bcryptjs": "2.4.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
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_ApplicationSettings" (
|
||||
@@ -12,11 +16,12 @@ CREATE TABLE "new_ApplicationSettings" (
|
||||
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "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";
|
||||
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||
@@ -135,6 +135,8 @@ model Application {
|
||||
dockerRegistryId String?
|
||||
dockerRegistryImageName String?
|
||||
simpleDockerfile String?
|
||||
basicAuthUser String?
|
||||
basicAuthPw String?
|
||||
|
||||
persistentStorage ApplicationPersistentStorage[]
|
||||
secrets Secret[]
|
||||
@@ -187,6 +189,7 @@ model ApplicationSettings {
|
||||
isDBBranching Boolean @default(false)
|
||||
isCustomSSL Boolean @default(false)
|
||||
isHttp2 Boolean @default(false)
|
||||
basicAuth Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
@@ -195,6 +198,7 @@ model ApplicationSettings {
|
||||
model ApplicationPersistentStorage {
|
||||
id String @id @default(cuid())
|
||||
applicationId String
|
||||
hostPath String?
|
||||
path String
|
||||
oldPath Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -12,7 +12,7 @@ async function main() {
|
||||
await prisma.setting.create({
|
||||
data: {
|
||||
id: '0',
|
||||
arch: process.arch,
|
||||
arch: process.arch
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -81,16 +81,295 @@ async function main() {
|
||||
});
|
||||
}
|
||||
// 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) {
|
||||
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) {
|
||||
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()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
@@ -99,15 +378,11 @@ main()
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
const encrypt = (text) => {
|
||||
if (text) {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv);
|
||||
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
content: encrypted.toString('hex')
|
||||
});
|
||||
}
|
||||
};
|
||||
reEncryptSecrets()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
@@ -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 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 path, { join } from 'path';
|
||||
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 {
|
||||
cleanupDockerStorage,
|
||||
createRemoteEngineConfiguration,
|
||||
@@ -18,26 +23,20 @@ import {
|
||||
isDev,
|
||||
listSettings,
|
||||
prisma,
|
||||
sentryDSN,
|
||||
startTraefikProxy,
|
||||
startTraefikTCPProxy,
|
||||
version
|
||||
} 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 { 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 * as Sentry from '@sentry/node';
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
config: {
|
||||
COOLIFY_APP_ID: string;
|
||||
COOLIFY_SECRET_KEY: string;
|
||||
COOLIFY_SECRET_KEY_BETTER: string | null;
|
||||
COOLIFY_DATABASE_URL: string;
|
||||
COOLIFY_IS_ON: string;
|
||||
COOLIFY_WHITE_LABELED: string;
|
||||
@@ -67,6 +66,10 @@ const host = '0.0.0.0';
|
||||
COOLIFY_SECRET_KEY: {
|
||||
type: 'string'
|
||||
},
|
||||
COOLIFY_SECRET_KEY_BETTER: {
|
||||
type: 'string',
|
||||
default: null
|
||||
},
|
||||
COOLIFY_DATABASE_URL: {
|
||||
type: 'string',
|
||||
default: 'file:../db/dev.db'
|
||||
@@ -164,13 +167,18 @@ const host = '0.0.0.0';
|
||||
// autoUpdater
|
||||
setInterval(async () => {
|
||||
await autoUpdater();
|
||||
}, 60000 * 15);
|
||||
}, 60000 * 60);
|
||||
|
||||
// cleanupStorage
|
||||
setInterval(async () => {
|
||||
await cleanupStorage();
|
||||
}, 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
|
||||
setInterval(async () => {
|
||||
await checkProxies();
|
||||
@@ -180,24 +188,32 @@ const host = '0.0.0.0';
|
||||
// Refresh and check templates
|
||||
setInterval(async () => {
|
||||
await refreshTemplates();
|
||||
}, 60000);
|
||||
}, 60000 * 10);
|
||||
|
||||
setInterval(async () => {
|
||||
await refreshTags();
|
||||
}, 60000);
|
||||
}, 60000 * 10);
|
||||
|
||||
setInterval(
|
||||
async () => {
|
||||
await migrateServicesToNewTemplate();
|
||||
},
|
||||
isDev ? 10000 : 60000
|
||||
isDev ? 10000 : 60000 * 10
|
||||
);
|
||||
|
||||
setInterval(async () => {
|
||||
await copySSLCertificates();
|
||||
}, 10000);
|
||||
|
||||
await Promise.all([getTagsTemplates(), getArch(), getIPAddress(), configureRemoteDockers()]);
|
||||
await Promise.all([
|
||||
getTagsTemplates(),
|
||||
getArch(),
|
||||
getIPAddress(),
|
||||
configureRemoteDockers(),
|
||||
refreshTemplates(),
|
||||
refreshTags()
|
||||
// cleanupStuckedContainers()
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
@@ -219,7 +235,7 @@ async function getIPAddress() {
|
||||
console.log(`Getting public IPv6 address...`);
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
async function getTagsTemplates() {
|
||||
const { default: got } = await import('got');
|
||||
@@ -231,7 +247,7 @@ async function getTagsTemplates() {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
try {
|
||||
if (await fs.stat('./testTags.json')) {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
@@ -265,9 +281,6 @@ async function initServer() {
|
||||
if (settings.doNotTrack === true) {
|
||||
console.log('[000] Telemetry disabled...');
|
||||
} else {
|
||||
if (settings.sentryDSN !== sentryDSN) {
|
||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
|
||||
}
|
||||
// Initialize Sentry
|
||||
// Sentry.init({
|
||||
// dsn: sentryDSN,
|
||||
@@ -282,7 +295,7 @@ async function initServer() {
|
||||
try {
|
||||
console.log(`[001] Initializing server...`);
|
||||
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
try {
|
||||
console.log(`[002] Cleanup stucked builds...`);
|
||||
const isOlder = compareVersions('3.8.1', version);
|
||||
@@ -292,10 +305,10 @@ async function initServer() {
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
try {
|
||||
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||
await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||
if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
@@ -308,9 +321,52 @@ async function getArch() {
|
||||
console.log(`Getting architecture...`);
|
||||
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() {
|
||||
try {
|
||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||
@@ -348,14 +404,21 @@ async function autoUpdater() {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||
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({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
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 {
|
||||
@@ -421,7 +484,7 @@ async function checkProxies() {
|
||||
}
|
||||
try {
|
||||
await createRemoteEngineConfiguration(docker.id);
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// TCP Proxies
|
||||
@@ -460,7 +523,7 @@ async function checkProxies() {
|
||||
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||
// }
|
||||
// }
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
async function copySSLCertificates() {
|
||||
@@ -492,7 +555,11 @@ async function copySSLCertificates() {
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} 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();
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
|
||||
if (destination.engine) enginesDone.add(destination.engine);
|
||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress);
|
||||
let force = false;
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null;
|
||||
if (!isDev) {
|
||||
const output = await executeCommand({
|
||||
dockerId: destination.id,
|
||||
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
shell: true
|
||||
});
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await executeCommand({
|
||||
command: `df -kPT /`
|
||||
});
|
||||
stdout = output.stdout;
|
||||
}
|
||||
let lines = stdout.trim().split('\n');
|
||||
let header = lines[0];
|
||||
let regex =
|
||||
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
const boundaries = [];
|
||||
let match;
|
||||
if (destination.engine) {
|
||||
enginesDone.add(destination.engine);
|
||||
}
|
||||
if (destination.remoteIpAddress) {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
await cleanupDockerStorage(destination.id);
|
||||
// let lowDiskSpace = false;
|
||||
// try {
|
||||
// let stdout = null;
|
||||
// if (!isDev) {
|
||||
// const output = await executeCommand({
|
||||
// dockerId: destination.id,
|
||||
// command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
// shell: true
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// } else {
|
||||
// const output = await executeCommand({
|
||||
// command: `df -kPT /`
|
||||
// });
|
||||
// stdout = output.stdout;
|
||||
// }
|
||||
// let lines = stdout.trim().split('\n');
|
||||
// let header = lines[0];
|
||||
// let regex =
|
||||
// /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
|
||||
// const boundaries = [];
|
||||
// let match;
|
||||
|
||||
while ((match = regex.exec(header))) {
|
||||
boundaries.push(match[0].length);
|
||||
}
|
||||
// while ((match = regex.exec(header))) {
|
||||
// boundaries.push(match[0].length);
|
||||
// }
|
||||
|
||||
boundaries[boundaries.length - 1] = -1;
|
||||
const data = lines.slice(1).map((line) => {
|
||||
const cl = boundaries.map((boundary) => {
|
||||
const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
line = line.slice(boundary);
|
||||
return column.trim();
|
||||
});
|
||||
return {
|
||||
capacity: Number.parseInt(cl[5], 10) / 100
|
||||
};
|
||||
});
|
||||
if (data.length > 0) {
|
||||
const { capacity } = data[0];
|
||||
if (capacity > 0.8) {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
await cleanupDockerStorage(destination.id, lowDiskSpace, force);
|
||||
// boundaries[boundaries.length - 1] = -1;
|
||||
// const data = lines.slice(1).map((line) => {
|
||||
// const cl = boundaries.map((boundary) => {
|
||||
// const column = boundary > 0 ? line.slice(0, boundary) : line;
|
||||
// line = line.slice(boundary);
|
||||
// return column.trim();
|
||||
// });
|
||||
// return {
|
||||
// capacity: Number.parseInt(cl[5], 10) / 100
|
||||
// };
|
||||
// });
|
||||
// if (data.length > 0) {
|
||||
// const { capacity } = data[0];
|
||||
// if (capacity > 0.8) {
|
||||
// lowDiskSpace = true;
|
||||
// }
|
||||
// }
|
||||
// } catch (error) {}
|
||||
// if (lowDiskSpace) {
|
||||
// await cleanupDockerStorage(destination.id);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!application) {
|
||||
await prisma.build.update({
|
||||
where: { id: queueBuild.id },
|
||||
data: {
|
||||
status: 'failed'
|
||||
}
|
||||
});
|
||||
throw new Error('Application not found');
|
||||
}
|
||||
let {
|
||||
id: buildId,
|
||||
type,
|
||||
@@ -110,6 +118,9 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
if (storage.hostPath) {
|
||||
return `${storage.hostPath}:${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
|
||||
});
|
||||
try {
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
const composeVolumes = volumes
|
||||
.filter((v) => {
|
||||
if (
|
||||
!v.startsWith('.') &&
|
||||
!v.startsWith('..') &&
|
||||
!v.startsWith('/') &&
|
||||
!v.startsWith('~')
|
||||
) {
|
||||
return v;
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
@@ -233,7 +255,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -256,7 +278,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||
}
|
||||
} 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' }
|
||||
@@ -381,6 +403,9 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
if (storage.hostPath) {
|
||||
return `${storage.hostPath}:${storage.path}`;
|
||||
}
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
|
||||
@@ -406,7 +431,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
installCommand = configuration.installCommand;
|
||||
startCommand = configuration.startCommand;
|
||||
buildCommand = configuration.buildCommand;
|
||||
publishDirectory = configuration.publishDirectory;
|
||||
publishDirectory = configuration.publishDirectory || '';
|
||||
baseDirectory = configuration.baseDirectory || '';
|
||||
dockerFileLocation = configuration.dockerFileLocation;
|
||||
dockerComposeFileLocation = configuration.dockerComposeFileLocation;
|
||||
@@ -693,13 +718,24 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
try {
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
const composeVolumes = volumes
|
||||
.filter((v) => {
|
||||
if (
|
||||
!v.startsWith('.') &&
|
||||
!v.startsWith('..') &&
|
||||
!v.startsWith('/') &&
|
||||
!v.startsWith('~')
|
||||
) {
|
||||
return v;
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.map((volume) => {
|
||||
return {
|
||||
[`${volume.split(':')[0]}`]: {
|
||||
name: volume.split(':')[0]
|
||||
}
|
||||
};
|
||||
});
|
||||
const composeFile = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
@@ -770,7 +806,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -791,7 +827,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||
}
|
||||
} 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' } });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -86,19 +86,20 @@ export async function migrateServicesToNewTemplate() {
|
||||
if (template.variables) {
|
||||
if (template.variables.length > 0) {
|
||||
for (const variable of template.variables) {
|
||||
const { defaultValue } = variable;
|
||||
let { defaultValue } = variable;
|
||||
defaultValue = defaultValue.toString();
|
||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined
|
||||
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||
if (defaultValue.startsWith('$$generate_password')) {
|
||||
variable.value = generatePassword({ length });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||
} else if (defaultValue.startsWith('$$generate_hex')) {
|
||||
variable.value = generatePassword({ length, isHex: true });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||
} else if (defaultValue.startsWith('$$generate_username')) {
|
||||
variable.value = cuid();
|
||||
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
||||
} else if (defaultValue.startsWith('$$generate_token')) {
|
||||
variable.value = generateToken()
|
||||
} else {
|
||||
variable.value = variable.defaultValue || '';
|
||||
variable.value = defaultValue || '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,8 +341,8 @@ export function setDefaultBaseImage(
|
||||
};
|
||||
if (nodeBased.includes(buildPack)) {
|
||||
if (deploymentType === 'static') {
|
||||
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM()
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
@@ -355,8 +355,8 @@ export function setDefaultBaseImage(
|
||||
}
|
||||
}
|
||||
if (staticApps.includes(buildPack)) {
|
||||
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM()
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
@@ -376,18 +376,18 @@ export function setDefaultBaseImage(
|
||||
payload.baseImage = 'denoland/deno:latest';
|
||||
}
|
||||
if (buildPack === 'php') {
|
||||
payload.baseImage = isARM(process.arch)
|
||||
payload.baseImage = isARM()
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
payload.baseImages = isARM()
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
}
|
||||
if (buildPack === 'laravel') {
|
||||
payload.baseImage = isARM(process.arch)
|
||||
payload.baseImage = isARM()
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
payload.baseImages = isARM()
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
payload.baseBuildImage = 'node:18';
|
||||
@@ -429,7 +429,12 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
startCommand = template?.startCommand || 'yarn start';
|
||||
if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel')
|
||||
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.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/')
|
||||
@@ -702,9 +707,8 @@ export async function buildImage({
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker ${location ? `--config ${location}` : ''} build ${
|
||||
forceRebuild ? '--no-cache' : ''
|
||||
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||
command: `docker ${location ? `--config ${location}` : ''} build ${forceRebuild ? '--no-cache' : ''
|
||||
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||
});
|
||||
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
@@ -800,6 +804,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
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 resources /app/resources`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`RUN yarn install && yarn production`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
@@ -838,6 +844,7 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
Dockerfile.push('RUN cargo install cargo-chef');
|
||||
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 rm -fr .git');
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ export default async function (data) {
|
||||
dockerComposeConfiguration,
|
||||
dockerComposeFileLocation
|
||||
} = 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 dockerComposeYaml = yaml.load(dockerComposeRaw);
|
||||
if (!dockerComposeYaml.services) {
|
||||
@@ -31,61 +33,114 @@ export default async function (data) {
|
||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||
}
|
||||
|
||||
await fs.writeFile(envFile, envs.join('\n'));
|
||||
const composeVolumes = [];
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
let [v, path] = volume.split(':');
|
||||
composeVolumes[v] = {
|
||||
name: v
|
||||
};
|
||||
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||
composeVolumes[v] = {
|
||||
name: v
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let networks = {};
|
||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||
value['container_name'] = `${applicationId}-${key}`;
|
||||
|
||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||
if (Object.keys(environment).length > 0) {
|
||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
||||
if (value['env_file']) {
|
||||
delete value['env_file'];
|
||||
}
|
||||
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'];
|
||||
if (typeof build === 'string') {
|
||||
build = { context: build };
|
||||
}
|
||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||
let finalArgs = [...buildEnvs];
|
||||
let finalBuildArgs = [...buildEnvs];
|
||||
if (Object.keys(buildArgs).length > 0) {
|
||||
for (const arg of buildArgs) {
|
||||
for (const arg of Object.keys(buildArgs)) {
|
||||
const [key, _] = arg.split('=');
|
||||
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalArgs.push(arg);
|
||||
if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalBuildArgs.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
value['build'] = {
|
||||
...build,
|
||||
args: finalArgs
|
||||
};
|
||||
if (build.length > 0 || buildArgs.length > 0) {
|
||||
value['build'] = {
|
||||
...build,
|
||||
args: finalBuildArgs
|
||||
};
|
||||
}
|
||||
|
||||
value['labels'] = labels;
|
||||
// TODO: If we support separated volume for each service, we need to add it here
|
||||
if (value['volumes']?.length > 0) {
|
||||
value['volumes'] = value['volumes'].map((volume) => {
|
||||
let [v, path, permission] = volume.split(':');
|
||||
if (!path) {
|
||||
path = v;
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
} else {
|
||||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
|
||||
if (typeof volume === 'string') {
|
||||
let [v, path, permission] = volume.split(':');
|
||||
if (
|
||||
v.startsWith('.') ||
|
||||
v.startsWith('..') ||
|
||||
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) {
|
||||
@@ -93,17 +148,24 @@ export default async function (data) {
|
||||
value['volumes'].push(volume);
|
||||
}
|
||||
}
|
||||
if (dockerComposeConfiguration[key].port) {
|
||||
if (dockerComposeConfiguration[key]?.port) {
|
||||
value['expose'] = [dockerComposeConfiguration[key].port];
|
||||
}
|
||||
if (value['networks']?.length > 0) {
|
||||
value['networks'].forEach((network) => {
|
||||
networks[network] = {
|
||||
name: network
|
||||
};
|
||||
});
|
||||
value['networks'] = [network];
|
||||
if (value['build']?.network) {
|
||||
delete value['build']['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],
|
||||
restart: defaultComposeConfiguration(network).restart,
|
||||
@@ -116,7 +178,6 @@ export default async function (data) {
|
||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||
|
||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||
console.log(yaml.dump(dockerComposeYaml));
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
|
||||
@@ -36,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
Dockerfile.push(`RUN deno cache ${denoMainFile}`);
|
||||
Dockerfile.push(`ENV NO_COLOR true`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
|
||||
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('WORKDIR /app');
|
||||
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')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
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`
|
||||
);
|
||||
Dockerfile.push(`COPY --chown=application:application . ./`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
|
||||
import { buildCacheImageWithNode, buildImage } from './common';
|
||||
|
||||
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 isPnpm = startCommand.includes('pnpm');
|
||||
|
||||
@@ -12,8 +12,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
if (isPnpm) {
|
||||
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(`CMD ${startCommand}`);
|
||||
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(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
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`);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
}
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
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(`RUN ${installCommand}`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ${startCommand}`);
|
||||
} else if (deploymentType === 'static') {
|
||||
if (baseImage?.includes('nginx')) {
|
||||
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`);
|
||||
}
|
||||
|
||||
|
||||
@@ -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('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
} else {
|
||||
Dockerfile.push(`CMD python ${pythonModule}`);
|
||||
}
|
||||
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
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(`LABEL coolify.buildId=${buildId}`);
|
||||
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')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
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(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
Dockerfile.push(`CMD ["/app/${name}"]`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
|
||||
|
||||
@@ -31,13 +31,14 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
});
|
||||
}
|
||||
if (buildCommand) {
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`);
|
||||
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
|
||||
} else {
|
||||
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
|
||||
}
|
||||
if (baseImage?.includes('nginx')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
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('WORKDIR /app');
|
||||
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')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
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('WORKDIR /app');
|
||||
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')) {
|
||||
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
|
||||
}
|
||||
Dockerfile.push('RUN rm -fr .git');
|
||||
Dockerfile.push(`EXPOSE ${port}`);
|
||||
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 fsNormal from 'fs';
|
||||
import yaml from 'js-yaml';
|
||||
import forge from 'node-forge';
|
||||
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 crypto from 'crypto';
|
||||
import { promises as dns } from 'dns';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import os from 'os';
|
||||
import sshConfig from 'ssh-config';
|
||||
import * as SSHConfig from 'ssh-config/src/ssh-config';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { checkContainer, removeContainer } from './docker';
|
||||
import { day } from './dayjs';
|
||||
import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common';
|
||||
import { saveBuildLog } from './buildPacks/common';
|
||||
import { scheduler } from './scheduler';
|
||||
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 sentryDSN =
|
||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
||||
export const proxyPort = process.env.COOLIFY_PROXY_PORT;
|
||||
export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
|
||||
|
||||
const algorithm = 'aes-256-ctr';
|
||||
const customConfig: Config = {
|
||||
dictionaries: [adjectives, colors, animals],
|
||||
@@ -170,13 +170,19 @@ export const base64Encode = (text: string): string => {
|
||||
export const base64Decode = (text: string): string => {
|
||||
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) => {
|
||||
if (hashString) {
|
||||
try {
|
||||
const hash = JSON.parse(hashString);
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
process.env['COOLIFY_SECRET_KEY'],
|
||||
getSecretKey(),
|
||||
Buffer.from(hash.iv, 'hex')
|
||||
);
|
||||
const decrpyted = Buffer.concat([
|
||||
@@ -193,7 +199,7 @@ export const decrypt = (hashString: string) => {
|
||||
export const encrypt = (text: string) => {
|
||||
if (text) {
|
||||
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()]);
|
||||
return JSON.stringify({
|
||||
iv: iv.toString('hex'),
|
||||
@@ -400,8 +406,8 @@ export const supportedDatabaseTypesAndVersions = [
|
||||
fancyName: 'MongoDB',
|
||||
baseImage: 'bitnami/mongodb',
|
||||
baseImageARM: 'mongo',
|
||||
versions: ['5.0', '4.4', '4.2'],
|
||||
versionsARM: ['5.0', '4.4', '4.2']
|
||||
versions: ['6.0', '5.0', '4.4', '4.2'],
|
||||
versionsARM: ['6.0', '5.0', '4.4', '4.2']
|
||||
},
|
||||
{
|
||||
name: 'mysql',
|
||||
@@ -416,16 +422,16 @@ export const supportedDatabaseTypesAndVersions = [
|
||||
fancyName: 'MariaDB',
|
||||
baseImage: 'bitnami/mariadb',
|
||||
baseImageARM: 'mariadb',
|
||||
versions: ['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']
|
||||
versions: ['10.11', '10.10', '10.9', '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',
|
||||
fancyName: 'PostgreSQL',
|
||||
baseImage: 'bitnami/postgresql',
|
||||
baseImageARM: 'postgres',
|
||||
versions: ['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']
|
||||
versions: ['15.2.0', '14.7.0', '14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
|
||||
versionsARM: ['15.2', '14.7', '14.5', '13.8', '12.12', '11.17', '10.22']
|
||||
},
|
||||
{
|
||||
name: 'redis',
|
||||
@@ -440,14 +446,14 @@ export const supportedDatabaseTypesAndVersions = [
|
||||
fancyName: 'CouchDB',
|
||||
baseImage: 'bitnami/couchdb',
|
||||
baseImageARM: 'couchdb',
|
||||
versions: ['3.2.2', '3.1.2', '2.3.1'],
|
||||
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
|
||||
versions: ['3.3.1', '3.2.2', '3.1.2', '2.3.1'],
|
||||
versionsARM: ['3.3', '3.2.2', '3.1.2', '2.3.1']
|
||||
},
|
||||
{
|
||||
name: 'edgedb',
|
||||
fancyName: '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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ssh config file with a host
|
||||
*
|
||||
* @param id Destination ID
|
||||
* @returns
|
||||
*/
|
||||
export async function createRemoteEngineConfiguration(id: string) {
|
||||
const homedir = os.homedir();
|
||||
const sshKeyFile = `/tmp/id_rsa-${id}`;
|
||||
const localPort = await getFreeSSHLocalPort(id);
|
||||
const {
|
||||
sshKey: { privateKey },
|
||||
network,
|
||||
remoteIpAddress,
|
||||
remotePort,
|
||||
remoteUser
|
||||
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
|
||||
|
||||
// Write new keyfile
|
||||
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
|
||||
const config = sshConfig.parse('');
|
||||
|
||||
const Host = `${remoteIpAddress}-remote`;
|
||||
|
||||
// Removes previous ssh-keys
|
||||
try {
|
||||
await executeCommand({ command: `ssh-keygen -R ${Host}` });
|
||||
await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` });
|
||||
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 foundIp = config.find({ Host: remoteIpAddress });
|
||||
|
||||
if (found) config.remove({ Host });
|
||||
if (foundIp) config.remove({ Host: remoteIpAddress });
|
||||
|
||||
// Create the new config
|
||||
config.append({
|
||||
Host,
|
||||
Hostname: remoteIpAddress,
|
||||
@@ -535,13 +564,17 @@ export async function createRemoteEngineConfiguration(id: string) {
|
||||
ControlPersist: '10m'
|
||||
});
|
||||
|
||||
// Check if .ssh folder exists, and if not create one
|
||||
try {
|
||||
await fs.stat(`${homedir}/.ssh/`);
|
||||
} catch (error) {
|
||||
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({
|
||||
command,
|
||||
dockerId = null,
|
||||
@@ -550,7 +583,8 @@ export async function executeCommand({
|
||||
stream = false,
|
||||
buildId,
|
||||
applicationId,
|
||||
debug
|
||||
debug,
|
||||
timeout = 0
|
||||
}: {
|
||||
command: string;
|
||||
sshCommand?: boolean;
|
||||
@@ -560,6 +594,7 @@ export async function executeCommand({
|
||||
buildId?: string;
|
||||
applicationId?: string;
|
||||
debug?: boolean;
|
||||
timeout?: number;
|
||||
}): Promise<ExecaChildProcess<string>> {
|
||||
const { execa, execaCommand } = await import('execa');
|
||||
const { parse } = await import('shell-quote');
|
||||
@@ -584,20 +619,26 @@ export async function executeCommand({
|
||||
}
|
||||
if (sshCommand) {
|
||||
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) {
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
let subprocess = null;
|
||||
if (shell) {
|
||||
subprocess = execaCommand(command, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
} else {
|
||||
subprocess = execa(dockerCommand, dockerArgs, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
}
|
||||
const logs = [];
|
||||
@@ -651,19 +692,26 @@ export async function executeCommand({
|
||||
} else {
|
||||
if (shell) {
|
||||
return await execaCommand(command, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
} else {
|
||||
return await execa(dockerCommand, dockerArgs, {
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
|
||||
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
|
||||
timeout
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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 /var/run/docker.sock:/var/run/docker.sock \
|
||||
--network coolify-infra \
|
||||
-p "80:80" \
|
||||
-p "443:443" \
|
||||
-p ${proxyPort ? `${proxyPort}:80` : `80:80`} \
|
||||
-p ${proxySecurePort ? `${proxySecurePort}:443` : `443:443`} \
|
||||
${isDev ? '-p "8080:8080"' : ''} \
|
||||
--name coolify-proxy \
|
||||
-d ${defaultTraefikImage} \
|
||||
@@ -797,7 +845,7 @@ export function generateToken() {
|
||||
{
|
||||
nbf: Math.floor(Date.now() / 1000) - 30
|
||||
},
|
||||
process.env['COOLIFY_SECRET_KEY']
|
||||
getSecretKey()
|
||||
);
|
||||
}
|
||||
export function generatePassword({
|
||||
@@ -820,101 +868,101 @@ export function generatePassword({
|
||||
|
||||
type DatabaseConfiguration =
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MYSQL_DATABASE: string;
|
||||
MYSQL_PASSWORD: string;
|
||||
MYSQL_ROOT_USER: string;
|
||||
MYSQL_USER: string;
|
||||
MYSQL_ROOT_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MYSQL_DATABASE: string;
|
||||
MYSQL_PASSWORD: string;
|
||||
MYSQL_ROOT_USER: string;
|
||||
MYSQL_USER: string;
|
||||
MYSQL_ROOT_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||
MONGODB_ROOT_USER?: string;
|
||||
MONGODB_ROOT_PASSWORD?: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MONGO_INITDB_ROOT_USERNAME?: string;
|
||||
MONGO_INITDB_ROOT_PASSWORD?: string;
|
||||
MONGODB_ROOT_USER?: string;
|
||||
MONGODB_ROOT_PASSWORD?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: string;
|
||||
MARIADB_ROOT_PASSWORD: string;
|
||||
MARIADB_USER: string;
|
||||
MARIADB_PASSWORD: string;
|
||||
MARIADB_DATABASE: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
MARIADB_ROOT_USER: string;
|
||||
MARIADB_ROOT_PASSWORD: string;
|
||||
MARIADB_USER: string;
|
||||
MARIADB_PASSWORD: string;
|
||||
MARIADB_DATABASE: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
POSTGRES_PASSWORD?: string;
|
||||
POSTGRES_USER?: string;
|
||||
POSTGRES_DB?: string;
|
||||
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
||||
POSTGRESQL_USERNAME?: string;
|
||||
POSTGRESQL_PASSWORD?: string;
|
||||
POSTGRESQL_DATABASE?: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
POSTGRES_PASSWORD?: string;
|
||||
POSTGRES_USER?: string;
|
||||
POSTGRES_DB?: string;
|
||||
POSTGRESQL_POSTGRES_PASSWORD?: string;
|
||||
POSTGRESQL_USERNAME?: string;
|
||||
POSTGRESQL_PASSWORD?: string;
|
||||
POSTGRESQL_DATABASE?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
REDIS_AOF_ENABLED: string;
|
||||
REDIS_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
REDIS_AOF_ENABLED: string;
|
||||
REDIS_PASSWORD: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
COUCHDB_PASSWORD: string;
|
||||
COUCHDB_USER: string;
|
||||
};
|
||||
}
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
COUCHDB_PASSWORD: string;
|
||||
COUCHDB_USER: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
EDGEDB_SERVER_PASSWORD: string;
|
||||
EDGEDB_SERVER_USER: string;
|
||||
EDGEDB_SERVER_DATABASE: string;
|
||||
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
||||
};
|
||||
};
|
||||
export function generateDatabaseConfiguration(database: any, arch: string): DatabaseConfiguration {
|
||||
volume: string;
|
||||
image: string;
|
||||
command?: string;
|
||||
ulimits: Record<string, unknown>;
|
||||
privatePort: number;
|
||||
environmentVariables: {
|
||||
EDGEDB_SERVER_PASSWORD: string;
|
||||
EDGEDB_SERVER_USER: string;
|
||||
EDGEDB_SERVER_DATABASE: string;
|
||||
EDGEDB_SERVER_TLS_CERT_MODE: string;
|
||||
};
|
||||
};
|
||||
export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
|
||||
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
|
||||
database;
|
||||
const baseImage = getDatabaseImage(type, arch);
|
||||
const baseImage = getDatabaseImage(type);
|
||||
if (type === 'mysql') {
|
||||
const configuration = {
|
||||
privatePort: 3306,
|
||||
@@ -929,7 +977,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||
}
|
||||
return configuration;
|
||||
@@ -947,7 +995,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/mariadb`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
|
||||
}
|
||||
return configuration;
|
||||
@@ -962,7 +1010,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
configuration.environmentVariables = {
|
||||
MONGO_INITDB_ROOT_USERNAME: rootUser,
|
||||
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
|
||||
@@ -983,8 +1031,8 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`;
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/var/lib/postgresql/data`;
|
||||
configuration.environmentVariables = {
|
||||
POSTGRES_PASSWORD: dbUserPassword,
|
||||
POSTGRES_USER: dbUser,
|
||||
@@ -1007,11 +1055,10 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/data`;
|
||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${
|
||||
appendOnly ? 'yes' : 'no'
|
||||
} --requirepass ${dbUserPassword}`;
|
||||
configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
|
||||
} --requirepass ${dbUserPassword}`;
|
||||
}
|
||||
return configuration;
|
||||
} else if (type === 'couchdb') {
|
||||
@@ -1025,7 +1072,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
||||
ulimits: {}
|
||||
};
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
|
||||
}
|
||||
return configuration;
|
||||
@@ -1045,16 +1092,17 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
export function isARM(arch: string) {
|
||||
export function isARM() {
|
||||
const arch = process.arch;
|
||||
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function getDatabaseImage(type: string, arch: string): string {
|
||||
export function getDatabaseImage(type: string): string {
|
||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
return found.baseImageARM || found.baseImage;
|
||||
}
|
||||
return found.baseImage;
|
||||
@@ -1062,10 +1110,10 @@ export function getDatabaseImage(type: string, arch: string): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
export function getDatabaseVersions(type: string, arch: string): string[] {
|
||||
export function getDatabaseVersions(type: string): string[] {
|
||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
if (isARM(arch)) {
|
||||
if (isARM()) {
|
||||
return found.versionsARM || found.versions;
|
||||
}
|
||||
return found.versions;
|
||||
@@ -1095,12 +1143,12 @@ export type ComposeFileService = {
|
||||
command?: string;
|
||||
ports?: string[];
|
||||
build?:
|
||||
| {
|
||||
context: string;
|
||||
dockerfile: string;
|
||||
args?: Record<string, unknown>;
|
||||
}
|
||||
| string;
|
||||
| {
|
||||
context: string;
|
||||
dockerfile: string;
|
||||
args?: Record<string, unknown>;
|
||||
}
|
||||
| string;
|
||||
deploy?: {
|
||||
restart_policy?: {
|
||||
condition?: string;
|
||||
@@ -1171,7 +1219,7 @@ export const createDirectories = async ({
|
||||
let workdirFound = false;
|
||||
try {
|
||||
workdirFound = !!(await fs.stat(workdir));
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
if (workdirFound) {
|
||||
await executeCommand({ command: `rm -fr ${workdir}` });
|
||||
}
|
||||
@@ -1631,8 +1679,8 @@ export function errorHandler({
|
||||
type?: string | null;
|
||||
}) {
|
||||
if (message.message) message = message.message;
|
||||
if (type === 'normal') {
|
||||
Sentry.captureException(message);
|
||||
if (message.includes('Unique constraint failed')) {
|
||||
message = 'This data is unique and already exists. Please try again with a different value.';
|
||||
}
|
||||
throw { status, message };
|
||||
}
|
||||
@@ -1695,7 +1743,7 @@ export async function stopBuild(buildId, applicationId) {
|
||||
}
|
||||
}
|
||||
count++;
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
@@ -1714,77 +1762,28 @@ export function convertTolOldVolumeNames(type) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
||||
// Cleanup old coolify images
|
||||
export async function cleanupDockerStorage(dockerId, volumes = false) {
|
||||
// Cleanup images that are not used by any container
|
||||
try {
|
||||
let { stdout: images } = await executeCommand({
|
||||
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||
} catch (error) { }
|
||||
|
||||
// Prune coolify managed containers
|
||||
try {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`,
|
||||
shell: true
|
||||
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||
});
|
||||
} catch (error) { }
|
||||
|
||||
images = images.trim();
|
||||
if (images) {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command: `docker rmi -f ${images}" -q | xargs -r`,
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
if (lowDiskSpace || force) {
|
||||
// Cleanup images that are not used
|
||||
// Cleanup build caches
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||
} catch (error) { }
|
||||
if (volumes) {
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker image prune -f` });
|
||||
} 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) {}
|
||||
await executeCommand({ dockerId, command: `docker volume prune -af` });
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1948,3 +1947,49 @@ export function generateSecrets(
|
||||
}
|
||||
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 {
|
||||
let data = await open.readFile({ encoding: 'utf-8' });
|
||||
let jsonData = JSON.parse(data);
|
||||
if (isARM(process.arch)) {
|
||||
if (isARM()) {
|
||||
jsonData = jsonData.filter((d) => d.arch !== 'amd64');
|
||||
}
|
||||
return jsonData;
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
const { id } = request.params;
|
||||
const teamId = request.user.teamId;
|
||||
const service = await getServiceFromDB({ id, teamId });
|
||||
const arm = isARM(service.arch);
|
||||
const arm = isARM();
|
||||
const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } =
|
||||
service;
|
||||
|
||||
@@ -50,24 +50,12 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
|
||||
const config = {};
|
||||
for (const s in template.services) {
|
||||
let newEnvironments = []
|
||||
if (arm) {
|
||||
if (template.services[s]?.environmentArm?.length > 0) {
|
||||
for (const environment of template.services[s].environmentArm) {
|
||||
let [env, ...value] = environment.split("=");
|
||||
value = value.join("=")
|
||||
if (!value.startsWith('$$secret') && 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}`)
|
||||
}
|
||||
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 } })
|
||||
let volumes = new Set()
|
||||
if (arm) {
|
||||
template.services[s]?.volumesArm && template.services[s].volumesArm.length > 0 && template.services[s].volumesArm.forEach(v => volumes.add(v))
|
||||
if (arm && template.services[s]?.volumesArm?.length > 0) {
|
||||
template.services[s].volumesArm.forEach(v => volumes.add(v))
|
||||
} 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
|
||||
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
|
||||
let temp = Array.from(volumes)
|
||||
|
||||
@@ -624,7 +624,7 @@ export const glitchTip = [{
|
||||
isEncrypted: false
|
||||
},
|
||||
{
|
||||
name: 'emailSmtpUseSsl',
|
||||
name: 'emailSmtpUseTls',
|
||||
isEditable: true,
|
||||
isLowerCase: false,
|
||||
isNumber: false,
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import fp from 'fastify-plugin'
|
||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt'
|
||||
import fp from 'fastify-plugin';
|
||||
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt';
|
||||
|
||||
declare module "@fastify/jwt" {
|
||||
interface FastifyJWT {
|
||||
user: {
|
||||
userId: string,
|
||||
teamId: string,
|
||||
permission: string,
|
||||
isAdmin: boolean
|
||||
}
|
||||
}
|
||||
declare module '@fastify/jwt' {
|
||||
interface FastifyJWT {
|
||||
user: {
|
||||
userId: string;
|
||||
teamId: string;
|
||||
permission: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default fp<FastifyJWTOptions>(async (fastify, opts) => {
|
||||
fastify.register(fastifyJwt, {
|
||||
secret: fastify.config.COOLIFY_SECRET_KEY
|
||||
})
|
||||
let secretKey = fastify.config.COOLIFY_SECRET_KEY_BETTER;
|
||||
if (!secretKey) {
|
||||
secretKey = fastify.config.COOLIFY_SECRET_KEY;
|
||||
}
|
||||
fastify.register(fastifyJwt, {
|
||||
secret: secretKey
|
||||
});
|
||||
|
||||
fastify.decorate("authenticate", async function (request, reply) {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch (err) {
|
||||
reply.send(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
fastify.decorate('authenticate', async function (request, reply) {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch (err) {
|
||||
reply.send(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
declare module 'fastify' {
|
||||
export interface FastifyInstance {
|
||||
authenticate(): Promise<void>
|
||||
}
|
||||
export interface FastifyInstance {
|
||||
authenticate(): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export async function getImages(request: FastifyRequest<GetImages>) {
|
||||
export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) {
|
||||
try {
|
||||
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 } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
@@ -167,7 +167,7 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { teamId } = request.user;
|
||||
let payload = [];
|
||||
const payload = [];
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
if (application.buildPack === 'compose') {
|
||||
@@ -398,7 +398,9 @@ export async function saveApplication(
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
dockerRegistryImageName
|
||||
dockerRegistryImageName,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
} = request.body;
|
||||
if (port) port = Number(port);
|
||||
if (exposePort) {
|
||||
@@ -453,6 +455,8 @@ export async function saveApplication(
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
dockerRegistryImageName,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
...defaultConfiguration,
|
||||
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
|
||||
}
|
||||
@@ -476,6 +480,8 @@ export async function saveApplication(
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
simpleDockerfile,
|
||||
basicAuthPw,
|
||||
basicAuthUser,
|
||||
dockerRegistryImageName,
|
||||
...defaultConfiguration
|
||||
}
|
||||
@@ -499,12 +505,11 @@ export async function saveApplicationSettings(
|
||||
previews,
|
||||
dualCerts,
|
||||
autodeploy,
|
||||
branch,
|
||||
projectId,
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
} = request.body;
|
||||
await prisma.application.update({
|
||||
where: { id },
|
||||
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
|
||||
isBot,
|
||||
isDBBranching,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
isHttp2,
|
||||
basicAuth,
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -565,7 +571,7 @@ export async function restartApplication(
|
||||
const { id } = request.params;
|
||||
const { imageId = null } = request.body;
|
||||
const { teamId } = request.user;
|
||||
let application: any = await getApplicationFromDB(id, teamId);
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
const buildId = cuid();
|
||||
const { id: dockerId, network } = application.destinationDocker;
|
||||
@@ -640,9 +646,7 @@ export async function restartApplication(
|
||||
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||
buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
@@ -732,14 +736,15 @@ export async function deleteApplication(
|
||||
) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { force } = request.body;
|
||||
|
||||
const { teamId } = request.user;
|
||||
const application = await prisma.application.findUnique({
|
||||
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({
|
||||
dockerId: application.destinationDocker.id,
|
||||
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) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { path, newStorage, storageId } = request.body;
|
||||
const { hostPath, path, newStorage, storageId } = request.body;
|
||||
|
||||
if (newStorage) {
|
||||
await prisma.applicationPersistentStorage.create({
|
||||
data: { path, application: { connect: { id } } }
|
||||
data: { hostPath, path, application: { connect: { id } } }
|
||||
});
|
||||
} else {
|
||||
await prisma.applicationPersistentStorage.update({
|
||||
where: { id: storageId },
|
||||
data: { path }
|
||||
data: { hostPath, path }
|
||||
});
|
||||
}
|
||||
return reply.code(201).send();
|
||||
@@ -1376,7 +1381,7 @@ export async function restartPreview(
|
||||
try {
|
||||
const { id, pullmergeRequestId } = request.params;
|
||||
const { teamId } = request.user;
|
||||
let application: any = await getApplicationFromDB(id, teamId);
|
||||
const application: any = await getApplicationFromDB(id, teamId);
|
||||
if (application?.destinationDockerId) {
|
||||
const buildId = cuid();
|
||||
const { id: dockerId, network } = application.destinationDocker;
|
||||
@@ -1427,9 +1432,8 @@ export async function restartPreview(
|
||||
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
|
||||
buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
|
||||
}${storage.path}`;
|
||||
}) || [];
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
return {
|
||||
@@ -1691,7 +1695,7 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
||||
try {
|
||||
await fs.stat(file);
|
||||
} catch (error) {
|
||||
let logs = await prisma.buildLog.findMany({
|
||||
const logs = await prisma.buildLog.findMany({
|
||||
where: { buildId, time: { gt: sequence } },
|
||||
orderBy: { time: 'asc' }
|
||||
});
|
||||
@@ -1707,9 +1711,9 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
|
||||
status: data?.status || 'queued'
|
||||
};
|
||||
}
|
||||
let fileLogs = (await fs.readFile(file)).toString();
|
||||
let decryptedLogs = await csv({ noheader: true }).fromString(fileLogs);
|
||||
let logs = decryptedLogs
|
||||
const fileLogs = (await fs.readFile(file)).toString();
|
||||
const decryptedLogs = await csv({ noheader: true }).fromString(fileLogs);
|
||||
const logs = decryptedLogs
|
||||
.map((log) => {
|
||||
const parsed = {
|
||||
time: log['field1'],
|
||||
|
||||
@@ -28,6 +28,8 @@ export interface SaveApplication extends OnlyId {
|
||||
dockerComposeConfiguration: string;
|
||||
simpleDockerfile: string;
|
||||
dockerRegistryImageName: string;
|
||||
basicAuthPw: string;
|
||||
basicAuthUser: string;
|
||||
};
|
||||
}
|
||||
export interface SaveApplicationSettings extends OnlyId {
|
||||
@@ -43,6 +45,7 @@ export interface SaveApplicationSettings extends OnlyId {
|
||||
isDBBranching: boolean;
|
||||
isCustomSSL: boolean;
|
||||
isHttp2: boolean;
|
||||
basicAuth: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
@@ -96,6 +99,7 @@ export interface DeleteSecret extends OnlyId {
|
||||
}
|
||||
export interface SaveStorage extends OnlyId {
|
||||
Body: {
|
||||
hostPath?: string;
|
||||
path: string;
|
||||
newStorage: boolean;
|
||||
storageId: string;
|
||||
|
||||
@@ -20,7 +20,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||
isARM: isARM(process.arch)
|
||||
isARM: isARM()
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
|
||||
@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
|
||||
import fs from 'fs/promises';
|
||||
import {
|
||||
ComposeFile,
|
||||
backupDatabaseNow,
|
||||
createDirectories,
|
||||
decrypt,
|
||||
defaultComposeConfiguration,
|
||||
@@ -302,7 +303,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
databaseSecret
|
||||
} = database;
|
||||
const { privatePort, command, environmentVariables, image, volume, ulimits } =
|
||||
generateDatabaseConfiguration(database, arch);
|
||||
generateDatabaseConfiguration(database);
|
||||
|
||||
const network = destinationDockerId && destinationDocker.network;
|
||||
const volumeName = volume.split(':')[0];
|
||||
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
|
||||
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>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
@@ -427,19 +443,15 @@ export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
const { force } = request.body;
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
if (!force) {
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted)
|
||||
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
|
||||
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
|
||||
if (database.destinationDockerId) {
|
||||
const everStarted = await stopDatabaseContainer(database);
|
||||
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
|
||||
}
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -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/stop', async (request) => await stopDatabase(request));
|
||||
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
|
||||
Body: { type: string }
|
||||
}
|
||||
export interface DeleteDatabase extends OnlyId {
|
||||
Body: { force: string }
|
||||
Body: { }
|
||||
}
|
||||
export interface SaveVersion extends OnlyId {
|
||||
Body: {
|
||||
|
||||
@@ -1,279 +1,384 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import sshConfig from 'ssh-config'
|
||||
import fs from 'fs/promises'
|
||||
import os from 'os';
|
||||
|
||||
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import {
|
||||
errorHandler,
|
||||
executeCommand,
|
||||
listSettings,
|
||||
prisma,
|
||||
startTraefikProxy,
|
||||
stopTraefikProxy
|
||||
} from '../../../../lib/common';
|
||||
import { checkContainer } from '../../../../lib/docker';
|
||||
|
||||
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>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { onlyVerified = false } = request.query
|
||||
let destinations = []
|
||||
if (teamId === '0') {
|
||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||
} else {
|
||||
destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
if (onlyVerified) {
|
||||
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
|
||||
}
|
||||
return {
|
||||
destinations
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { onlyVerified = false } = request.query;
|
||||
let destinations = [];
|
||||
if (teamId === '0') {
|
||||
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
|
||||
} else {
|
||||
destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
if (onlyVerified) {
|
||||
destinations = destinations.filter(
|
||||
(destination) =>
|
||||
destination.engine || (destination.remoteEngine && destination.remoteVerified)
|
||||
);
|
||||
}
|
||||
return {
|
||||
destinations
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function checkDestination(request: FastifyRequest<CheckDestination>) {
|
||||
try {
|
||||
const { network } = request.body;
|
||||
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Network already exists: ${network}`
|
||||
};
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { network } = request.body;
|
||||
const found = await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
if (found) {
|
||||
throw {
|
||||
message: `Network already exists: ${network}`
|
||||
};
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function getDestination(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const teamId = request.user?.teamId;
|
||||
const destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { sshKey: true, application: true, service: true, database: true }
|
||||
});
|
||||
if (!destination && id !== 'new') {
|
||||
throw { status: 404, message: `Destination not found.` };
|
||||
}
|
||||
const settings = await listSettings();
|
||||
const payload = {
|
||||
destination,
|
||||
settings
|
||||
};
|
||||
return {
|
||||
...payload
|
||||
};
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const teamId = request.user?.teamId;
|
||||
const destination = await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { sshKey: true, application: true, service: true, database: true }
|
||||
});
|
||||
if (!destination && id !== 'new') {
|
||||
throw { status: 404, message: `Destination not found.` };
|
||||
}
|
||||
const settings = await listSettings();
|
||||
const payload = {
|
||||
destination,
|
||||
settings
|
||||
};
|
||||
return {
|
||||
...payload
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.params;
|
||||
|
||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
|
||||
if (id === 'new') {
|
||||
if (engine) {
|
||||
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
|
||||
if (stdout === '') {
|
||||
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
||||
}
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const destination = destinations.find((destination) => destination.network === network);
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||
}
|
||||
if (isCoolifyProxyUsed) {
|
||||
await startTraefikProxy(destination.id);
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
} else {
|
||||
const destination = await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, 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 })
|
||||
}
|
||||
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
|
||||
request.body;
|
||||
if (id === 'new') {
|
||||
if (engine) {
|
||||
const { stdout } = await await executeCommand({
|
||||
command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
|
||||
});
|
||||
if (stdout === '') {
|
||||
await await executeCommand({ command: `docker network create --attachable ${network}` });
|
||||
}
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const destination = destinations.find((destination) => destination.network === network);
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) =>
|
||||
destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
}
|
||||
if (isCoolifyProxyUsed) {
|
||||
await startTraefikProxy(destination.id);
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
} else {
|
||||
const destination = await prisma.destinationDocker.create({
|
||||
data: {
|
||||
name,
|
||||
teams: { connect: { id: teamId } },
|
||||
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>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
if (isCoolifyProxyUsed) {
|
||||
if (engine || remoteVerified) {
|
||||
const { stdout: found } = await executeCommand({
|
||||
dockerId: id,
|
||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||
})
|
||||
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 })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
|
||||
const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
|
||||
const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
|
||||
if (appFound || serviceFound || databaseFound) {
|
||||
throw {
|
||||
message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
|
||||
};
|
||||
}
|
||||
const { network, remoteVerified, engine, isCoolifyProxyUsed } =
|
||||
await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
if (isCoolifyProxyUsed) {
|
||||
if (engine || remoteVerified) {
|
||||
const { stdout: found } = await executeCommand({
|
||||
dockerId: id,
|
||||
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
|
||||
});
|
||||
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>) {
|
||||
try {
|
||||
const { engine, isCoolifyProxyUsed } = request.body;
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
try {
|
||||
const { engine, isCoolifyProxyUsed } = request.body;
|
||||
await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
|
||||
return {
|
||||
status: 202
|
||||
}
|
||||
// return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
status: 202
|
||||
};
|
||||
// return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function startProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await startTraefikProxy(id);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
await stopTraefikProxy(id);
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await startTraefikProxy(id);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
await stopTraefikProxy(id);
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function stopProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function restartProxy(request: FastifyRequest<Proxy>) {
|
||||
const { id } = request.params
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
await startTraefikProxy(id);
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: true }
|
||||
});
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: false }
|
||||
});
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await stopTraefikProxy(id);
|
||||
await startTraefikProxy(id);
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: true }
|
||||
});
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { isCoolifyProxyUsed: false }
|
||||
});
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function assignSSHKey(request: FastifyRequest) {
|
||||
try {
|
||||
const { id: sshKeyId } = request.body;
|
||||
const { id } = request.params;
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id: sshKeyId } = request.body;
|
||||
const { id } = request.params;
|
||||
await prisma.destinationDocker.update({
|
||||
where: { id },
|
||||
data: { sshKey: { connect: { id: sshKeyId } } }
|
||||
});
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function verifyRemoteDockerEngineFn(id: string) {
|
||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
|
||||
const daemonJson = `daemon-${id}.json`
|
||||
try {
|
||||
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id });
|
||||
} catch (error) {
|
||||
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
|
||||
}
|
||||
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
|
||||
{ where: { id } }
|
||||
);
|
||||
const daemonJson = `daemon-${id}.json`;
|
||||
try {
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
command: `docker network inspect ${network}`,
|
||||
dockerId: id
|
||||
});
|
||||
} catch (error) {
|
||||
await executeCommand({
|
||||
command: `docker network create --attachable ${network}`,
|
||||
dockerId: id
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id });
|
||||
} catch (error) {
|
||||
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
|
||||
}
|
||||
try {
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
command: `docker network inspect coolify-infra`,
|
||||
dockerId: id
|
||||
});
|
||||
} catch (error) {
|
||||
await executeCommand({
|
||||
command: `docker network create --attachable coolify-infra`,
|
||||
dockerId: id
|
||||
});
|
||||
}
|
||||
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
let isUpdated = false;
|
||||
let daemonJsonParsed = {
|
||||
"live-restore": true,
|
||||
"features": {
|
||||
"buildkit": true
|
||||
}
|
||||
};
|
||||
try {
|
||||
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` });
|
||||
daemonJsonParsed = JSON.parse(daemonJson);
|
||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed['live-restore'] = true
|
||||
|
||||
}
|
||||
if (!daemonJsonParsed?.features?.buildkit) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed.features = {
|
||||
buildkit: true
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
isUpdated = true;
|
||||
}
|
||||
try {
|
||||
if (isUpdated) {
|
||||
await executeCommand({ shell: true, command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}` })
|
||||
await executeCommand({ dockerId: id, command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json` });
|
||||
await executeCommand({ command: `rm /tmp/${daemonJson}` })
|
||||
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
|
||||
}
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
|
||||
} catch (error) {
|
||||
throw new Error('Error while verifying remote docker engine')
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startTraefikProxy(id);
|
||||
let isUpdated = false;
|
||||
let daemonJsonParsed = {
|
||||
'live-restore': true,
|
||||
features: {
|
||||
buildkit: true
|
||||
}
|
||||
};
|
||||
try {
|
||||
const { stdout: daemonJson } = await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId: id,
|
||||
command: `cat /etc/docker/daemon.json`
|
||||
});
|
||||
daemonJsonParsed = JSON.parse(daemonJson);
|
||||
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed['live-restore'] = true;
|
||||
}
|
||||
if (!daemonJsonParsed?.features?.buildkit) {
|
||||
isUpdated = true;
|
||||
daemonJsonParsed.features = {
|
||||
buildkit: true
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
isUpdated = true;
|
||||
}
|
||||
try {
|
||||
if (isUpdated) {
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: id,
|
||||
command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json`
|
||||
});
|
||||
await executeCommand({ command: `rm /tmp/${daemonJson}` });
|
||||
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
|
||||
}
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Error while verifying remote docker engine');
|
||||
}
|
||||
}
|
||||
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await verifyRemoteDockerEngineFn(id);
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
export async function verifyRemoteDockerEngine(
|
||||
request: FastifyRequest<OnlyId>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
const { id } = request.params;
|
||||
try {
|
||||
await verifyRemoteDockerEngineFn(id);
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
|
||||
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
|
||||
return {
|
||||
isRunning
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const destination = await prisma.destinationDocker.findUnique({ where: { id } });
|
||||
const { found: isRunning } = await checkContainer({
|
||||
dockerId: destination.id,
|
||||
container: 'coolify-proxy',
|
||||
remove: true
|
||||
});
|
||||
return {
|
||||
isRunning
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { 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.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||
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.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
prisma,
|
||||
uniqueName,
|
||||
version,
|
||||
sentryDSN,
|
||||
executeCommand
|
||||
} from '../../../lib/common';
|
||||
import { scheduler } from '../../../lib/scheduler';
|
||||
@@ -20,8 +19,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { Login, Update } from '.';
|
||||
import type { GetCurrentUser } from './types';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const saltRounds = 15;
|
||||
export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
|
||||
return bcrypt.hash(password, saltRounds);
|
||||
}
|
||||
|
||||
@@ -59,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
const destination = await prisma.destinationDocker.findUnique({
|
||||
where: { id: serverId }
|
||||
});
|
||||
await cleanupDockerStorage(destination.id, true, true);
|
||||
await cleanupDockerStorage(destination.id, true);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -78,7 +76,7 @@ export async function refreshTags() {
|
||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
} else {
|
||||
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')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||
} else {
|
||||
@@ -156,14 +154,21 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
try {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
||||
let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
|
||||
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({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
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 {};
|
||||
} else {
|
||||
@@ -445,7 +450,6 @@ export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fa
|
||||
});
|
||||
return {
|
||||
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||
sentryDSN,
|
||||
pendingInvitations,
|
||||
token,
|
||||
...request.user
|
||||
|
||||
@@ -50,6 +50,7 @@ import type {
|
||||
SetWordpressSettings
|
||||
} from './types';
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import { refreshTags, refreshTemplates } from '../handlers';
|
||||
|
||||
export async function listServices(request: FastifyRequest) {
|
||||
try {
|
||||
@@ -412,22 +413,23 @@ export async function saveServiceType(
|
||||
if (foundTemplate.variables) {
|
||||
if (foundTemplate.variables.length > 0) {
|
||||
for (const variable of foundTemplate.variables) {
|
||||
const { defaultValue } = variable;
|
||||
let { defaultValue } = variable;
|
||||
defaultValue = defaultValue.toString();
|
||||
const regex = /^\$\$.*\((\d+)\)$/g;
|
||||
const length = Number(regex.exec(defaultValue)?.[1]) || undefined;
|
||||
if (variable.defaultValue.startsWith('$$generate_password')) {
|
||||
if (defaultValue.startsWith('$$generate_password')) {
|
||||
variable.value = generatePassword({ length });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_hex')) {
|
||||
} else if (defaultValue.startsWith('$$generate_hex')) {
|
||||
variable.value = generatePassword({ length, isHex: true });
|
||||
} else if (variable.defaultValue.startsWith('$$generate_username')) {
|
||||
} else if (defaultValue.startsWith('$$generate_username')) {
|
||||
variable.value = cuid();
|
||||
} else if (variable.defaultValue.startsWith('$$generate_token')) {
|
||||
} else if (defaultValue.startsWith('$$generate_token')) {
|
||||
variable.value = generateToken();
|
||||
} else {
|
||||
variable.value = variable.defaultValue || '';
|
||||
variable.value = defaultValue || '';
|
||||
}
|
||||
const foundVariableSomewhereElse = foundTemplate.variables.find((v) =>
|
||||
v.defaultValue.includes(variable.id)
|
||||
v.defaultValue.toString().includes(variable.id)
|
||||
);
|
||||
if (foundVariableSomewhereElse) {
|
||||
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(
|
||||
@@ -475,7 +477,7 @@ export async function saveServiceType(
|
||||
const [volumeName, path] = volume.split(':');
|
||||
if (!volumeName.startsWith('/')) {
|
||||
const found = await prisma.servicePersistentStorage.findFirst({
|
||||
where: { volumeName, serviceId: id }
|
||||
where: { volumeName, serviceId: id, path }
|
||||
});
|
||||
if (!found) {
|
||||
await prisma.servicePersistentStorage.create({
|
||||
@@ -617,6 +619,29 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
|
||||
export async function deleteService(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
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 });
|
||||
return {};
|
||||
} 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;
|
||||
if (value) {
|
||||
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 (!variableName) {
|
||||
@@ -958,11 +986,22 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
|
||||
const teamId = request.user.teamId;
|
||||
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
|
||||
if (destinationDockerId) {
|
||||
await executeCommand({
|
||||
const logTables = await executeCommand({
|
||||
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.{};\"`,
|
||||
shell: true
|
||||
command: `docker exec ${id}-clickhouse clickhouse-client -q "SELECT name FROM system.tables;"`,
|
||||
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();
|
||||
}
|
||||
throw { status: 500, message: 'Could cleanup logs.' };
|
||||
@@ -1078,17 +1117,14 @@ export async function activateWordpressFtp(
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
const volumes = [
|
||||
`${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`,
|
||||
`${
|
||||
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`,
|
||||
`${
|
||||
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`
|
||||
];
|
||||
|
||||
@@ -1158,6 +1194,6 @@ export async function activateWordpressFtp(
|
||||
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`
|
||||
});
|
||||
} catch (error) {}
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,235 +1,312 @@
|
||||
import { promises as dns } from 'dns';
|
||||
import { X509Certificate } from 'node:crypto';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
|
||||
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
|
||||
|
||||
import {
|
||||
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) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
|
||||
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } })
|
||||
registries = registries.map((registry) => {
|
||||
if (registry.password) {
|
||||
registry.password = decrypt(registry.password)
|
||||
}
|
||||
return registry
|
||||
})
|
||||
const unencryptedKeys = []
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.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 })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } });
|
||||
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } });
|
||||
registries = registries.map((registry) => {
|
||||
if (registry.password) {
|
||||
registry.password = decrypt(registry.password);
|
||||
}
|
||||
return registry;
|
||||
});
|
||||
const unencryptedKeys = [];
|
||||
if (sshKeys.length > 0) {
|
||||
for (const key of sshKeys) {
|
||||
unencryptedKeys.push({
|
||||
id: key.id,
|
||||
name: key.name,
|
||||
privateKey: decrypt(key.privateKey),
|
||||
createdAt: key.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 {
|
||||
settings,
|
||||
certificates: cns,
|
||||
sshKeys: unencryptedKeys,
|
||||
registries
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
settings,
|
||||
certificates: cns,
|
||||
sshKeys: unencryptedKeys,
|
||||
registries
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
|
||||
try {
|
||||
let {
|
||||
previewSeparator,
|
||||
numberOfDockerImagesKeptLocally,
|
||||
doNotTrack,
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
proxyDefaultRedirect
|
||||
} = request.body
|
||||
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||
if (numberOfDockerImagesKeptLocally) {
|
||||
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally)
|
||||
}
|
||||
if (previewSeparator == '') {
|
||||
previewSeparator = '.'
|
||||
}
|
||||
if (SetPreviewSeparator != previewSeparator) {
|
||||
const applications = await prisma.application.findMany({ where: { previewApplication: { some: { id: { not: undefined } } } }, include: { previewApplication: true } })
|
||||
for (const application of applications) {
|
||||
for (const preview of application.previewApplication) {
|
||||
const { protocol } = new URL(preview.customDomain)
|
||||
const { pullmergeRequestId } = preview
|
||||
const { fqdn } = application
|
||||
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}`
|
||||
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } })
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
let {
|
||||
previewSeparator,
|
||||
numberOfDockerImagesKeptLocally,
|
||||
doNotTrack,
|
||||
fqdn,
|
||||
isAPIDebuggingEnabled,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
minPort,
|
||||
maxPort,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
proxyDefaultRedirect
|
||||
} = request.body;
|
||||
const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
|
||||
if (numberOfDockerImagesKeptLocally) {
|
||||
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally);
|
||||
}
|
||||
if (previewSeparator == '') {
|
||||
previewSeparator = '.';
|
||||
}
|
||||
if (SetPreviewSeparator != previewSeparator) {
|
||||
const applications = await prisma.application.findMany({
|
||||
where: { previewApplication: { some: { id: { not: undefined } } } },
|
||||
include: { previewApplication: true }
|
||||
});
|
||||
for (const application of applications) {
|
||||
for (const preview of application.previewApplication) {
|
||||
const { protocol } = new URL(preview.customDomain);
|
||||
const { pullmergeRequestId } = preview;
|
||||
const { fqdn } = application;
|
||||
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(
|
||||
fqdn
|
||||
)}`;
|
||||
await prisma.previewApplication.update({
|
||||
where: { id: preview.id },
|
||||
data: { customDomain: newPreviewDomain }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { previewSeparator, numberOfDockerImagesKeptLocally, doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled }
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
}
|
||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||
if (minPort && maxPort) {
|
||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||
}
|
||||
if (doNotTrack === false) {
|
||||
// Sentry.init({
|
||||
// dsn: sentryDSN,
|
||||
// environment: isDev ? 'development' : 'production',
|
||||
// release: version
|
||||
// });
|
||||
// console.log('Sentry initialized')
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: {
|
||||
previewSeparator,
|
||||
numberOfDockerImagesKeptLocally,
|
||||
doNotTrack,
|
||||
isRegistrationEnabled,
|
||||
dualCerts,
|
||||
isAutoUpdateEnabled,
|
||||
isDNSCheckEnabled,
|
||||
DNSServers,
|
||||
isAPIDebuggingEnabled
|
||||
}
|
||||
});
|
||||
if (fqdn) {
|
||||
await prisma.setting.update({ where: { id }, data: { fqdn } });
|
||||
}
|
||||
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
|
||||
if (minPort && maxPort) {
|
||||
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
|
||||
}
|
||||
if (doNotTrack === false) {
|
||||
// Sentry.init({
|
||||
// dsn: sentryDSN,
|
||||
// environment: isDev ? 'development' : 'production',
|
||||
// release: version
|
||||
// });
|
||||
// console.log('Sentry initialized')
|
||||
}
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
|
||||
try {
|
||||
const { fqdn } = request.body
|
||||
const { DNSServers } = await listSettings();
|
||||
if (DNSServers) {
|
||||
dns.setServers([...DNSServers.split(',')]);
|
||||
}
|
||||
let ip;
|
||||
try {
|
||||
ip = await dns.resolve(fqdn);
|
||||
} catch (error) {
|
||||
// Do not care.
|
||||
}
|
||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined)
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { fqdn } = request.body;
|
||||
const { DNSServers } = await listSettings();
|
||||
if (DNSServers) {
|
||||
dns.setServers([...DNSServers.split(',')]);
|
||||
}
|
||||
let ip;
|
||||
try {
|
||||
ip = await dns.resolve(fqdn);
|
||||
} catch (error) {
|
||||
// Do not care.
|
||||
}
|
||||
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined);
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
const found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw { message: "Domain already configured" };
|
||||
}
|
||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||
const hostname = request.hostname.split(':')[0]
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body;
|
||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||
const found = await isDomainConfigured({ id, fqdn });
|
||||
if (found) {
|
||||
throw { message: 'Domain already configured' };
|
||||
}
|
||||
if (isDNSCheckEnabled && !forceSave && !isDev) {
|
||||
const hostname = request.hostname.split(':')[0];
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||
try {
|
||||
const { domain } = request.params;
|
||||
await isDNSValid(request.hostname, domain);
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { domain } = request.params;
|
||||
await isDNSValid(request.hostname, domain);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { privateKey, name } = request.body;
|
||||
const found = await prisma.sshKey.findMany({ where: { name } })
|
||||
if (found.length > 0) {
|
||||
throw {
|
||||
message: "Name already used. Choose another one please."
|
||||
}
|
||||
}
|
||||
const encryptedSSHKey = encrypt(privateKey)
|
||||
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { privateKey, name } = request.body;
|
||||
const found = await prisma.sshKey.findMany({ where: { name } });
|
||||
if (found.length > 0) {
|
||||
throw {
|
||||
message: 'Name already used. Choose another one please.'
|
||||
};
|
||||
}
|
||||
const encryptedSSHKey = encrypt(privateKey);
|
||||
await prisma.sshKey.create({
|
||||
data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
|
||||
});
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.sshKey.deleteMany({ where: { id, teamId } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id } = request.body;
|
||||
await prisma.sshKey.deleteMany({ where: { id, teamId } });
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
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 })
|
||||
await prisma.certificate.deleteMany({ where: { id, teamId } })
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
export async function deleteCertificates(
|
||||
request: FastifyRequest<OnlyIdInBody>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
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
|
||||
});
|
||||
await prisma.certificate.deleteMany({ where: { id, teamId } });
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id, username, password } = request.body;
|
||||
export async function setDockerRegistry(
|
||||
request: FastifyRequest<SetDefaultRegistry>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { id, username, password } = request.body;
|
||||
|
||||
let encryptedPassword = ''
|
||||
if (password) encryptedPassword = encrypt(password)
|
||||
let encryptedPassword = '';
|
||||
if (password) encryptedPassword = encrypt(password);
|
||||
|
||||
if (teamId === '0') {
|
||||
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } })
|
||||
} else {
|
||||
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } })
|
||||
}
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
if (teamId === '0') {
|
||||
await prisma.dockerRegistry.update({
|
||||
where: { id },
|
||||
data: { username, password: encryptedPassword }
|
||||
});
|
||||
} else {
|
||||
await prisma.dockerRegistry.updateMany({
|
||||
where: { id, teamId },
|
||||
data: { username, password: encryptedPassword }
|
||||
});
|
||||
}
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { name, url, username, password } = request.body;
|
||||
export async function addDockerRegistry(
|
||||
request: FastifyRequest<AddDefaultRegistry>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
const { name, url, username, password } = request.body;
|
||||
|
||||
let encryptedPassword = ''
|
||||
if (password) encryptedPassword = encrypt(password)
|
||||
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
|
||||
let encryptedPassword = '';
|
||||
if (password) encryptedPassword = encrypt(password);
|
||||
await prisma.dockerRegistry.create({
|
||||
data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } }
|
||||
});
|
||||
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
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 });
|
||||
}
|
||||
}
|
||||
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 type { FastifyRequest } from 'fastify';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
|
||||
import { OnlyId } from '../../../../types';
|
||||
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
|
||||
|
||||
export async function listSources(request: FastifyRequest) {
|
||||
try {
|
||||
const teamId = request.user?.teamId;
|
||||
const sources = await prisma.gitSource.findMany({
|
||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { teams: true, githubApp: true, gitlabApp: true }
|
||||
});
|
||||
return {
|
||||
sources
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const teamId = request.user?.teamId;
|
||||
const sources = await prisma.gitSource.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { teams: true, githubApp: true, gitlabApp: true }
|
||||
});
|
||||
return {
|
||||
sources
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveSource(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
|
||||
if (customPort) customPort = Number(customPort)
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
try {
|
||||
const { id } = request.params;
|
||||
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
|
||||
if (customPort) customPort = Number(customPort);
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send();
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function getSource(request: FastifyRequest<OnlyId>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { teamId } = request.user;
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
|
||||
if (id === 'new') {
|
||||
return {
|
||||
source: {
|
||||
name: null,
|
||||
type: null,
|
||||
htmlUrl: null,
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
customUser: 'git',
|
||||
},
|
||||
settings
|
||||
}
|
||||
}
|
||||
if (id === 'new') {
|
||||
return {
|
||||
source: {
|
||||
name: null,
|
||||
type: null,
|
||||
htmlUrl: null,
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
customUser: 'git'
|
||||
},
|
||||
settings
|
||||
};
|
||||
}
|
||||
|
||||
const source = await prisma.gitSource.findFirst({
|
||||
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (!source) {
|
||||
throw { status: 404, message: 'Source not found.' }
|
||||
}
|
||||
const source = await prisma.gitSource.findFirst({
|
||||
where: {
|
||||
id,
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (!source) {
|
||||
throw { status: 404, message: 'Source not found.' };
|
||||
}
|
||||
|
||||
if (source?.githubApp?.clientSecret)
|
||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||
if (source?.githubApp?.webhookSecret)
|
||||
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
||||
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
||||
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
||||
if (source?.githubApp?.clientSecret)
|
||||
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
|
||||
if (source?.githubApp?.webhookSecret)
|
||||
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
|
||||
if (source?.githubApp?.privateKey)
|
||||
source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
|
||||
if (source?.gitlabApp?.appSecret)
|
||||
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
|
||||
|
||||
return {
|
||||
source,
|
||||
settings
|
||||
};
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
return {
|
||||
source,
|
||||
settings
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSource(request) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const source = await prisma.gitSource.delete({
|
||||
where: { id },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (source.githubAppId) {
|
||||
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 })
|
||||
}
|
||||
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
|
||||
if (gitAppFound) {
|
||||
throw {
|
||||
status: 400,
|
||||
message: 'This source is used by an application. Please remove the application first.'
|
||||
};
|
||||
}
|
||||
const source = await prisma.gitSource.delete({
|
||||
where: { id },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (source.githubAppId) {
|
||||
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>) {
|
||||
try {
|
||||
const { teamId } = request.user
|
||||
try {
|
||||
const { teamId } = request.user;
|
||||
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body
|
||||
const { id } = request.params;
|
||||
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
|
||||
|
||||
if (customPort) customPort = Number(customPort)
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization,
|
||||
customPort,
|
||||
isSystemWide,
|
||||
type: 'github',
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
id: newId
|
||||
}
|
||||
}
|
||||
throw { status: 500, message: 'Wrong request.' }
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
if (customPort) customPort = Number(customPort);
|
||||
if (id === 'new') {
|
||||
const newId = cuid();
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization,
|
||||
customPort,
|
||||
isSystemWide,
|
||||
type: 'github',
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
id: newId
|
||||
};
|
||||
}
|
||||
throw { status: 500, message: 'Wrong request.' };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
|
||||
request.body
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const { teamId } = request.user;
|
||||
let {
|
||||
type,
|
||||
name,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
oauthId,
|
||||
appId,
|
||||
appSecret,
|
||||
groupName,
|
||||
customPort,
|
||||
customUser
|
||||
} = request.body;
|
||||
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
if (customPort) customPort = Number(customPort)
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
if (customPort) customPort = Number(customPort);
|
||||
const encryptedAppSecret = encrypt(appSecret);
|
||||
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
gitSource: { connect: { id: newId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
status: 201,
|
||||
id: newId
|
||||
}
|
||||
} else {
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
}
|
||||
});
|
||||
}
|
||||
return { status: 201 };
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
if (id === 'new') {
|
||||
const newId = cuid();
|
||||
await prisma.gitSource.create({
|
||||
data: {
|
||||
id: newId,
|
||||
type,
|
||||
apiUrl,
|
||||
htmlUrl,
|
||||
name,
|
||||
customPort,
|
||||
customUser,
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encryptedAppSecret,
|
||||
gitSource: { connect: { id: newId } }
|
||||
}
|
||||
});
|
||||
return {
|
||||
status: 201,
|
||||
id: newId
|
||||
};
|
||||
} else {
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { type, apiUrl, htmlUrl, name, customPort, customUser }
|
||||
});
|
||||
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>) {
|
||||
try {
|
||||
const { oauthId } = request.body
|
||||
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
||||
if (found) {
|
||||
throw { status: 500, message: 'OAuthID already configured in Coolify.' }
|
||||
}
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
try {
|
||||
const { oauthId } = request.body;
|
||||
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
|
||||
if (found) {
|
||||
throw { status: 500, message: 'OAuthID already configured in Coolify.' };
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 { 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) {
|
||||
if (isHttp2) {
|
||||
@@ -38,7 +40,7 @@ function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps
|
||||
}
|
||||
};
|
||||
}
|
||||
function generateRouters(
|
||||
async function generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
@@ -47,17 +49,22 @@ function generateRouters(
|
||||
isWWW,
|
||||
isDualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2 = false
|
||||
) {
|
||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
let http: any = {
|
||||
isHttp2 = false,
|
||||
httpBasicAuth = null,
|
||||
}) {
|
||||
const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
|
||||
|
||||
const http: any = {
|
||||
entrypoints: ['web'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let https: any = {
|
||||
const https: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
@@ -67,16 +74,16 @@ function generateRouters(
|
||||
},
|
||||
middlewares: []
|
||||
};
|
||||
let httpWWW: any = {
|
||||
const httpWWW: any = {
|
||||
entrypoints: ['web'],
|
||||
rule,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
};
|
||||
let httpsWWW: any = {
|
||||
const httpsWWW: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
tls: {
|
||||
@@ -93,6 +100,10 @@ function generateRouters(
|
||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. http + www only
|
||||
@@ -104,6 +115,10 @@ function generateRouters(
|
||||
https.middlewares.push('redirect-to-www');
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 5. https + non-www only
|
||||
if (isHttps && !isWWW) {
|
||||
@@ -132,6 +147,10 @@ function generateRouters(
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
}
|
||||
// 6. https + www only
|
||||
if (isHttps && isWWW) {
|
||||
@@ -141,6 +160,11 @@ function generateRouters(
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
}
|
||||
|
||||
if (httpBasicAuth) {
|
||||
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
|
||||
}
|
||||
|
||||
if (isCustomSSL) {
|
||||
if (isDualCerts) {
|
||||
https.tls = true;
|
||||
@@ -162,23 +186,23 @@ function generateRouters(
|
||||
}
|
||||
}
|
||||
if (isHttp2) {
|
||||
let http2 = {
|
||||
const http2 = {
|
||||
...http,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let http2WWW = {
|
||||
const http2WWW = {
|
||||
...httpWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let https2 = {
|
||||
const https2 = {
|
||||
...https,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
|
||||
let https2WWW = {
|
||||
const https2WWW = {
|
||||
...httpsWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
@@ -194,14 +218,17 @@ function generateRouters(
|
||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
const result = {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${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 = {
|
||||
tls: {
|
||||
certificates: []
|
||||
@@ -294,7 +321,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
});
|
||||
}
|
||||
|
||||
let parsedCertificates = [];
|
||||
const parsedCertificates = [];
|
||||
for (const certificate of certificates) {
|
||||
parsedCertificates.push({
|
||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||
@@ -365,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
dockerComposeConfiguration,
|
||||
destinationDocker,
|
||||
destinationDockerId,
|
||||
settings
|
||||
settings,
|
||||
basicAuthUser,
|
||||
basicAuthPw,
|
||||
settings: { basicAuth: isBasicAuthEnabled }
|
||||
} = application;
|
||||
if (!destinationDockerId) {
|
||||
continue;
|
||||
@@ -378,6 +408,14 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let httpBasicAuth = null;
|
||||
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
|
||||
httpBasicAuth = {
|
||||
basicAuth: {
|
||||
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
|
||||
}
|
||||
};
|
||||
}
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||
if (services.length > 0) {
|
||||
@@ -400,27 +438,33 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, containerId, port)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
@@ -433,22 +477,28 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
)
|
||||
isHttp2,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
if (previews) {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
@@ -462,29 +512,35 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}${
|
||||
coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${container}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
previewDomain,
|
||||
domain: previewDomain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2: false,
|
||||
httpBasicAuth
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port, isHttp2)
|
||||
};
|
||||
if (httpBasicAuth) {
|
||||
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
|
||||
...httpBasicAuth
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -533,11 +589,15 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||
for (const oneService of Object.keys(found.services)) {
|
||||
const isDomainConfiguration =
|
||||
found?.services[oneService]?.proxy?.filter((p) => p.domain) ?? [];
|
||||
if (isDomainConfiguration.length > 0) {
|
||||
const { proxy } = found.services[oneService];
|
||||
for (let configuration of proxy) {
|
||||
const isDomainAndProxyConfiguration =
|
||||
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||
if (isDomainAndProxyConfiguration.length > 0) {
|
||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||
for (const configuration of proxy) {
|
||||
if (configuration.hostPort) {
|
||||
continue;
|
||||
}
|
||||
if (configuration.domain) {
|
||||
const setting = serviceSetting.find(
|
||||
(a) => a.variableName === configuration.domain
|
||||
@@ -574,16 +634,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL,
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
@@ -611,16 +671,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
@@ -652,16 +712,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
...await generateRouters({
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isDualCerts: dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
})
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
|
||||
@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||
import { OtherProxyConfiguration } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
||||
fastify.get<OnlyId>('/main.json', async (request) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request) =>
|
||||
otherProxyConfiguration(request)
|
||||
);
|
||||
};
|
||||
|
||||
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