Compare commits

..

148 Commits

Author SHA1 Message Date
Andras Bacsai
6311627899 Merge pull request #882 from coollabsio/next
v3.12.18
2023-01-24 15:09:51 +01:00
Andras Bacsai
37cea5fb61 update mattermost + traefik 2023-01-24 14:48:03 +01:00
Andras Bacsai
655a8cd60d feat: able to use $$ in traefik config gen
fix: repman icon
2023-01-24 13:52:39 +01:00
Andras Bacsai
4c8babc96a version++ 2023-01-23 10:45:19 +01:00
Andras Bacsai
612bacebed fix: cleanupStuckedContainers 2023-01-23 10:37:28 +01:00
Andras Bacsai
ade7c8566d fix: cleanupStuckedContainers 2023-01-23 10:37:14 +01:00
Andras Bacsai
19553ce5c8 Merge pull request #874 from coollabsio/next
v3.12.17
2023-01-20 21:14:06 +01:00
Andras Bacsai
18ed2527e8 fix 2023-01-20 20:51:37 +01:00
Andras Bacsai
b0652bc884 Merge pull request #872 from coollabsio/next
Next
2023-01-20 14:07:46 +01:00
Andras Bacsai
15c9ad23fe fix: stucked containers 2023-01-20 14:06:55 +01:00
Andras Bacsai
578bb12562 test new release gh action 2023-01-20 14:05:07 +01:00
Andras Bacsai
f82cfda07f version++ 2023-01-20 13:55:05 +01:00
Andras Bacsai
9e52b2788d Pocketbase GH release updated 2023-01-20 13:49:39 +01:00
Andras Bacsai
2e56a113d9 Test GH release thing 2023-01-20 13:48:57 +01:00
Andras Bacsai
4722d777e6 Merge pull request #871 from coollabsio/next
v3.12.15
2023-01-20 13:22:11 +01:00
Andras Bacsai
2141d54ae0 fix 2023-01-20 13:15:52 +01:00
Andras Bacsai
e346225136 fix 2023-01-20 13:10:40 +01:00
Andras Bacsai
012d4dae56 testing 2023-01-20 11:15:38 +01:00
Andras Bacsai
b4d9fe70af fix 2023-01-20 10:43:21 +01:00
Andras Bacsai
85e83b5441 Test new gh actions 2023-01-20 10:42:13 +01:00
Andras Bacsai
6b2a453b8f fix: deletion + cleanupStuckedContainers 2023-01-20 10:10:36 +01:00
Andras Bacsai
27021538d8 fix: cleanup stucked containers 2023-01-20 09:40:29 +01:00
Andras Bacsai
8b57a2b055 fix: cleanup function 2023-01-20 09:26:48 +01:00
Andras Bacsai
75dd894685 Merge pull request #867 from coollabsio/next
v3.12.14
2023-01-19 14:36:05 +01:00
Andras Bacsai
9101ef8774 version++ 2023-01-19 14:33:33 +01:00
Andras Bacsai
5932540630 fix: www redirect 2023-01-19 14:33:20 +01:00
Andras Bacsai
ec376b2e47 Merge pull request #864 from coollabsio/next
v3.12.13
2023-01-18 19:00:03 +01:00
Andras Bacsai
a176562ad0 fix: secrets 2023-01-18 18:51:03 +01:00
Andras Bacsai
becf37b676 Merge pull request #858 from coollabsio/next
v3.12.12
2023-01-17 12:33:51 +01:00
Andras Bacsai
9b5efab8f8 fix: grpc 2023-01-17 11:51:53 +01:00
Andras Bacsai
e98a8ba599 traefik dashbord in dev 2023-01-17 11:12:52 +01:00
Andras Bacsai
7ddac50008 feat: http + h2c paralel 2023-01-17 11:12:42 +01:00
Andras Bacsai
9837ae359f feat: init h2c (http2/grpc) support 2023-01-17 10:35:04 +01:00
Andras Bacsai
710a829dcb version++ 2023-01-17 10:00:50 +01:00
Andras Bacsai
ccd84fa454 fix: build args docker compose 2023-01-17 10:00:27 +01:00
Andras Bacsai
335b36d3a9 Merge pull request #857 from zek/patch-2
Fix docker-compose build args
2023-01-17 09:26:12 +01:00
Talha Zekeriya Durmuş
2be30fae00 Handle string build parameter 2023-01-17 02:11:06 +01:00
Talha Zekeriya Durmuş
db5cd21884 Fix docker-compose build args 2023-01-17 02:04:01 +01:00
Andras Bacsai
bfd3020031 Merge pull request #853 from coollabsio/next
v3.12.11
2023-01-16 12:44:04 +01:00
Andras Bacsai
344c36997a fix: public gh repo reload compose 2023-01-16 12:36:59 +01:00
Andras Bacsai
dfd9272b70 version ++ 2023-01-16 12:17:48 +01:00
Andras Bacsai
359f4520f5 test template + tags during dev 2023-01-16 11:45:45 +01:00
Andras Bacsai
aecf014f4e Merge pull request #843 from zek/soketi-logo
Add Soketi Logo
2023-01-16 11:10:48 +01:00
Andras Bacsai
d2a89ddf84 Merge pull request #844 from zek/repman-logo
Add Repman logo
2023-01-16 11:10:35 +01:00
Andras Bacsai
c01fe153ae Merge pull request #847 from zek/mattermost
Add Matermost Logo
2023-01-16 11:10:14 +01:00
Andras Bacsai
4f4a838799 update templates+tags 2023-01-16 10:58:44 +01:00
Andras Bacsai
ac6f2567eb fix: build env variables with docker compose 2023-01-16 10:42:23 +01:00
Andras Bacsai
05a5816ac6 fix: do not cleanup compose applications as unconfigured 2023-01-16 10:22:14 +01:00
Andras Bacsai
9c8f6e9195 fix: delete apps with previews 2023-01-16 10:16:49 +01:00
Andras Bacsai
2fd001f6d2 fix: docker log sequence 2023-01-16 10:06:41 +01:00
Andras Bacsai
d641d32413 fix: compose file location 2023-01-16 09:48:15 +01:00
Andras Bacsai
18064ef6a2 fixes related to docker-compose 2023-01-16 09:44:08 +01:00
Andras Bacsai
5cb9216add wip: trpc 2023-01-13 15:50:20 +01:00
Andras Bacsai
91c36dc810 wip: trpc 2023-01-13 15:24:43 +01:00
Andras Bacsai
6efb02fa32 wip: trpc 2023-01-13 15:21:54 +01:00
Andras Bacsai
97313e4180 wip: trpc 2023-01-13 14:54:21 +01:00
Andras Bacsai
568ab24fd9 wip: trpc 2023-01-13 14:17:36 +01:00
Talha Zekeriya Durmuş
5a745efcd3 Add Matermost Logo 2023-01-13 02:38:33 +01:00
Andras Bacsai
c651570e62 wip: trpc 2023-01-12 16:50:17 +01:00
Andras Bacsai
8980598085 wip: trpc 2023-01-12 16:43:41 +01:00
Talha Zekeriya Durmuş
c07c742feb Add Repman logo 2023-01-12 01:45:13 +01:00
Talha Zekeriya Durmuş
1053abb9a9 Add Soketi Logo 2023-01-12 01:41:35 +01:00
Andras Bacsai
2c9e57cbb1 Merge pull request #841 from coollabsio/next
v3.12.10
2023-01-11 11:44:22 +01:00
Andras Bacsai
c6eaa2c8a6 update packages in api 2023-01-11 11:35:57 +01:00
Andras Bacsai
5ab5e913ee Merge pull request #840 from zek/patch-1
Fix: add missing variables
2023-01-11 11:33:17 +01:00
Talha Zekeriya Durmuş
cea53ca476 Fix: add missing variables 2023-01-11 11:12:44 +01:00
Andras Bacsai
58af09114b Merge pull request #834 from coollabsio/next
v3.12.9
2023-01-11 11:00:56 +01:00
Andras Bacsai
c4c0417e2d new pocketbase 2023-01-11 10:55:55 +01:00
Andras Bacsai
74f90e6947 Merge pull request #838 from zek/patch-1
Add Build Time Secrets for Laravel
2023-01-11 10:12:59 +01:00
Andras Bacsai
ad5c339780 fix 2023-01-11 10:11:32 +01:00
Andras Bacsai
305823db00 fix: secrets 2023-01-11 09:29:59 +01:00
Talha Zekeriya Durmuş
baf58b298f Add Build Time Secrets 2023-01-11 01:43:43 +01:00
Andras Bacsai
c37367d018 add directus 2023-01-10 15:30:10 +01:00
Andras Bacsai
1c98796e64 new templates + tags + dev mode updated 2023-01-10 13:24:04 +01:00
Andras Bacsai
e686d9a6ea add lock file 2023-01-10 13:01:37 +01:00
Andras Bacsai
a1936b9d59 update jsonwebtoken 2023-01-10 13:01:03 +01:00
Andras Bacsai
834f9c9337 template updates 2023-01-10 13:01:03 +01:00
Andras Bacsai
615f8cfd3b feat: handle invite_only plausible analytics 2023-01-10 13:01:03 +01:00
Andras Bacsai
8ed134105f remove console.log 2023-01-10 13:01:03 +01:00
Andras Bacsai
5d6169b270 Merge pull request #781 from kaname-png/libretranslate
feat(ui): add libretranslate service icon
2023-01-10 12:57:05 +01:00
Andras Bacsai
e83de8b938 fix: local images for reverting 2023-01-10 12:24:22 +01:00
Andras Bacsai
ee55e039b2 Merge pull request #798 from hyddeos/main
fix the console error on Documentation hover
2023-01-10 11:52:57 +01:00
Andras Bacsai
086dd89144 fix: temporary disable dns check with dns servers 2023-01-10 11:50:41 +01:00
Andras Bacsai
68e5d4dd2c fix: doc link 2023-01-10 11:35:10 +01:00
Andras Bacsai
55a35c6bec fix: remove prefetches 2023-01-10 11:31:44 +01:00
Andras Bacsai
d09b4885fe Merge pull request #784 from hyddeos/tool-tip
Change color for Tooltip on hover
2023-01-10 11:29:54 +01:00
Andras Bacsai
a46773e6d8 Merge branch 'next' into tool-tip 2023-01-10 11:29:36 +01:00
Andras Bacsai
a422d0220c fix: add documentation link again 2023-01-10 11:27:43 +01:00
Andras Bacsai
e5eba8430a Merge pull request #783 from hyddeos/doc-in-mob-menu
Add link to the Documentation in the mobile menu
2023-01-10 11:26:42 +01:00
Andras Bacsai
3d235dc316 Merge pull request #794 from TetrisIQ/main
feat: adding icon for whoogle
2023-01-10 11:19:42 +01:00
Andras Bacsai
80d3b4be8c Merge pull request #825 from MrSquaare/feature/openblocks-service
feat: add Openblocks icon
2023-01-10 11:19:13 +01:00
Andras Bacsai
fe8b7480df Merge pull request #836 from coollabsio/feat/git-source-custom-user
fix: custom gitlab git user
2023-01-10 11:17:42 +01:00
Andras Bacsai
cebfc3aaa0 Merge pull request #804 from titouanmathis/feat/git-source-custom-user
feat(git-source): Add support for custom SSH user for GitLab self-hosted
2023-01-10 11:17:01 +01:00
Andras Bacsai
f778b5a12d fix: custom gitlab git user 2023-01-10 11:15:21 +01:00
Andras Bacsai
2244050160 Merge pull request #816 from Yarmeli/main
[Bug] Fixed issue with docker-compose not loading for Gitlab instances
2023-01-10 10:56:03 +01:00
Andras Bacsai
9284e42b62 fix: $ sign in secrets 2023-01-10 10:52:40 +01:00
Andras Bacsai
ee40120496 fix: read-only iam 2023-01-10 10:26:11 +01:00
Andras Bacsai
30cd2149ea fix: read-only permission 2023-01-10 10:15:03 +01:00
Andras Bacsai
395df36d57 chore: version++ 2023-01-10 09:57:27 +01:00
Andras Bacsai
79597ea0e5 fix: parsing secrets 2023-01-10 09:57:01 +01:00
Guillaume Bonnet
283f39270a feat: add Openblocks icon 2023-01-05 12:26:50 +00:00
Andras Bacsai
7d892bb19d esbuild 2022-12-29 22:33:31 +01:00
Yarmeli
a025f124f3 Updated index.svelte with the same changes from +page.svelte 2022-12-29 19:00:11 +00:00
Yarmeli
84f7287bf8 Fixed issue unable to find the docker compose file 2022-12-29 18:54:54 +00:00
Andras Bacsai
a58544b502 Merge pull request #813 from coollabsio/next
v3.12.8
2022-12-27 21:10:41 +01:00
Andras Bacsai
4d26175ebe omg, what have I done.. 2022-12-27 21:02:04 +01:00
Andras Bacsai
78f0e6ff6b Update package.json 2022-12-27 14:22:49 +01:00
Andras Bacsai
3af97af634 Update common.ts 2022-12-27 14:22:29 +01:00
Andras Bacsai
2c2663c8a4 Update common.ts 2022-12-27 14:20:19 +01:00
Andras Bacsai
1122b8a2f7 Update index.ts 2022-12-27 13:48:19 +01:00
Andras Bacsai
5b9f38948b Update index.ts 2022-12-27 13:46:33 +01:00
Andras Bacsai
507eb3b424 Update common.ts 2022-12-27 13:40:00 +01:00
Andras Bacsai
56fbc0ed6c Update package.json 2022-12-27 13:39:38 +01:00
Andras Bacsai
7aaad314e3 Update common.ts 2022-12-27 13:39:03 +01:00
Andras Bacsai
356949dd54 Merge pull request #811 from coollabsio/next
v3.12.5
2022-12-26 21:51:09 +01:00
Andras Bacsai
9878baca53 Update index.ts 2022-12-26 21:29:54 +01:00
Andras Bacsai
9cbc7c2939 Merge pull request #809 from Tiagofv/main
Fix bug: value.environment is not iterable
2022-12-26 21:19:11 +01:00
Andras Bacsai
4680b63911 fix: cleanupstorage 2022-12-26 21:17:53 +01:00
Tiago Braga
ce4a2d95f2 fix: remove unused imports 2022-12-24 16:28:02 -03:00
Tiago Braga
b2e048de8d Fix: conditional on environment 2022-12-24 16:27:10 -03:00
Andras Bacsai
d25a9d7515 devtemplates update 2022-12-22 11:31:46 +01:00
Andras Bacsai
dc130d3705 new pocketbase version 2022-12-22 11:22:37 +01:00
Titouan Mathis
2391850218 Add support for custom SSH user for GitLab self-hosted 2022-12-21 15:10:51 +01:00
Andras Bacsai
c8f7ca920e wip: trpc 2022-12-21 15:06:33 +01:00
Andras Bacsai
e3e39af6fb remove console.log 2022-12-21 14:11:07 +01:00
Andras Bacsai
f38114f5a5 Merge pull request #802 from coollabsio/next
v3.12.4
2022-12-21 13:25:46 +01:00
Andras Bacsai
1ee9d041df fix: duplicate env variables 2022-12-21 13:24:30 +01:00
Andras Bacsai
9c6f412f04 wip: trpc 2022-12-21 13:06:44 +01:00
Andras Bacsai
4fa0f2d04a fix: gh actions 2022-12-21 12:47:42 +01:00
Andras Bacsai
e566a66ea4 test 2022-12-21 12:33:27 +01:00
Andras Bacsai
58a42abc67 test 2022-12-21 11:40:47 +01:00
Andras Bacsai
5676bd9d0d test 2022-12-21 11:24:19 +01:00
Andras Bacsai
9691010e7b test 2022-12-21 11:11:55 +01:00
Andras Bacsai
d19be3ad52 Merge pull request #801 from coollabsio/next
v3.12.3
2022-12-21 10:53:09 +01:00
Andras Bacsai
ec3cbf788b fix: secrets 2022-12-21 10:40:27 +01:00
Andras Bacsai
1282fd0b76 fix: secrets 2022-12-21 10:11:03 +01:00
Andras Bacsai
93430e5607 fix: add default node_env variable 2022-12-19 23:07:01 +01:00
Andras Bacsai
14201f4052 fix: add default node_env variable 2022-12-19 22:15:00 +01:00
Andras Bacsai
47979bf16d fix: secrets 2022-12-19 22:11:21 +01:00
Andras Bacsai
29530f3b17 fix: secrets with newline 2022-12-19 21:48:31 +01:00
Eric
af548e6ef8 Change of link "rel" to "external"
To prevent hover console-error like on the documentations-icon in the desktop mode
2022-12-19 20:33:22 +01:00
hyddeos
ed24a9c990 fix the consol error on documentation hover 2022-12-19 20:02:24 +01:00
Alex
0d51b04d79 feat: adding icon for whoogle 2022-12-18 14:13:49 +01:00
hyddeos
379b1de64f change color for Tooltip on hover 2022-12-12 18:24:13 +01:00
hyddeos
f3ff324925 fixed size on icon 2022-12-12 17:50:35 +01:00
hyddeos
0f2160222f Add Documents link to Mobile-Menu 2022-12-12 17:45:04 +01:00
hyddeos
ce3750c51c Add link document to Mobile-menu 2022-12-12 17:38:45 +01:00
Kaname
72a7ea6e91 feat(ui): add libretranslate service icon 2022-12-12 15:48:23 +00:00
504 changed files with 39792 additions and 4962 deletions

View File

@@ -3,11 +3,11 @@ name: pocketbase-release
on:
push:
paths:
- "others/pocketbase"
- "others/pocketbase/*"
- ".github/workflows/pocketbase-release.yml"
branches:
- next
- main
jobs:
arm64:
runs-on: [self-hosted, arm64]
@@ -29,7 +29,7 @@ jobs:
context: others/pocketbase/
platforms: linux/arm64
push: true
tags: coollabsio/pocketbase:0.8.0-arm64
tags: coollabsio/pocketbase:0.11.0-arm64
amd64:
runs-on: ubuntu-latest
steps:
@@ -50,7 +50,7 @@ jobs:
context: others/pocketbase/
platforms: linux/amd64
push: true
tags: coollabsio/pocketbase:0.8.0-amd64
tags: coollabsio/pocketbase:0.11.0-amd64
aarch64:
runs-on: [self-hosted, arm64]
steps:
@@ -71,7 +71,7 @@ jobs:
context: others/pocketbase/
platforms: linux/aarch64
push: true
tags: coollabsio/pocketbase:0.8.0-aarch64
tags: coollabsio/pocketbase:0.11.0-aarch64
merge-manifest:
runs-on: ubuntu-latest
needs: [amd64, arm64, aarch64]
@@ -89,5 +89,5 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker manifest create coollabsio/pocketbase:0.8.0 --amend coollabsio/pocketbase:0.8.0-amd64 --amend coollabsio/pocketbase:0.8.0-arm64 --amend coollabsio/pocketbase:0.8.0-aarch64
docker manifest push coollabsio/pocketbase:0.8.0
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

View File

@@ -54,7 +54,7 @@ jobs:
context: .
platforms: linux/amd64
push: true
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
aarch64:
@@ -103,8 +103,10 @@ jobs:
id: package-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 push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
docker buildx imagetools create --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 --tag coollabsio/coolify:${{steps.package-version.outputs.current-version}}
docker tag coollabsio/coolify:${{steps.package-version.outputs.current-version}} coollabsio/coolify:latest
docker push coollabsio/coolify:latest
docker buildx imagetools create --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 --tag coollabsio/coolify:latest
- uses: sarisia/actions-status-discord@v1
if: always()
with:

View File

@@ -65,7 +65,7 @@ jobs:
context: .
platforms: linux/amd64
push: true
tags: coollabsio/coolify:next-amd64
tags: coollabsio/coolify:next
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
merge-manifest:
@@ -85,8 +85,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- 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 coollabsio/coolify:next-arm64 --tag coollabsio/coolify:next
- uses: sarisia/actions-status-discord@v1
if: always()
with:

6
.gitignore vendored
View File

@@ -1,7 +1,8 @@
.DS_Store
node_modules
.pnpm-store
build
/apps/ui/build
/build
.svelte-kit
package
.env
@@ -11,9 +12,10 @@ dist
apps/api/db/*.db
apps/api/db/migration.db-journal
apps/api/core*
apps/server/build
apps/backup/backups/*
!apps/backup/backups/.gitkeep
logs
/logs
others/certificates
backups/*
!backups/.gitkeep

4
apps/api/.gitignore vendored
View File

@@ -8,4 +8,6 @@ package
!.env.example
dist
dev.db
client
client
testTemplate.yaml
testTags.json

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,229 @@
- templateVersion: 1.0.0
defaultVersion: "0.8.0"
defaultVersion: "9.22"
documentation: https://docs.directus.io/getting-started/introduction.html
type: directus-postgresql
name: Directus
subname: (PostgreSQL)
description: >-
Directus is a free and open-source headless CMS framework for managing custom SQL-based databases.
labels:
- CMS
- headless
services:
$$id:
name: Directus
depends_on:
- $$id-postgresql
- $$id-redis
image: directus/directus:$$core_version
volumes:
- $$id-uploads:/directus/uploads
- $$id-database:/directus/database
- $$id-extensions:/directus/extensions
environment:
- KEY=$$secret_key
- SECRET=$$secret_secret
- DB_CLIENT=pg
- DB_CONNECTION_STRING=$$secret_db_connection_string
- CACHE_ENABLED=true
- CACHE_STORE=redis
- CACHE_REDIS=$$secret_cache_redis
- ADMIN_EMAIL=$$config_admin_email
- ADMIN_PASSWORD=$$secret_admin_password
- CACHE_AUTO_PURGE=true
- PUBLIC_URL=$$config_public_url
ports:
- "8055"
$$id-postgresql:
name: Directus PostgreSQL
depends_on: []
image: postgres:14-alpine
volumes:
- $$id-postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=$$config_postgres_user
- POSTGRES_PASSWORD=$$secret_postgres_password
- POSTGRES_DB=$$config_postgres_db
ports: []
$$id-redis:
name: Directus Redis
depends_on: []
image: redis:7.0.4-alpine
command: "--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5"
volumes:
- "$$id-redis:/data"
environment: []
variables:
- id: $$config_public_url
name: PUBLIC_URL
label: Public URL
defaultValue: $$generate_fqdn
description: ""
- id: $$secret_db_connection_string
name: DB_CONNECTION_STRING
label: Directus Database Url
defaultValue: postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db
description: ""
- id: $$config_postgres_db
main: $$id-postgresql
name: POSTGRES_DB
label: Database
defaultValue: directus
description: ""
- id: $$config_postgres_user
main: $$id-postgresql
name: POSTGRES_USER
label: User
defaultValue: $$generate_username
description: ""
- id: $$secret_postgres_password
main: $$id-postgresql
name: POSTGRES_PASSWORD
label: Password
defaultValue: $$generate_password
description: ""
showOnConfiguration: true
- id: $$secret_cache_redis
name: CACHE_REDIS
label: Redis Url
defaultValue: redis://$$id-redis:6379
description: ""
- id: $$config_admin_email
name: ADMIN_EMAIL
label: Initial Admin Email
defaultValue: "admin@example.com"
description: "The email address of the first user that is automatically created. You can change it later in Directus."
- id: $$secret_admin_password
name: ADMIN_PASSWORD
label: Initial Admin Password
defaultValue: $$generate_password
description: "The password of the first user that is automatically created."
showOnConfiguration: true
- id: $$secret_key
name: KEY
label: Key
defaultValue: $$generate_password
description: "Unique identifier for the project."
showOnConfiguration: true
- id: $$secret_secret
name: SECRET
label: Secret
defaultValue: $$generate_password
description: "Secret string for the project."
showOnConfiguration: true
- templateVersion: 1.0.0
defaultVersion: v1.3.8
documentation: https://github.com/LibreTranslate/LibreTranslate
description: Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup.
type: libretranslate
name: Libretranslate
labels:
- translator
- argos
- python
- libretranslate
services:
$$id:
name: Libretranslate
image: libretranslate/libretranslate:$$core_version
environment:
- LT_HOST=0.0.0.0
- LT_SUGGESTIONS=true
- LT_CHAR_LIMIT=$$config_lt_char_limit
- LT_REQ_LIMIT=$$config_lt_req_limit
- LT_BATCH_LIMIT=$$config_lt_batch_limit
- LT_GA_ID=$$config_lt_ga_id
- LT_DISABLE_WEB_UI=$$config_lt_web_ui
volumes:
- $$id-libretranslate:/libretranslate
ports:
- "5000"
variables:
- id: $$config_lt_char_limit
name: LT_CHAR_LIMIT
label: Char limit
defaultValue: "5000"
description: "Set character limit."
- id: $$config_lt_req_limit
name: LT_REQ_LIMIT
label: Request limit
defaultValue: "5000"
description: "Set maximum number of requests per minute per client."
- id: $$config_lt_batch_limit
name: LT_BATCH_LIMIT
label: Batch Limit
defaultValue: "5000"
description: "Set maximum number of texts to translate in a batch request."
- id: $$config_lt_ga_id
name: LT_GA_ID
label: Google Analytics ID
defaultValue: ""
description: "Enable Google Analytics on the API client page by providing an ID"
- id: $$config_lt_web_ui
name: LT_DISABLE_WEB_UI
label: Web UI
defaultValue: "false"
description: "Disable or enable web ui. True or false."
- templateVersion: 1.0.0
defaultVersion: 0.8.0
documentation: https://github.com/benbusby/whoogle-search
type: whoogle
name: Whoogle Search
description: A self-hosted, ad-free, privacy-respecting metasearch engine
labels:
- search
- google
services:
$$id:
name: Whoogle Search
documentation: https://github.com/benbusby/whoogle-search
depends_on: []
image: benbusby/whoogle-search:$$core_version
cap_drop:
- ALL
environment:
- WHOOGLE_USER=$$config_whoogle_username
- WHOOGLE_PASS=$$secret_whoogle_password
- WHOOGLE_CONFIG_PREFERENCES_KEY=$$secret_whoogle_preferences_key
ulimits:
nofile:
soft: 262144
hard: 262144
ports:
- "5000"
variables:
- id: $$config_whoogle_username
name: WHOOGLE_USER
label: Whoogle User
defaultValue: $$generate_username
description: "Username to log into Whoogle"
- id: $$secret_whoogle_password
name: WHOOGLE_PASSWORD
label: Whoogle Password
defaultValue: $$generate_password
description: "Password to log into Whoogle"
showOnConfiguration: true
- id: $$secret_whoogle_preferences_key
name: WHOOGLE_CONFIG_PREFERENCES_KEY
label: Whoogle preferences key
defaultValue: $$generate_password
description: "password to encrypt preferences"
- templateVersion: 1.0.0
defaultVersion: 1.1.3
documentation: https://docs.openblocks.dev/
type: openblocks
name: Openblocks
description: The Open Source Retool Alternative
services:
$$id:
image: openblocksdev/openblocks-ce:$$core_version
volumes:
- $$id-stacks-data:/openblocks-stacks
ports:
- "3000"
- templateVersion: 1.0.0
defaultVersion: "0.11.0"
documentation: https://pocketbase.io/docs/
type: pocketbase
name: Pocketbase
@@ -124,12 +348,12 @@
description: ""
- id: $$config_disable_auth
name: DISABLE_AUTH
label: Disable Authentication
label: Authentication
defaultValue: "false"
description: ""
- id: $$config_disable_registration
name: DISABLE_REGISTRATION
label: Disable Registration
label: Registration
defaultValue: "true"
description: ""
- id: $$config_postgres_user
@@ -157,7 +381,7 @@
defaultValue: plausible.js
description: This is the default script name.
- templateVersion: 1.0.0
defaultVersion: "1.17"
defaultVersion: "1.18"
documentation: https://docs.gitea.io
type: gitea
name: Gitea
@@ -332,12 +556,12 @@
volumes:
- $$id-lavalink:/lavalink
ports:
- "2333"
- $$config_port
files:
- location: /opt/Lavalink/application.yml
content: >-
server:
port: $$config_port
port: 2333
address: 0.0.0.0
lavalink:
server:
@@ -364,18 +588,13 @@
max-file-size: 1GB
max-history: 30
variables:
- id: $$config_port
name: PORT
label: Port
defaultValue: "2333"
required: true
- id: $$secret_password
name: PASSWORD
label: Password
defaultValue: $$generate_password
required: true
- templateVersion: 1.0.0
defaultVersion: v1.8.9
defaultVersion: v1.9.3
documentation: https://docs.appsmith.com/getting-started/setup/instance-configuration/
type: appsmith
name: Appsmith
@@ -408,7 +627,7 @@
defaultValue: "true"
description: ""
- templateVersion: 1.0.0
defaultVersion: 0.57.4
defaultVersion: 0.58.4
documentation: https://hub.docker.com/r/zadam/trilium
description: "A hierarchical note taking application with focus on building large personal knowledge bases."
labels:
@@ -428,7 +647,7 @@
- "8080"
variables: []
- templateVersion: 1.0.0
defaultVersion: 1.18.5
defaultVersion: 1.19.4
documentation: https://hub.docker.com/r/louislam/uptime-kuma
description: A free & fancy self-hosted monitoring tool.
labels:
@@ -445,7 +664,7 @@
- "3001"
variables: []
- templateVersion: 1.0.0
defaultVersion: "5.8"
defaultVersion: "6.0"
documentation: https://hub.docker.com/r/silviof/docker-languagetool
description: "A multilingual grammar, style and spell checker."
type: languagetool
@@ -460,7 +679,7 @@
- "8010"
variables: []
- templateVersion: 1.0.0
defaultVersion: 1.26.0
defaultVersion: 1.27.0
documentation: https://hub.docker.com/r/vaultwarden/server
description: "Bitwarden compatible server written in Rust."
type: vaultwarden
@@ -478,7 +697,7 @@
- "80"
variables: []
- templateVersion: 1.0.0
defaultVersion: 9.3.1
defaultVersion: 9.3.2
documentation: https://hub.docker.com/r/grafana/grafana
type: grafana
name: Grafana
@@ -499,7 +718,7 @@
- "3000"
variables: []
- templateVersion: 1.0.0
defaultVersion: 1.1.2
defaultVersion: 1.2.0
documentation: https://appwrite.io/docs
type: appwrite
name: Appwrite
@@ -1669,7 +1888,7 @@
defaultValue: weblate
description: ""
- templateVersion: 1.0.0
defaultVersion: 2022.12.12-966e9c3c
defaultVersion: 2023.01.15-52d41559
documentation: https://docs.searxng.org/
type: searxng
name: SearXNG
@@ -1742,7 +1961,7 @@
defaultValue: $$generate_password
description: ""
- templateVersion: 1.0.0
defaultVersion: v3.0.0
defaultVersion: v3.0.2
documentation: https://glitchtip.com/documentation
type: glitchtip
name: GlitchTip
@@ -1964,7 +2183,7 @@
defaultValue: glitchtip
description: ""
- templateVersion: 1.0.0
defaultVersion: v2.16.0
defaultVersion: v2.16.1
documentation: https://hasura.io/docs/latest/index/
type: hasura
name: Hasura
@@ -2444,7 +2663,7 @@
description: ""
showOnConfiguration: true
- templateVersion: 1.0.0
defaultVersion: v0.30.1
defaultVersion: v0.30.5
documentation: https://docs.meilisearch.com/learn/getting_started/quick_start.html
type: meilisearch
name: MeiliSearch
@@ -2474,7 +2693,7 @@
showOnConfiguration: true
- templateVersion: 1.0.0
ignore: true
defaultVersion: latest
defaultVersion: 5.30.0
documentation: https://docs.ghost.org
arch: amd64
type: ghost-mariadb
@@ -2592,7 +2811,7 @@
defaultValue: $$generate_password
description: ""
- templateVersion: 1.0.0
defaultVersion: "5.25.3"
defaultVersion: 5.30.0
documentation: https://docs.ghost.org
type: ghost-only
name: Ghost
@@ -2656,7 +2875,7 @@
placeholder: "ghost_db"
required: true
- templateVersion: 1.0.0
defaultVersion: "5.25.3"
defaultVersion: 5.30.0
documentation: https://docs.ghost.org
type: ghost-mysql
name: Ghost
@@ -2733,7 +2952,7 @@
defaultValue: $$generate_password
description: ""
- templateVersion: 1.0.0
defaultVersion: php8.1
defaultVersion: php8.2
documentation: https://wordpress.org/
type: wordpress
name: WordPress
@@ -2823,7 +3042,7 @@
description: ""
readOnly: true
- templateVersion: 1.0.0
defaultVersion: php8.1
defaultVersion: php8.2
documentation: https://wordpress.org/
type: wordpress-only
name: WordPress
@@ -2897,7 +3116,7 @@
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);
- templateVersion: 1.0.0
defaultVersion: 4.9.0
defaultVersion: 4.9.1
documentation: https://coder.com/docs/coder-oss/latest
type: vscodeserver
name: VSCode Server
@@ -2912,7 +3131,6 @@
depends_on: []
image: "codercom/code-server:$$core_version"
volumes:
- "$$id-config-data:/home/coder/.local/share/code-server"
- "$$id-vscodeserver-data:/home/coder"
- "$$id-keys-directory:/root/.ssh"
- "$$id-theme-and-plugin-directory:/root/.local/share/code-server"
@@ -2928,7 +3146,7 @@
description: ""
showOnConfiguration: true
- templateVersion: 1.0.0
defaultVersion: RELEASE.2022-12-12T19-27-27Z
defaultVersion: RELEASE.2023-01-12T02-06-16Z
documentation: https://min.io/docs/minio
type: minio
name: MinIO
@@ -2987,7 +3205,7 @@
description: ""
showOnConfiguration: true
- templateVersion: 1.0.0
defaultVersion: 0.21.1
defaultVersion: stable
documentation: https://fider.io/docs
type: fider
name: Fider
@@ -3106,7 +3324,7 @@
defaultValue: $$generate_username
description: ""
- templateVersion: 1.0.0
defaultVersion: 0.207.0
defaultVersion: 0.210.1
documentation: https://docs.n8n.io
type: n8n
name: n8n.io
@@ -3137,7 +3355,7 @@
defaultValue: $$generate_fqdn
description: ""
- templateVersion: 1.0.0
defaultVersion: stable
defaultVersion: v1.5.1
documentation: https://plausible.io/doc/
arch: amd64
type: plausibleanalytics
@@ -3250,12 +3468,12 @@
description: ""
- id: $$config_disable_auth
name: DISABLE_AUTH
label: Disable Authentication
label: Authentication
defaultValue: "false"
description: ""
- id: $$config_disable_registration
name: DISABLE_REGISTRATION
label: Disable Registration
label: Registration
defaultValue: "true"
description: ""
- id: $$config_postgresql_username
@@ -3283,7 +3501,7 @@
defaultValue: plausible.js
description: This is the default script name.
- templateVersion: 1.0.0
defaultVersion: 0.99.1
defaultVersion: 0.101.2
documentation: https://docs.nocodb.com
type: nocodb
name: NocoDB

View File

@@ -16,31 +16,31 @@
},
"dependencies": {
"@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.5.0",
"@fastify/autoload": "5.7.0",
"@fastify/cookie": "8.3.0",
"@fastify/cors": "8.2.0",
"@fastify/env": "4.1.0",
"@fastify/jwt": "6.3.3",
"@fastify/env": "4.2.0",
"@fastify/jwt": "6.5.0",
"@fastify/multipart": "7.3.0",
"@fastify/static": "6.5.1",
"@fastify/static": "6.6.0",
"@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.0.2",
"@prisma/client": "4.6.1",
"@sentry/node": "7.21.1",
"@sentry/tracing": "7.21.1",
"axe": "11.0.0",
"@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.2",
"cabin": "11.0.1",
"bree": "9.1.3",
"cabin": "11.1.1",
"compare-versions": "5.0.1",
"csv-parse": "5.3.2",
"csv-parse": "5.3.3",
"csvtojson": "2.0.10",
"cuid": "2.1.8",
"dayjs": "1.11.6",
"dayjs": "1.11.7",
"dockerode": "3.3.4",
"dotenv-extended": "2.9.0",
"execa": "6.1.0",
"fastify": "4.10.2",
"fastify": "4.11.0",
"fastify-plugin": "4.3.0",
"fastify-socket.io": "4.0.0",
"generate-password": "1.7.0",
@@ -48,36 +48,36 @@
"is-ip": "5.0.0",
"is-port-reachable": "4.0.0",
"js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1",
"jsonwebtoken": "9.0.0",
"minimist": "^1.2.7",
"node-forge": "1.3.1",
"node-os-utils": "1.3.7",
"p-all": "4.0.0",
"p-throttle": "5.0.0",
"prisma": "4.6.1",
"prisma": "4.8.1",
"public-ip": "6.0.1",
"pump": "3.0.0",
"shell-quote": "^1.7.4",
"socket.io": "4.5.3",
"ssh-config": "4.1.6",
"socket.io": "4.5.4",
"ssh-config": "4.2.0",
"strip-ansi": "7.0.1",
"unique-names-generator": "4.7.1"
},
"devDependencies": {
"@types/node": "18.11.9",
"@types/node": "18.11.18",
"@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.44.0",
"@typescript-eslint/parser": "5.44.0",
"esbuild": "0.15.15",
"eslint": "8.28.0",
"eslint-config-prettier": "8.5.0",
"@typescript-eslint/eslint-plugin": "5.48.1",
"@typescript-eslint/parser": "5.48.1",
"esbuild": "0.16.16",
"eslint": "8.31.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-prettier": "4.2.1",
"nodemon": "2.0.20",
"prettier": "2.7.1",
"prettier": "2.8.2",
"rimraf": "3.0.2",
"tsconfig-paths": "4.1.0",
"tsconfig-paths": "4.1.2",
"types-fastify-socket.io": "0.0.1",
"typescript": "4.9.3"
"typescript": "4.9.4"
},
"prisma": {
"seed": "node prisma/seed.js"

View File

@@ -0,0 +1,27 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_GitSource" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"forPublic" BOOLEAN NOT NULL DEFAULT false,
"type" TEXT,
"apiUrl" TEXT,
"htmlUrl" TEXT,
"customPort" INTEGER NOT NULL DEFAULT 22,
"customUser" TEXT NOT NULL DEFAULT 'git',
"organization" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"githubAppId" TEXT,
"gitlabAppId" TEXT,
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt" FROM "GitSource";
DROP TABLE "GitSource";
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,24 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" (
"id" TEXT NOT NULL PRIMARY KEY,
"applicationId" TEXT NOT NULL,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"debug" BOOLEAN NOT NULL DEFAULT false,
"previews" BOOLEAN NOT NULL DEFAULT false,
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
"isBot" BOOLEAN NOT NULL DEFAULT false,
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -186,6 +186,7 @@ model ApplicationSettings {
isPublicRepository Boolean @default(false)
isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false)
isHttp2 Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
@@ -325,6 +326,7 @@ model GitSource {
apiUrl String?
htmlUrl String?
customPort Int @default(22)
customUser String @default("git")
organization String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

View File

@@ -6,14 +6,27 @@ import cookie from '@fastify/cookie';
import multipart from '@fastify/multipart';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import socketIO from 'fastify-socket.io'
import socketIOServer from './realtime'
import socketIO from 'fastify-socket.io';
import socketIOServer from './realtime';
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
import {
cleanupDockerStorage,
createRemoteEngineConfiguration,
decrypt,
executeCommand,
generateDatabaseConfiguration,
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 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';
@@ -23,13 +36,13 @@ import * as Sentry from '@sentry/node';
declare module 'fastify' {
interface FastifyInstance {
config: {
COOLIFY_APP_ID: string,
COOLIFY_SECRET_KEY: string,
COOLIFY_DATABASE_URL: string,
COOLIFY_IS_ON: string,
COOLIFY_WHITE_LABELED: string,
COOLIFY_WHITE_LABELED_ICON: string | null,
COOLIFY_AUTO_UPDATE: string,
COOLIFY_APP_ID: string;
COOLIFY_SECRET_KEY: string;
COOLIFY_DATABASE_URL: string;
COOLIFY_IS_ON: string;
COOLIFY_WHITE_LABELED: string;
COOLIFY_WHITE_LABELED_ICON: string | null;
COOLIFY_AUTO_UPDATE: string;
};
}
}
@@ -38,7 +51,7 @@ const port = isDev ? 3001 : 3000;
const host = '0.0.0.0';
(async () => {
const settings = await prisma.setting.findFirst()
const settings = await prisma.setting.findFirst();
const fastify = Fastify({
logger: settings?.isAPIDebuggingEnabled || false,
trustProxy: true
@@ -49,10 +62,10 @@ const host = '0.0.0.0';
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
properties: {
COOLIFY_APP_ID: {
type: 'string',
type: 'string'
},
COOLIFY_SECRET_KEY: {
type: 'string',
type: 'string'
},
COOLIFY_DATABASE_URL: {
type: 'string',
@@ -73,8 +86,7 @@ const host = '0.0.0.0';
COOLIFY_AUTO_UPDATE: {
type: 'string',
default: 'false'
},
}
}
};
const options = {
@@ -103,13 +115,13 @@ const host = '0.0.0.0';
fastify.register(autoLoad, {
dir: join(__dirname, 'routes')
});
fastify.register(cookie)
fastify.register(cookie);
fastify.register(cors);
fastify.register(socketIO, {
cors: {
origin: isDev ? "*" : ''
origin: isDev ? '*' : ''
}
})
});
// To detect allowed origins
// fastify.addHook('onRequest', async (request, reply) => {
// console.log(request.headers.host)
@@ -131,10 +143,9 @@ const host = '0.0.0.0';
// }
// })
try {
await fastify.listen({ port, host })
await socketIOServer(fastify)
await fastify.listen({ port, host });
await socketIOServer(fastify);
console.log(`Coolify's API is listening on ${host}:${port}`);
migrateServicesToNewTemplate();
@@ -148,105 +159,125 @@ const host = '0.0.0.0';
if (!scheduler.workers.has('deployApplication')) {
scheduler.run('deployApplication');
}
}, 2000)
}, 2000);
// autoUpdater
setInterval(async () => {
await autoUpdater()
}, 60000 * 15)
await autoUpdater();
}, 60000 * 15);
// cleanupStorage
setInterval(async () => {
await cleanupStorage()
}, 60000 * 10)
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();
await checkFluentBit();
}, 60000)
}, 60000);
// Refresh and check templates
setInterval(async () => {
await refreshTemplates()
}, 60000)
await refreshTemplates();
}, 60000);
setInterval(async () => {
await refreshTags()
}, 60000)
await refreshTags();
}, 60000);
setInterval(async () => {
await migrateServicesToNewTemplate()
}, isDev ? 10000 : 60000)
setInterval(
async () => {
await migrateServicesToNewTemplate();
},
isDev ? 10000 : 60000
);
setInterval(async () => {
await copySSLCertificates();
}, 10000)
}, 10000);
await Promise.all([
getTagsTemplates(),
getArch(),
getIPAddress(),
configureRemoteDockers(),
])
configureRemoteDockers()
// cleanupStuckedContainers()
]);
} catch (error) {
console.error(error);
process.exit(1);
}
})();
async function getIPAddress() {
const { publicIpv4, publicIpv6 } = await import('public-ip')
const { publicIpv4, publicIpv6 } = await import('public-ip');
try {
const settings = await listSettings();
if (!settings.ipv4) {
const ipv4 = await publicIpv4({ timeout: 2000 })
const ipv4 = await publicIpv4({ timeout: 2000 });
console.log(`Getting public IPv4 address...`);
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } });
}
if (!settings.ipv6) {
const ipv6 = await publicIpv6({ timeout: 2000 })
const ipv6 = await publicIpv6({ timeout: 2000 });
console.log(`Getting public IPv6 address...`);
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
}
} catch (error) { }
} catch (error) {}
}
async function getTagsTemplates() {
const { default: got } = await import('got')
const { default: got } = await import('got');
try {
if (isDev) {
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
const tags = await fs.readFile('./devTags.json', 'utf8')
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
await fs.writeFile('./tags.json', tags)
console.log('[004] Tags and templates loaded in dev mode...')
} else {
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
await fs.writeFile('/app/tags.json', tags)
console.log('[004] Tags and templates loaded...')
}
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
let tags = await fs.readFile('./devTags.json', 'utf8');
try {
if (await fs.stat('./testTemplate.yaml')) {
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
}
} catch (error) {}
try {
if (await fs.stat('./testTags.json')) {
const testTags = await fs.readFile('./testTags.json', 'utf8');
if (testTags.length > 0) {
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
}
}
} catch (error) {}
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
await fs.writeFile('./tags.json', tags);
console.log('[004] Tags and templates loaded in dev mode...');
} else {
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
const response = await got
.get('https://get.coollabs.io/coolify/service-templates.yaml')
.text();
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
await fs.writeFile('/app/tags.json', tags);
console.log('[004] Tags and templates loaded...');
}
} catch (error) {
console.log("Couldn't get latest templates.")
console.log(error)
console.log("Couldn't get latest templates.");
console.log(error);
}
}
async function initServer() {
const appId = process.env['COOLIFY_APP_ID'];
const settings = await prisma.setting.findUnique({ where: { id: '0' } })
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
try {
if (settings.doNotTrack === true) {
console.log('[000] Telemetry disabled...')
console.log('[000] Telemetry disabled...');
} else {
if (settings.sentryDSN !== sentryDSN) {
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } })
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
}
// Initialize Sentry
// Sentry.init({
@@ -257,37 +288,83 @@ async function initServer() {
// console.log('[000] Sentry initialized...')
}
} catch (error) {
console.error(error)
console.error(error);
}
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);
if (isOlder === 1) {
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
await prisma.build.updateMany({
where: { status: { in: ['running', 'queued'] } },
data: { status: 'failed' }
});
}
} catch (error) { }
} catch (error) {}
try {
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
await fs.rm('/tmp/build-sources', { recursive: true, force: true })
await fs.rm('/tmp/build-sources', { recursive: true, force: true });
} catch (error) {
console.log(error)
console.log(error);
}
}
async function getArch() {
try {
const settings = await prisma.setting.findFirst({})
const settings = await prisma.setting.findFirst({});
if (settings && !settings.arch) {
console.log(`Getting architecture...`);
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } });
}
} catch (error) { }
} catch (error) {}
}
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({
@@ -296,37 +373,44 @@ async function configureRemoteDockers() {
if (remoteDocker.length > 0) {
console.log(`Verifying Remote Docker Engines...`);
for (const docker of remoteDocker) {
console.log('Verifying:', docker.remoteIpAddress)
console.log('Verifying:', docker.remoteIpAddress);
await verifyRemoteDockerEngineFn(docker.id);
}
}
} catch (error) {
console.log(error)
console.log(error);
}
}
async function autoUpdater() {
try {
const { default: got } = await import('got')
const { default: got } = await import('got');
const currentVersion = version;
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
searchParams: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
}).json()
const { coolify } = await got
.get('https://get.coollabs.io/versions.json', {
searchParams: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
})
.json();
const latestVersion = coolify.main.version;
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
if (isUpdateAvailable === 1) {
const activeCount = 0
const activeCount = 0;
if (activeCount === 0) {
if (!isDev) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
if (isAutoUpdateEnabled) {
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` })
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` })
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
await executeCommand({
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
});
await executeCommand({
shell: true,
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
});
}
} else {
console.log('Updating (not really in dev mode).');
@@ -334,7 +418,7 @@ async function autoUpdater() {
}
}
} catch (error) {
console.log(error)
console.log(error);
}
}
@@ -345,14 +429,18 @@ async function checkFluentBit() {
const { id } = await prisma.destinationDocker.findFirst({
where: { engine, network: 'coolify' }
});
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
const { found } = await checkContainer({
dockerId: id,
container: 'coolify-fluentbit',
remove: true
});
if (!found) {
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
await executeCommand({ command: `docker compose up -d fluent-bit` });
}
}
} catch (error) {
console.log(error)
console.log(error);
}
}
async function checkProxies() {
@@ -368,7 +456,7 @@ async function checkProxies() {
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
});
if (localDocker) {
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
portReachable = await isReachable(80, { host: ipv4 || ipv6 });
if (!portReachable) {
await startTraefikProxy(localDocker.id);
}
@@ -380,14 +468,14 @@ async function checkProxies() {
if (remoteDocker.length > 0) {
for (const docker of remoteDocker) {
if (docker.isCoolifyProxyUsed) {
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
portReachable = await isReachable(80, { host: docker.remoteIpAddress });
if (!portReachable) {
await startTraefikProxy(docker.id);
}
}
try {
await createRemoteEngineConfiguration(docker.id)
} catch (error) { }
await createRemoteEngineConfiguration(docker.id);
} catch (error) {}
}
}
// TCP Proxies
@@ -426,80 +514,109 @@ async function checkProxies() {
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
// }
// }
} catch (error) {
}
} catch (error) {}
}
async function copySSLCertificates() {
try {
const pAll = await import('p-all');
const actions = []
const certificates = await prisma.certificate.findMany({ include: { team: true } })
const teamIds = certificates.map(c => c.teamId)
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
const actions = [];
const certificates = await prisma.certificate.findMany({ include: { team: true } });
const teamIds = certificates.map((c) => c.teamId);
const destinations = await prisma.destinationDocker.findMany({
where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } }
});
for (const certificate of certificates) {
const { id, key, cert } = certificate
const decryptedKey = decrypt(key)
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
const { id, key, cert } = certificate;
const decryptedKey = decrypt(key);
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey);
await fs.writeFile(`/tmp/${id}-cert.pem`, cert);
for (const destination of destinations) {
if (destination.remoteEngine) {
if (destination.remoteVerified) {
const { id: dockerId, remoteIpAddress } = destination
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
const { id: dockerId, remoteIpAddress } = destination;
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress));
}
} else {
actions.push(async () => copyLocalCertificates(id))
actions.push(async () => copyLocalCertificates(id));
}
}
}
await pAll.default(actions, { concurrency: 1 })
await pAll.default(actions, { concurrency: 1 });
} catch (error) {
console.log(error)
console.log(error);
} finally {
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
}
}
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
try {
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
await executeCommand({ sshCommand: true, shell: true, dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({
command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`
});
await executeCommand({
sshCommand: true,
shell: true,
dockerId,
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`
});
await executeCommand({
sshCommand: true,
dockerId,
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
});
await executeCommand({
sshCommand: true,
dockerId,
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
});
} catch (error) {
console.log({ error })
console.log({ error });
}
}
async function copyLocalCertificates(id: string) {
try {
await executeCommand({ command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`, shell: true })
await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeCommand({
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`,
shell: true
});
await executeCommand({
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
});
await executeCommand({
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
});
} catch (error) {
console.log({ error })
console.log({ error });
}
}
async function cleanupStorage() {
const destinationDockers = await prisma.destinationDocker.findMany();
let enginesDone = new Set()
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)
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);
}
let lowDiskSpace = false;
try {
let stdout = null
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 })
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 /`
command: `df -kPT /`
});
stdout = output.stdout;
}
@@ -531,7 +648,9 @@ async function cleanupStorage() {
lowDiskSpace = true;
}
}
} catch (error) { }
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
} catch (error) {}
if (lowDiskSpace) {
await cleanupDockerStorage(destination.id);
}
}
}
}

View File

@@ -20,7 +20,8 @@ import {
decryptApplication,
isDev,
pushToRegistry,
executeCommand
executeCommand,
generateSecrets
} from '../lib/common';
import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks';
@@ -140,33 +141,13 @@ import * as buildpacks from '../lib/buildPacks';
} catch (error) {
//
}
const envs = [`PORT='${port}'`];
let envs = [];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(
(s) => s.name === secret.name && s.isPRMRSecret
);
if (isSecretFound.length > 0) {
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
} else {
envs.push(`${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
}
}
});
envs = [
...envs,
...generateSecrets(secrets, pullmergeRequestId, false, port)
];
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
if (dockerRegistry) {
const { url, username, password } = dockerRegistry;
@@ -197,7 +178,7 @@ import * as buildpacks from '../lib/buildPacks';
container_name: applicationId,
volumes,
labels,
env_file: envFound ? [`${workdir}/.env`] : [],
environment: envs,
depends_on: [],
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
@@ -215,7 +196,7 @@ import * as buildpacks from '../lib/buildPacks';
await executeCommand({
debug: true,
dockerId: destinationDocker.id,
command: `docker compose --project-directory ${workdir} up -d`
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
});
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
} catch (error) {
@@ -438,6 +419,7 @@ import * as buildpacks from '../lib/buildPacks';
githubAppId: gitSource.githubApp?.id,
gitlabAppId: gitSource.gitlabApp?.id,
customPort: gitSource.customPort,
customUser: gitSource.customUser,
gitCommitHash,
configuration,
repository,
@@ -619,6 +601,7 @@ import * as buildpacks from '../lib/buildPacks';
}
if (buildPack === 'compose') {
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
try {
const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId,
@@ -648,7 +631,7 @@ import * as buildpacks from '../lib/buildPacks';
buildId,
applicationId,
dockerId: destinationDocker.id,
command: `docker compose --project-directory ${workdir} up -d`
command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d`
});
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
await prisma.build.update({
@@ -698,37 +681,17 @@ import * as buildpacks from '../lib/buildPacks';
} catch (error) {
//
}
const envs = [`PORT='${port}'`];
let envs = [];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(
(s) => s.name === secret.name && s.isPRMRSecret
);
if (isSecretFound.length > 0) {
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
} else {
envs.push(`${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
}
}
});
envs = [
...envs,
...generateSecrets(secrets, pullmergeRequestId, false, port)
];
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
if (dockerRegistry) {
const { url, username, password } = dockerRegistry;
await saveDockerRegistryCredentials({ url, username, password, workdir });
}
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
try {
const composeVolumes = volumes.map((volume) => {
return {
@@ -744,7 +707,7 @@ import * as buildpacks from '../lib/buildPacks';
image: imageFound,
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
environment: envs,
labels,
depends_on: [],
expose: [port],
@@ -763,7 +726,7 @@ import * as buildpacks from '../lib/buildPacks';
await executeCommand({
debug,
dockerId: destinationDocker.id,
command: `docker compose --project-directory ${workdir} up -d`
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
});
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
} catch (error) {

View File

@@ -3,6 +3,7 @@ import {
decrypt,
encrypt,
executeCommand,
generateSecrets,
generateTimestamp,
getDomain,
isARM,
@@ -653,7 +654,7 @@ export async function saveDockerRegistryCredentials({ url, username, password, w
try {
await fs.mkdir(`${workdir}/.docker`);
} catch (error) {
console.log(error);
// console.log(error);
}
const payload = JSON.stringify({
auths: {
@@ -787,21 +788,8 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (isPnpm) {
@@ -811,7 +799,6 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
if (installCommand) {
Dockerfile.push(`RUN ${installCommand}`);
}
// Dockerfile.push(`ARG CACHEBUST=1`);
Dockerfile.push(`RUN ${buildCommand}`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true });
@@ -819,27 +806,13 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
export async function buildCacheImageForLaravel(data, imageForBuild) {
const { workdir, buildId, secrets, pullmergeRequestId } = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
Dockerfile.push(`COPY *.json *.mix.js /app/`);

View File

@@ -1,5 +1,5 @@
import { promises as fs } from 'fs';
import { defaultComposeConfiguration, executeCommand } from '../common';
import { defaultComposeConfiguration, executeCommand, generateSecrets } from '../common';
import { saveBuildLog } from './common';
import yaml from 'js-yaml';
@@ -25,30 +25,13 @@ export default async function (data) {
if (!dockerComposeYaml.services) {
throw 'No Services found in docker-compose file.';
}
const envs = [];
let envs = [];
let buildEnvs = [];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
if (isSecretFound.length > 0) {
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
} else {
envs.push(`${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
}
}
});
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
}
const composeVolumes = [];
if (volumes.length > 0) {
for (const volume of volumes) {
@@ -62,7 +45,34 @@ export default async function (data) {
let networks = {};
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
value['container_name'] = `${applicationId}-${key}`;
value['env_file'] = envFound ? [`${workdir}/.env`] : [];
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
if (Object.keys(environment).length > 0) {
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
}
value['environment'] = [...environment, ...envs];
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];
if (Object.keys(buildArgs).length > 0) {
for (const arg of buildArgs) {
const [key, _] = arg.split('=');
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
finalArgs.push(arg);
}
}
}
if (build.length > 0 || buildArgs.length > 0 ) {
value['build'] = {
...build,
args: finalArgs
};
}
value['labels'] = labels;
// TODO: If we support separated volume for each service, we need to add it here
if (value['volumes']?.length > 0) {
@@ -106,13 +116,14 @@ export default async function (data) {
dockerComposeYaml['volumes'] = { ...composeVolumes };
}
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
await executeCommand({
debug,
buildId,
applicationId,
dockerId,
command: `docker compose --project-directory ${workdir} pull`
command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull`
});
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
await executeCommand({
@@ -120,7 +131,7 @@ export default async function (data) {
buildId,
applicationId,
dockerId,
command: `docker compose --project-directory ${workdir} build --progress plain`
command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain`
});
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
}

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildImage } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (depsFound) {

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildImage } from './common';
export default async function (data) {
@@ -13,19 +14,12 @@ export default async function (data) {
}
});
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (
(pullmergeRequestId && secret.isPRMRSecret) ||
(!pullmergeRequestId && !secret.isPRMRSecret)
) {
Dockerfile.forEach((line, index) => {
if (line.startsWith('FROM')) {
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}='${secret.value}'`);
}
});
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.forEach((line, index) => {
if (line.startsWith('FROM')) {
Dockerfile.splice(index + 1, 0, env);
}
}
});
});
}
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));

View File

@@ -1,12 +1,18 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildCacheImageForLaravel, buildImage } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const { workdir, applicationId, tag, buildId, port } = data;
const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${image}`);
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
Dockerfile.push(`COPY --chown=application:application composer.* ./`);

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (isPnpm) {

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildImage, checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -20,21 +21,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (isPnpm) {

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (isPnpm) {

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildImage } from './common';
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
@@ -13,21 +14,8 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
Dockerfile.push(`FROM ${image}`);
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
Dockerfile.push('WORKDIR /app');

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildImage } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -18,21 +19,8 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (pythonWSGI?.toLowerCase() === 'gunicorn') {

View File

@@ -1,4 +1,5 @@
import { promises as fs } from 'fs';
import { generateSecrets } from '../common';
import { buildCacheImageWithNode, buildImage } from './common';
const createDockerfile = async (data, image): Promise<void> => {
@@ -25,21 +26,8 @@ const createDockerfile = async (data, image): Promise<void> => {
}
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
} else {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
}
}
}
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
Dockerfile.push(env);
});
}
if (buildCommand) {

View File

@@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common
import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa';
export const version = '3.12.2';
export const version = '3.12.18';
export const isDev = process.env.NODE_ENV === 'development';
export const sentryDSN =
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
@@ -714,8 +714,10 @@ export async function startTraefikProxy(id: string): Promise<void> {
--network coolify-infra \
-p "80:80" \
-p "443:443" \
${isDev ? '-p "8080:8080"' : ''} \
--name coolify-proxy \
-d ${defaultTraefikImage} \
${isDev ? '--api.insecure=true' : ''} \
--entrypoints.web.address=:80 \
--entrypoints.web.forwardedHeaders.insecure=true \
--entrypoints.websecure.address=:443 \
@@ -1712,70 +1714,24 @@ export function convertTolOldVolumeNames(type) {
}
}
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
// Cleanup old coolify images
export async function cleanupDockerStorage(dockerId) {
// Cleanup images that are not used by any container
try {
let { stdout: images } = await executeCommand({
dockerId,
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`,
shell: true
});
images = images.trim();
if (images) {
await executeCommand({
dockerId,
command: `docker rmi -f ${images}" -q | xargs -r`,
shell: true
});
}
await executeCommand({ dockerId, command: `docker image prune -af` });
} catch (error) {}
if (lowDiskSpace || force) {
// Cleanup images that are not used
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({
// Prune coolify managed containers
try {
await executeCommand({
dockerId,
command: `docker images|grep -v "<none>"|grep -v REPOSITORY|awk '{print $1, $2}'`,
shell: true
command: `docker container prune -f --filter "label=coolify.managed=true"`
});
const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
let deleteImage = [];
for (const image of imagesSet) {
let keepImage = [];
for (const image2 of imagesArray) {
if (image2.startsWith(image)) {
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
deleteImage.push(image2);
} else {
keepImage.push(image2);
}
}
}
}
for (const image of deleteImage) {
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
}
} catch (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) {}
}
// Cleanup build caches
try {
await executeCommand({ dockerId, command: `docker builder prune -af` });
} catch (error) {}
}
export function persistentVolumes(id, persistentStorage, config) {
@@ -1875,3 +1831,66 @@ export async function pushToRegistry(
command: pushCommand
});
}
function parseSecret(secret, isBuild) {
if (secret.value.includes('$')) {
secret.value = secret.value.replaceAll('$', '$$$$');
}
if (secret.value.includes('\\n')) {
if (isBuild) {
return `ARG ${secret.name}=${secret.value}`;
} else {
return `${secret.name}=${secret.value}`;
}
} else if (secret.value.includes(' ')) {
if (isBuild) {
return `ARG ${secret.name}='${secret.value}'`;
} else {
return `${secret.name}='${secret.value}'`;
}
} else {
if (isBuild) {
return `ARG ${secret.name}=${secret.value}`;
} else {
return `${secret.name}=${secret.value}`;
}
}
}
export function generateSecrets(
secrets: Array<any>,
pullmergeRequestId: string,
isBuild = false,
port = null,
compose = false
): Array<string> {
const envs = [];
const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret);
const normalSecrets = secrets.filter((s) => !s.isPRMRSecret);
if (pullmergeRequestId && isPRMRSecret.length > 0) {
isPRMRSecret.forEach((secret) => {
if (isBuild && !secret.isBuildSecret) {
return;
}
const build = isBuild && secret.isBuildSecret;
envs.push(parseSecret(secret, compose ? false : build));
});
}
if (!pullmergeRequestId && normalSecrets.length > 0) {
normalSecrets.forEach((secret) => {
if (isBuild && !secret.isBuildSecret) {
return;
}
const build = isBuild && secret.isBuildSecret;
envs.push(parseSecret(secret, compose ? false : build));
});
}
const portFound = envs.filter((env) => env.startsWith('PORT'));
if (portFound.length === 0 && port && !isBuild) {
envs.push(`PORT=${port}`);
}
const nodeEnv = envs.filter((env) => env.startsWith('NODE_ENV'));
if (nodeEnv.length === 0 && !isBuild) {
envs.push(`NODE_ENV=production`);
}
return envs;
}

View File

@@ -12,7 +12,8 @@ export default async function ({
buildId,
privateSshKey,
customPort,
forPublic
forPublic,
customUser,
}: {
applicationId: string;
workdir: string;
@@ -25,6 +26,7 @@ export default async function ({
privateSshKey: string;
customPort: number;
forPublic: boolean;
customUser: string;
}): Promise<string> {
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
if (!forPublic) {
@@ -53,7 +55,7 @@ export default async function ({
} else {
await executeCommand({
command:
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
`git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
}
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,154 +1,170 @@
import type { OnlyId } from "../../../../types";
import type { OnlyId } from '../../../../types';
export interface SaveApplication extends OnlyId {
Body: {
name: string,
buildPack: string,
fqdn: string,
port: number,
exposePort: number,
installCommand: string,
buildCommand: string,
startCommand: string,
baseDirectory: string,
publishDirectory: string,
pythonWSGI: string,
pythonModule: string,
pythonVariable: string,
dockerFileLocation: string,
denoMainFile: string,
denoOptions: string,
baseImage: string,
gitCommitHash: string,
baseBuildImage: string,
deploymentType: string,
baseDatabaseBranch: string,
dockerComposeFile: string,
dockerComposeFileLocation: string,
dockerComposeConfiguration: string,
simpleDockerfile: string,
dockerRegistryImageName: string
}
Body: {
name: string;
buildPack: string;
fqdn: string;
port: number;
exposePort: number;
installCommand: string;
buildCommand: string;
startCommand: string;
baseDirectory: string;
publishDirectory: string;
pythonWSGI: string;
pythonModule: string;
pythonVariable: string;
dockerFileLocation: string;
denoMainFile: string;
denoOptions: string;
baseImage: string;
gitCommitHash: string;
baseBuildImage: string;
deploymentType: string;
baseDatabaseBranch: string;
dockerComposeFile: string;
dockerComposeFileLocation: string;
dockerComposeConfiguration: string;
simpleDockerfile: string;
dockerRegistryImageName: string;
};
}
export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
Querystring: { domain: string };
Body: {
debug: boolean;
previews: boolean;
dualCerts: boolean;
autodeploy: boolean;
branch: string;
projectId: number;
isBot: boolean;
isDBBranching: boolean;
isCustomSSL: boolean;
isHttp2: boolean;
};
}
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };
Body: { force: boolean }
Querystring: { domain: string };
Body: { force: boolean };
}
export interface CheckDomain extends OnlyId {
Querystring: { domain: string; };
Querystring: { domain: string };
}
export interface CheckDNS extends OnlyId {
Querystring: { domain: string; };
Body: {
exposePort: number,
fqdn: string,
forceSave: boolean,
dualCerts: boolean
}
Querystring: { domain: string };
Body: {
exposePort: number;
fqdn: string;
forceSave: boolean;
dualCerts: boolean;
};
}
export interface DeployApplication {
Querystring: { domain: string }
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
Querystring: { domain: string };
Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
}
export interface GetImages {
Body: { buildPack: string, deploymentType: string }
Body: { buildPack: string; deploymentType: string };
}
export interface SaveApplicationSource extends OnlyId {
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string }
Body: {
gitSourceId?: string | null;
forPublic?: boolean;
type?: string;
simpleDockerfile?: string;
};
}
export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string }
Querystring: { repository: string; branch: string };
}
export interface SaveDestination extends OnlyId {
Body: { destinationId: string }
Body: { destinationId: string };
}
export interface SaveSecret extends OnlyId {
Body: {
name: string,
value: string,
isBuildSecret: boolean,
previewSecret: boolean,
isNew: boolean
}
Body: {
name: string;
value: string;
isBuildSecret: boolean;
previewSecret: boolean;
isNew: boolean;
};
}
export interface DeleteSecret extends OnlyId {
Body: { name: string }
Body: { name: string };
}
export interface SaveStorage extends OnlyId {
Body: {
path: string,
newStorage: boolean,
storageId: string
}
Body: {
path: string;
newStorage: boolean;
storageId: string;
};
}
export interface DeleteStorage extends OnlyId {
Body: {
path: string,
}
Body: {
path: string;
};
}
export interface GetApplicationLogs {
Params: {
id: string,
containerId: string
}
Querystring: {
since: number,
}
Params: {
id: string;
containerId: string;
};
Querystring: {
since: number;
};
}
export interface GetBuilds extends OnlyId {
Querystring: {
buildId: string
skip: number,
}
Querystring: {
buildId: string;
skip: number;
};
}
export interface GetBuildIdLogs {
Params: {
id: string,
buildId: string
},
Querystring: {
sequence: number
}
Params: {
id: string;
buildId: string;
};
Querystring: {
sequence: number;
};
}
export interface SaveDeployKey extends OnlyId {
Body: {
deployKeyId: number
}
Body: {
deployKeyId: number;
};
}
export interface CancelDeployment {
Body: {
buildId: string,
applicationId: string
}
Body: {
buildId: string;
applicationId: string;
};
}
export interface DeployApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
branch: string,
forceRebuild?: boolean
}
Body: {
pullmergeRequestId: string | null;
branch: string;
forceRebuild?: boolean;
};
}
export interface StopPreviewApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
}
Body: {
pullmergeRequestId: string | null;
};
}
export interface RestartPreviewApplication {
Params: {
id: string,
pullmergeRequestId: string | null,
}
Params: {
id: string;
pullmergeRequestId: string | null;
};
}
export interface RestartApplication {
Params: {
id: string,
},
Body: {
imageId: string | null,
}
}
Params: {
id: string;
};
Body: {
imageId: string | null;
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -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: {

View File

@@ -1,6 +1,6 @@
import { compareVersions } from "compare-versions";
import cuid from "cuid";
import bcrypt from "bcryptjs";
import { compareVersions } from 'compare-versions';
import cuid from 'cuid';
import bcrypt from 'bcryptjs';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import {
@@ -13,12 +13,12 @@ import {
uniqueName,
version,
sentryDSN,
executeCommand,
} from "../../../lib/common";
import { scheduler } from "../../../lib/scheduler";
import type { FastifyReply, FastifyRequest } from "fastify";
import type { Login, Update } from ".";
import type { GetCurrentUser } from "./types";
executeCommand
} from '../../../lib/common';
import { scheduler } from '../../../lib/scheduler';
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;
@@ -29,9 +29,9 @@ export async function backup(request: FastifyRequest) {
try {
const { backupData } = request.params;
let std = null;
const [id, backupType, type, zipped, storage] = backupData.split(':')
console.log(id, backupType, type, zipped, storage)
const database = await prisma.database.findUnique({ where: { id } })
const [id, backupType, type, zipped, storage] = backupData.split(':');
console.log(id, backupType, type, zipped, storage);
const database = await prisma.database.findUnique({ where: { id } });
if (database) {
// await executeDockerCmd({
// dockerId: database.destinationDockerId,
@@ -40,8 +40,7 @@ export async function backup(request: FastifyRequest) {
std = await executeCommand({
dockerId: database.destinationDockerId,
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
})
});
}
if (std.stdout) {
return std.stdout;
@@ -58,9 +57,9 @@ export async function cleanupManually(request: FastifyRequest) {
try {
const { serverId } = request.body;
const destination = await prisma.destinationDocker.findUnique({
where: { id: serverId },
where: { id: serverId }
});
await cleanupDockerStorage(destination.id, true, true);
await cleanupDockerStorage(destination.id);
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
@@ -68,17 +67,25 @@ export async function cleanupManually(request: FastifyRequest) {
}
export async function refreshTags() {
try {
const { default: got } = await import('got')
const { default: got } = await import('got');
try {
if (isDev) {
const tags = await fs.readFile('./devTags.json', 'utf8')
await fs.writeFile('./tags.json', tags)
let tags = await fs.readFile('./devTags.json', 'utf8');
try {
if (await fs.stat('./testTags.json')) {
const testTags = await fs.readFile('./testTags.json', 'utf8');
if (testTags.length > 0) {
tags = JSON.parse(tags).concat(JSON.parse(testTags));
}
}
} catch (error) {}
await fs.writeFile('./tags.json', tags);
} else {
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
await fs.writeFile('/app/tags.json', tags)
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
await fs.writeFile('/app/tags.json', tags);
}
} catch (error) {
console.log(error)
console.log(error);
}
return {};
@@ -88,17 +95,25 @@ export async function refreshTags() {
}
export async function refreshTemplates() {
try {
const { default: got } = await import('got')
const { default: got } = await import('got');
try {
if (isDev) {
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
try {
if (await fs.stat('./testTemplate.yaml')) {
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
}
} catch (error) {}
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
} else {
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
const response = await got
.get('https://get.coollabs.io/coolify/service-templates.yaml')
.text();
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
}
} catch (error) {
console.log(error)
console.log(error);
}
return {};
} catch ({ status, message }) {
@@ -107,28 +122,29 @@ export async function refreshTemplates() {
}
export async function checkUpdate(request: FastifyRequest) {
try {
const { default: got } = await import('got')
const { default: got } = await import('got');
const isStaging =
request.hostname === "staging.coolify.io" ||
request.hostname === "arm.coolify.io";
request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io';
const currentVersion = version;
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
searchParams: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
}).json()
const { coolify } = await got
.get('https://get.coollabs.io/versions.json', {
searchParams: {
appId: process.env['COOLIFY_APP_ID'] || undefined,
version: currentVersion
}
})
.json();
const latestVersion = coolify.main.version;
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
if (isStaging) {
return {
isUpdateAvailable: true,
latestVersion: "next",
latestVersion: 'next'
};
}
return {
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
latestVersion,
latestVersion
};
} catch ({ status, message }) {
return errorHandler({ status, message });
@@ -142,8 +158,13 @@ export async function update(request: FastifyRequest<Update>) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` });
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"`
});
return {};
} else {
await asyncSleep(2000);
@@ -156,12 +177,12 @@ export async function update(request: FastifyRequest<Update>) {
export async function resetQueue(request: FastifyRequest<any>) {
try {
const teamId = request.user.teamId;
if (teamId === "0") {
if (teamId === '0') {
await prisma.build.updateMany({
where: { status: { in: ["queued", "running"] } },
data: { status: "canceled" },
where: { status: { in: ['queued', 'running'] } },
data: { status: 'canceled' }
});
scheduler.workers.get("deployApplication").postMessage("cancel");
scheduler.workers.get('deployApplication').postMessage('cancel');
}
} catch ({ status, message }) {
return errorHandler({ status, message });
@@ -170,7 +191,7 @@ export async function resetQueue(request: FastifyRequest<any>) {
export async function restartCoolify(request: FastifyRequest<any>) {
try {
const teamId = request.user.teamId;
if (teamId === "0") {
if (teamId === '0') {
if (!isDev) {
await executeCommand({ command: `docker restart coolify` });
return {};
@@ -180,7 +201,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
}
throw {
status: 500,
message: "You are not authorized to restart Coolify.",
message: 'You are not authorized to restart Coolify.'
};
} catch ({ status, message }) {
return errorHandler({ status, message });
@@ -192,43 +213,52 @@ export async function showDashboard(request: FastifyRequest) {
const userId = request.user.userId;
const teamId = request.user.teamId;
let applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true },
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true },
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
include: { destinationDocker: true, teams: true },
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, teams: true }
});
const gitSources = await prisma.gitSource.findMany({
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
include: { teams: true },
where: {
OR: [
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
{ isSystemWide: true }
]
},
include: { teams: true }
});
const destinations = await prisma.destinationDocker.findMany({
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
include: { teams: true },
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true }
});
const settings = await listSettings();
let foundUnconfiguredApplication = false;
for (const application of applications) {
if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
foundUnconfiguredApplication = true
if (
((!application.buildPack || !application.branch) && !application.simpleDockerfile) ||
!application.destinationDockerId ||
(!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose')
) {
foundUnconfiguredApplication = true;
}
}
let foundUnconfiguredService = false;
for (const service of services) {
if (!service.fqdn) {
foundUnconfiguredService = true
foundUnconfiguredService = true;
}
}
let foundUnconfiguredDatabase = false;
for (const database of databases) {
if (!database.version) {
foundUnconfiguredDatabase = true
foundUnconfiguredDatabase = true;
}
}
return {
@@ -240,101 +270,94 @@ export async function showDashboard(request: FastifyRequest) {
services,
gitSources,
destinations,
settings,
settings
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function login(
request: FastifyRequest<Login>,
reply: FastifyReply
) {
export async function login(request: FastifyRequest<Login>, reply: FastifyReply) {
if (request.user) {
return reply.redirect("/dashboard");
return reply.redirect('/dashboard');
} else {
const { email, password, isLogin } = request.body || {};
if (!email || !password) {
throw { status: 500, message: "Email and password are required." };
throw { status: 500, message: 'Email and password are required.' };
}
const users = await prisma.user.count();
const userFound = await prisma.user.findUnique({
where: { email },
include: { teams: true, permission: true },
rejectOnNotFound: false,
rejectOnNotFound: false
});
if (!userFound && isLogin) {
throw { status: 500, message: "User not found." };
throw { status: 500, message: 'User not found.' };
}
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
let uid = cuid();
let permission = "read";
let permission = 'read';
let isAdmin = false;
if (users === 0) {
await prisma.setting.update({
where: { id },
data: { isRegistrationEnabled: false },
data: { isRegistrationEnabled: false }
});
uid = "0";
uid = '0';
}
if (userFound) {
if (userFound.type === "email") {
if (userFound.password === "RESETME") {
if (userFound.type === 'email') {
if (userFound.password === 'RESETME') {
const hashedPassword = await hashPassword(password);
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
if (userFound.id === "0") {
if (userFound.id === '0') {
await prisma.user.update({
where: { email: userFound.email },
data: { password: "RESETME" },
data: { password: 'RESETME' }
});
} else {
await prisma.user.update({
where: { email: userFound.email },
data: { password: "RESETTIMEOUT" },
data: { password: 'RESETTIMEOUT' }
});
}
throw {
status: 500,
message:
"Password reset link has expired. Please request a new one.",
message: 'Password reset link has expired. Please request a new one.'
};
} else {
await prisma.user.update({
where: { email: userFound.email },
data: { password: hashedPassword },
data: { password: hashedPassword }
});
return {
userId: userFound.id,
teamId: userFound.id,
permission: userFound.permission,
isAdmin: true,
isAdmin: true
};
}
}
const passwordMatch = await bcrypt.compare(
password,
userFound.password
);
const passwordMatch = await bcrypt.compare(password, userFound.password);
if (!passwordMatch) {
throw {
status: 500,
message: "Wrong password or email address.",
message: 'Wrong password or email address.'
};
}
uid = userFound.id;
isAdmin = true;
}
} else {
permission = "owner";
permission = 'owner';
isAdmin = true;
if (!isRegistrationEnabled) {
throw {
status: 404,
message: "Registration disabled by administrator.",
message: 'Registration disabled by administrator.'
};
}
const hashedPassword = await hashPassword(password);
@@ -344,17 +367,17 @@ export async function login(
id: uid,
email,
password: hashedPassword,
type: "email",
type: 'email',
teams: {
create: {
id: uid,
name: uniqueName(),
destinationDocker: { connect: { network: "coolify" } },
},
destinationDocker: { connect: { network: 'coolify' } }
}
},
permission: { create: { teamId: uid, permission: "owner" } },
permission: { create: { teamId: uid, permission: 'owner' } }
},
include: { teams: true },
include: { teams: true }
});
} else {
await prisma.user.create({
@@ -362,16 +385,16 @@ export async function login(
id: uid,
email,
password: hashedPassword,
type: "email",
type: 'email',
teams: {
create: {
id: uid,
name: uniqueName(),
},
name: uniqueName()
}
},
permission: { create: { teamId: uid, permission: "owner" } },
permission: { create: { teamId: uid, permission: 'owner' } }
},
include: { teams: true },
include: { teams: true }
});
}
}
@@ -379,23 +402,20 @@ export async function login(
userId: uid,
teamId: uid,
permission,
isAdmin,
isAdmin
};
}
}
export async function getCurrentUser(
request: FastifyRequest<GetCurrentUser>,
fastify
) {
export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
let token = null;
const { teamId } = request.query;
try {
const user = await prisma.user.findUnique({
where: { id: request.user.userId },
where: { id: request.user.userId }
});
if (!user) {
throw "User not found";
throw 'User not found';
}
} catch (error) {
throw { status: 401, message: error };
@@ -404,17 +424,15 @@ export async function getCurrentUser(
try {
const user = await prisma.user.findFirst({
where: { id: request.user.userId, teams: { some: { id: teamId } } },
include: { teams: true, permission: true },
include: { teams: true, permission: true }
});
if (user) {
const permission = user.permission.find(
(p) => p.teamId === teamId
).permission;
const permission = user.permission.find((p) => p.teamId === teamId).permission;
const payload = {
...request.user,
teamId,
permission: permission || null,
isAdmin: permission === "owner" || permission === "admin",
isAdmin: permission === 'owner' || permission === 'admin'
};
token = fastify.jwt.sign(payload);
}
@@ -422,12 +440,14 @@ export async function getCurrentUser(
// No new token -> not switching teams
}
}
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
const pendingInvitations = await prisma.teamInvitation.findMany({
where: { uid: request.user.userId }
});
return {
settings: await prisma.setting.findUnique({ where: { id: "0" } }),
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
sentryDSN,
pendingInvitations,
token,
...request.user,
...request.user
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,11 +22,11 @@ export async function listSources(request: FastifyRequest) {
export async function saveSource(request, reply) {
try {
const { id } = request.params
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
if (customPort) customPort = Number(customPort)
await prisma.gitSource.update({
where: { id },
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
});
return reply.code(201).send()
} catch ({ status, message }) {
@@ -48,6 +48,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
apiUrl: null,
organization: null,
customPort: 22,
customUser: 'git',
},
settings
}
@@ -133,7 +134,7 @@ 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 } =
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
request.body
if (oauthId) oauthId = Number(oauthId);
@@ -142,7 +143,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
if (id === 'new') {
const newId = cuid()
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
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 } },
@@ -158,7 +159,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
id: newId
}
} else {
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
await prisma.gitlabApp.update({
where: { id },
data: {

View File

@@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
appSecret: string,
groupName: string,
customPort: number,
customUser: string,
}
}
export interface CheckGitLabOAuthId extends OnlyId {

View File

@@ -1,9 +1,32 @@
import { FastifyRequest } from "fastify";
import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common";
import { getTemplates } from "../../../lib/services";
import { OnlyId } from "../../../types";
import { FastifyRequest } from 'fastify';
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
import { getTemplates } from '../../../lib/services';
import { OnlyId } from '../../../types';
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
function generateServices(serviceId, containerId, port) {
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
if (isHttp2) {
return {
[serviceId]: {
loadbalancer: {
servers: [
{
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
}
]
}
},
[`${serviceId}-http2`]: {
loadbalancer: {
servers: [
{
url: `h2c://${containerId}:${port}`
}
]
}
}
};
}
return {
[serviceId]: {
loadbalancer: {
@@ -14,43 +37,57 @@ function generateServices(serviceId, containerId, port) {
]
}
}
}
};
}
function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) {
function generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
isDualCerts,
isCustomSSL,
isHttp2 = false
) {
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
}`;
let http: any = {
entrypoints: ['web'],
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
rule,
service: `${serviceId}`,
priority: 2,
middlewares: []
}
};
let https: any = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
rule,
service: `${serviceId}`,
priority: 2,
tls: {
certresolver: 'letsencrypt'
},
middlewares: []
}
};
let httpWWW: any = {
entrypoints: ['web'],
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
rule: ruleWWW,
service: `${serviceId}`,
priority: 2,
middlewares: []
}
};
let httpsWWW: any = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
rule: ruleWWW,
service: `${serviceId}`,
priority: 2,
tls: {
certresolver: 'letsencrypt'
},
middlewares: []
}
};
// 2. http + non-www only
if (!isHttps && !isWWW) {
https.middlewares.push('redirect-to-http');
@@ -58,19 +95,19 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpWWW.middlewares.push('redirect-to-non-www');
httpsWWW.middlewares.push('redirect-to-non-www');
delete https.tls
delete httpsWWW.tls
delete https.tls;
delete httpsWWW.tls;
}
// 3. http + www only
// 3. http + www only
if (!isHttps && isWWW) {
https.middlewares.push('redirect-to-http');
httpsWWW.middlewares.push('redirect-to-http');
http.middlewares.push('redirect-to-www');
https.middlewares.push('redirect-to-www');
delete https.tls
delete httpsWWW.tls
delete https.tls;
delete httpsWWW.tls;
}
// 5. https + non-www only
if (isHttps && !isWWW) {
@@ -86,17 +123,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpsWWW.tls = true;
} else {
https.tls = true;
delete httpsWWW.tls.certresolver
delete httpsWWW.tls.certresolver;
httpsWWW.tls.domains = {
main: domain
}
};
}
} else {
if (!isDualCerts) {
delete httpsWWW.tls.certresolver
delete httpsWWW.tls.certresolver;
httpsWWW.tls.domains = {
main: domain
}
};
}
}
}
@@ -114,26 +151,59 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpsWWW.tls = true;
} else {
httpsWWW.tls = true;
delete https.tls.certresolver
delete https.tls.certresolver;
https.tls.domains = {
main: domain
}
};
}
} else {
if (!isDualCerts) {
delete https.tls.certresolver
delete https.tls.certresolver;
https.tls.domains = {
main: domain
}
};
}
}
}
if (isHttp2) {
let http2 = {
...http,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let http2WWW = {
...httpWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2 = {
...https,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2WWW = {
...httpsWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
return {
[`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-http2`]: { ...http2 },
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-secure-http2`]: { ...https2 },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-www-http2`]: { ...http2WWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
};
}
return {
[`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
}
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
};
}
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
const traefik = {
@@ -174,26 +244,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const coolifySettings = await prisma.setting.findFirst();
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
traefik.http.routers['catchall-http'] = {
entrypoints: ["web"],
rule: "HostRegexp(`{catchall:.*}`)",
service: "noop",
entrypoints: ['web'],
rule: 'HostRegexp(`{catchall:.*}`)',
service: 'noop',
priority: 1,
middlewares: ["redirect-regexp"]
}
middlewares: ['redirect-regexp']
};
traefik.http.routers['catchall-https'] = {
entrypoints: ["websecure"],
rule: "HostRegexp(`{catchall:.*}`)",
service: "noop",
entrypoints: ['websecure'],
rule: 'HostRegexp(`{catchall:.*}`)',
service: 'noop',
priority: 1,
middlewares: ["redirect-regexp"]
}
middlewares: ['redirect-regexp']
};
traefik.http.middlewares['redirect-regexp'] = {
redirectregex: {
regex: '(.*)',
replacement: coolifySettings.proxyDefaultRedirect,
permanent: false
}
}
};
traefik.http.services['noop'] = {
loadBalancer: {
servers: [
@@ -202,25 +272,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
}
]
}
}
};
}
const sslpath = '/etc/traefik/acme/custom';
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
let certificates = await prisma.certificate.findMany({
where: {
team: {
applications: { some: { settings: { isCustomSSL: true } } },
destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } }
}
}
});
if (remote) {
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
certificates = await prisma.certificate.findMany({
where: {
team: {
applications: { some: { settings: { isCustomSSL: true } } },
destinationDocker: {
some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true }
}
}
}
});
}
let parsedCertificates = []
let parsedCertificates = [];
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem`
})
});
}
if (parsedCertificates.length > 0) {
traefik.tls.certificates = parsedCertificates
traefik.tls.certificates = parsedCertificates;
}
let applications = [];
@@ -236,7 +322,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
destinationDocker: true,
persistentStorage: true,
serviceSecret: true,
serviceSetting: true,
serviceSetting: true
},
orderBy: { createdAt: 'desc' }
});
@@ -251,23 +337,25 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
destinationDocker: true,
persistentStorage: true,
serviceSecret: true,
serviceSetting: true,
serviceSetting: true
},
orderBy: { createdAt: 'desc' },
orderBy: { createdAt: 'desc' }
});
}
if (applications.length > 0) {
const dockerIds = new Set()
const runningContainers = {}
const dockerIds = new Set();
const runningContainers = {};
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) {
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
const { stdout: container } = await executeCommand({
dockerId,
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
});
if (container) {
const containersArray = container.trim().split('\n');
if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray
runningContainers[dockerId] = containersArray;
}
}
}
@@ -289,38 +377,54 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
if (
!runningContainers[destinationDockerId] ||
runningContainers[destinationDockerId].length === 0 ||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id)).length === 0
runningContainers[destinationDockerId].filter((container) => container.startsWith(id))
.length === 0
) {
continue
continue;
}
if (buildPack === 'compose') {
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
if (services.length > 0) {
for (const service of services) {
const [key, value] = service
const [key, value] = service;
if (key && value) {
if (!value.fqdn || !value.port) {
continue;
}
const { fqdn, port } = value
const containerId = `${id}-${key}`
const { fqdn, port } = value;
const containerId = `${id}-${key}`;
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const pathPrefix = '/'
const pathPrefix = '/';
const isCustomSSL = false;
const dualCerts = false;
const serviceId = `${id}-${port || 'default'}`
const serviceId = `${id}-${port || 'default'}`;
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) }
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, containerId, port)
};
}
}
}
continue;
}
const { previews, dualCerts, isCustomSSL } = settings;
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
const { network, id: dockerId } = destinationDocker;
if (!fqdn) {
continue;
@@ -329,12 +433,31 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const pathPrefix = '/'
const serviceId = `${id}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
const pathPrefix = '/';
const serviceId = `${id}-${port || 'default'}`;
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL,
isHttp2
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, id, port, isHttp2, isHttps)
};
if (previews) {
const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
const { stdout } = await executeCommand({
dockerId,
command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
});
if (stdout) {
const containers = stdout
.trim()
@@ -343,44 +466,57 @@ 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(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
const pathPrefix = '/';
const serviceId = `${container}-${port || 'default'}`;
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
previewDomain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, container, port, isHttp2)
};
}
}
}
}
} catch (error) {
console.log(error)
console.log(error);
}
}
}
if (services.length > 0) {
const dockerIds = new Set()
const runningContainers = {}
const dockerIds = new Set();
const runningContainers = {};
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) {
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
const { stdout: container } = await executeCommand({
dockerId,
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
});
if (container) {
const containersArray = container.trim().split('\n');
if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray
runningContainers[dockerId] = containersArray;
}
}
}
for (const service of services) {
try {
let {
fqdn,
id,
type,
destinationDockerId,
dualCerts,
serviceSetting
} = service;
let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
if (!fqdn) {
continue;
}
@@ -392,7 +528,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
runningContainers[destinationDockerId].length === 0 ||
!runningContainers[destinationDockerId].includes(id)
) {
continue
continue;
}
const templates = await getTemplates();
let found = templates.find((a) => a.type === type);
@@ -401,88 +537,144 @@ 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];
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 (let configuration of proxy) {
if (configuration.domain) {
const setting = serviceSetting.find((a) => a.variableName === configuration.domain);
const setting = serviceSetting.find(
(a) => a.variableName === configuration.domain
);
if (setting) {
configuration.domain = configuration.domain.replace(configuration.domain, setting.value);
configuration.domain = configuration.domain.replace(
configuration.domain,
setting.value
);
}
}
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
const foundPortVariable = serviceSetting.find(
(a) => a.name.toLowerCase() === 'port'
);
if (foundPortVariable) {
configuration.port = foundPortVariable.value
configuration.port = foundPortVariable.value;
}
let port, pathPrefix, customDomain;
if (configuration) {
port = configuration?.port;
pathPrefix = configuration?.pathPrefix || '/';
customDomain = configuration?.domain
customDomain = configuration?.domain;
}
if (customDomain) {
fqdn = customDomain
fqdn = customDomain;
} else {
fqdn = service.fqdn
fqdn = service.fqdn;
}
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const isCustomSSL = false;
const serviceId = `${oneService}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) }
const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, oneService, port)
};
}
} else {
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
for (let [index, port] of found.services[oneService].ports.entries()) {
if (port == 22) continue;
if (index === 0) {
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
const foundPortVariable = serviceSetting.find(
(a) => a.name.toLowerCase() === 'port'
);
if (foundPortVariable) {
port = foundPortVariable.value
port = foundPortVariable.value;
}
}
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const pathPrefix = '/'
const isCustomSSL = false
const serviceId = `${oneService}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
const pathPrefix = '/';
const isCustomSSL = false;
const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, id, port)
};
}
}
}
}
} catch (error) {
console.log(error)
console.log(error);
}
}
}
if (!remote) {
const { fqdn, dualCerts } = await prisma.setting.findFirst();
if (!fqdn) {
return
return;
}
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const id = isDev ? 'host.docker.internal' : 'coolify'
const container = isDev ? 'host.docker.internal' : 'coolify'
const port = 3000
const pathPrefix = '/'
const isCustomSSL = false
const serviceId = `${id}-${port || 'default'}`
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
const id = isDev ? 'host.docker.internal' : 'coolify';
const container = isDev ? 'host.docker.internal' : 'coolify';
const port = 3000;
const pathPrefix = '/';
const isCustomSSL = false;
const serviceId = `${id}-${port || 'default'}`;
traefik.http.routers = {
...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, container, port)
};
}
} catch (error) {
console.log(error)
console.log(error);
} finally {
if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null;
@@ -496,9 +688,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
try {
const { id } = request.query
const { id } = request.query;
if (id) {
const { privatePort, publicPort, type, address = id } = request.query
const { privatePort, publicPort, type, address = id } = request.query;
let traefik = {};
if (publicPort && type && privatePort) {
if (type === 'tcp') {
@@ -559,18 +751,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
}
}
} else {
throw { status: 500 }
throw { status: 500 };
}
}
} else {
throw { status: 500 }
throw { status: 500 };
}
return {
...traefik
};
}
throw { status: 500 }
throw { status: 500 };
} catch ({ status, message }) {
return errorHandler({ status, message })
return errorHandler({ status, message });
}
}

View File

@@ -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, reply) => proxyConfiguration(request, false));
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) =>
otherProxyConfiguration(request)
);
};
export default root;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +0,0 @@
import { addToast } from './store';
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
export function errorNotification(error: any | { message: string }): void {
if (error instanceof Error) {
addToast({
message: error.message,
type: 'error'
});
} else {
addToast({
message: error,
type: 'error'
});
}
}
export function getRndInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

View File

@@ -1,7 +0,0 @@
import { goto } from '$app/navigation';
import { errorNotification } from '$lib/common';
import { trpc } from '$lib/store';
export async function saveForm() {
return await trpc.applications.save.mutate();
}

View File

@@ -1,11 +0,0 @@
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
server: {
host: '0.0.0.0',
},
plugins: [sveltekit()]
};
export default config;

View File

@@ -1,183 +0,0 @@
import type { Permission, Setting, Team, TeamInvitation, User } from '@prisma/client';
import { prisma } from '../prisma';
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
import fs from 'fs/promises';
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
import type { Config } from 'unique-names-generator';
import { env } from '../env';
import { day } from './dayjs';
import { executeCommand } from './executeCommand';
const customConfig: Config = {
dictionaries: [adjectives, colors, animals],
style: 'capital',
separator: ' ',
length: 3
};
const algorithm = 'aes-256-ctr';
export const isDev = env.NODE_ENV === 'development';
export const version = '3.13.0';
export const sentryDSN =
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
export async function listSettings(): Promise<Setting | null> {
return await prisma.setting.findUnique({ where: { id: '0' } });
}
export async function getCurrentUser(
userId: string
): Promise<(User & { permission: Permission[]; teams: Team[] }) | null> {
return await prisma.user.findUnique({
where: { id: userId },
include: { teams: true, permission: true }
});
}
export async function getTeamInvitation(userId: string): Promise<TeamInvitation[]> {
return await prisma.teamInvitation.findMany({ where: { uid: userId } });
}
export async function hashPassword(password: string): Promise<string> {
const saltRounds = 15;
return bcrypt.hash(password, saltRounds);
}
export async function comparePassword(password: string, hashedPassword: string): Promise<boolean> {
return bcrypt.compare(password, hashedPassword);
}
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
export const decrypt = (hashString: string) => {
if (hashString) {
try {
const hash = JSON.parse(hashString);
const decipher = crypto.createDecipheriv(
algorithm,
env.COOLIFY_SECRET_KEY,
Buffer.from(hash.iv, 'hex')
);
const decrpyted = Buffer.concat([
decipher.update(Buffer.from(hash.content, 'hex')),
decipher.final()
]);
return decrpyted.toString();
} catch (error) {
if (error instanceof Error) {
console.log({ decryptionError: error.message });
}
return hashString;
}
}
return false;
};
export function generateRangeArray(start: number, end: number) {
return Array.from({ length: end - start }, (_v, k) => k + start);
}
export function generateTimestamp(): string {
return `${day().format('HH:mm:ss.SSS')}`;
}
export const encrypt = (text: string) => {
if (text) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, env.COOLIFY_SECRET_KEY, iv);
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
return JSON.stringify({
iv: iv.toString('hex'),
content: encrypted.toString('hex')
});
}
return false;
};
export async function getTemplates() {
const templatePath = isDev ? './templates.json' : '/app/templates.json';
const open = await fs.open(templatePath, 'r');
try {
let data = await open.readFile({ encoding: 'utf-8' });
let jsonData = JSON.parse(data);
if (isARM(process.arch)) {
jsonData = jsonData.filter((d: { arch: string }) => d.arch !== 'amd64');
}
return jsonData;
} catch (error) {
return [];
} finally {
await open?.close();
}
}
export function isARM(arch: string) {
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
return true;
}
return false;
}
export async function removeService({ id }: { id: string }): Promise<void> {
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
await prisma.fider.deleteMany({ where: { serviceId: id } });
await prisma.ghost.deleteMany({ where: { serviceId: id } });
await prisma.umami.deleteMany({ where: { serviceId: id } });
await prisma.hasura.deleteMany({ where: { serviceId: id } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
await prisma.moodle.deleteMany({ where: { serviceId: id } });
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
await prisma.searxng.deleteMany({ where: { serviceId: id } });
await prisma.weblate.deleteMany({ where: { serviceId: id } });
await prisma.taiga.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } });
}
export const createDirectories = async ({
repository,
buildId
}: {
repository: string;
buildId: string;
}): Promise<{ workdir: string; repodir: string }> => {
if (repository) repository = repository.replaceAll(' ', '');
const repodir = `/tmp/build-sources/${repository}/`;
const workdir = `/tmp/build-sources/${repository}/${buildId}`;
let workdirFound = false;
try {
workdirFound = !!(await fs.stat(workdir));
} catch (error) {}
if (workdirFound) {
await executeCommand({ command: `rm -fr ${workdir}` });
}
await executeCommand({ command: `mkdir -p ${workdir}` });
return {
workdir,
repodir
};
};
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
if (!username || !password) {
return null;
}
let decryptedPassword = decrypt(password);
const location = `${workdir}/.docker`;
try {
await fs.mkdir(`${workdir}/.docker`);
} catch (error) {
console.log(error);
}
const payload = JSON.stringify({
auths: {
[url]: {
auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
}
}
});
await fs.writeFile(`${location}/config.json`, payload);
return location;
}

View File

@@ -1,391 +0,0 @@
import { z } from 'zod';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import { privateProcedure, router } from '../../trpc';
import { prisma } from '../../../prisma';
import { executeCommand } from '../../../lib/executeCommand';
import {
checkContainer,
defaultComposeConfiguration,
formatLabelsOnDocker,
removeContainer
} from '../../../lib/docker';
import { deployApplication, generateConfigHash, getApplicationFromDB } from './lib';
import cuid from 'cuid';
import { createDirectories, saveDockerRegistryCredentials } from '../../../lib/common';
export const applicationsRouter = router({
getApplicationById: privateProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const id: string = input.id;
const teamId = ctx.user?.teamId;
if (!teamId) {
throw { status: 400, message: 'Team not found.' };
}
const application = await getApplicationFromDB(id, teamId);
return {
success: true,
data: { ...application }
};
}),
save: privateProcedure
.input(
z.object({
id: z.string()
})
)
.mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
// const buildId = await deployApplication(id, teamId);
return {
// buildId
};
}),
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
const id: string = input.id;
const teamId = ctx.user?.teamId;
if (!teamId) {
throw { status: 400, message: 'Team not found.' };
}
let payload = [];
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
if (application.buildPack === 'compose') {
const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id,
command: `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
});
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0 && containersArray[0] !== '') {
for (const container of containersArray) {
let isRunning = false;
let isExited = false;
let isRestarting = false;
const containerObj = JSON.parse(container);
const status = containerObj.State;
if (status === 'running') {
isRunning = true;
}
if (status === 'exited') {
isExited = true;
}
if (status === 'restarting') {
isRestarting = true;
}
payload.push({
name: containerObj.Names,
status: {
isRunning,
isExited,
isRestarting
}
});
}
}
} else {
let isRunning = false;
let isExited = false;
let isRestarting = false;
const status = await checkContainer({
dockerId: application.destinationDocker.id,
container: id
});
if (status?.found) {
isRunning = status.status.isRunning;
isExited = status.status.isExited;
isRestarting = status.status.isRestarting;
payload.push({
name: id,
status: {
isRunning,
isExited,
isRestarting
}
});
}
}
}
return payload;
}),
cleanup: privateProcedure.query(async ({ ctx }) => {
const teamId = ctx.user?.teamId;
let applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
for (const application of applications) {
if (
!application.buildPack ||
!application.destinationDockerId ||
!application.branch ||
(!application.settings?.isBot && !application?.fqdn)
) {
if (application?.destinationDockerId && application.destinationDocker?.network) {
const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id,
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
});
if (containers) {
const containersArray = containers.trim().split('\n');
for (const container of containersArray) {
const containerObj = JSON.parse(container);
const id = containerObj.ID;
await removeContainer({ id, dockerId: application.destinationDocker.id });
}
}
}
await prisma.applicationSettings.deleteMany({ where: { applicationId: 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.application.deleteMany({ where: { id: application.id } });
}
}
return {};
}),
stop: privateProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
const { id: dockerId } = application.destinationDocker;
if (application.buildPack === 'compose') {
const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id,
command: `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
});
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0 && containersArray[0] !== '') {
for (const container of containersArray) {
const containerObj = JSON.parse(container);
await removeContainer({
id: containerObj.ID,
dockerId: application.destinationDocker.id
});
}
}
return;
}
const { found } = await checkContainer({ dockerId, container: id });
if (found) {
await removeContainer({ id, dockerId: application.destinationDocker.id });
}
}
return {};
}),
restart: privateProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
let application = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
const buildId = cuid();
const { id: dockerId, network } = application.destinationDocker;
const {
dockerRegistry,
secrets,
pullmergeRequestId,
port,
repository,
persistentStorage,
id: applicationId,
buildPack,
exposePort
} = application;
let location = null;
const labels = [];
let image = null;
const envs = [`PORT=${port}`];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
if (isSecretFound.length > 0) {
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
} else {
envs.push(`${secret.name}='${secret.value}'`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}='${secret.value}'`);
}
}
});
}
const { workdir } = await createDirectories({ repository, buildId });
const { stdout: container } = await executeCommand({
dockerId,
command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'`
});
const containersArray = container.trim().split('\n');
for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container);
image = containerObj[0].Image;
Object.keys(containerObj[0].Labels).forEach(function (key) {
if (key.startsWith('coolify')) {
labels.push(`${key}=${containerObj[0].Labels[key]}`);
}
});
}
if (dockerRegistry) {
const { url, username, password } = dockerRegistry;
location = await saveDockerRegistryCredentials({ url, username, password, workdir });
}
let imageFoundLocally = false;
try {
await executeCommand({
dockerId,
command: `docker image inspect ${image}`
});
imageFoundLocally = true;
} catch (error) {
//
}
let imageFoundRemotely = false;
try {
await executeCommand({
dockerId,
command: `docker ${location ? `--config ${location}` : ''} pull ${image}`
});
imageFoundRemotely = true;
} catch (error) {
//
}
if (!imageFoundLocally && !imageFoundRemotely) {
throw { status: 500, message: 'Image not found, cannot restart application.' };
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
const volumes =
persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
buildPack !== 'docker' ? '/app' : ''
}${storage.path}`;
}) || [];
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = {
version: '3.8',
services: {
[applicationId]: {
image,
container_name: applicationId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
labels,
depends_on: [],
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
...defaultComposeConfiguration(network)
}
},
networks: {
[network]: {
external: true
}
},
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
try {
await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` });
await executeCommand({ dockerId, command: `docker rm ${id}` });
} catch (error) {
//
}
await executeCommand({
dockerId,
command: `docker compose --project-directory ${workdir} up -d`
});
}
return {};
}),
deploy: privateProcedure
.input(
z.object({
id: z.string()
})
)
.mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const buildId = await deployApplication(id, teamId);
return {
buildId
};
}),
forceRedeploy: privateProcedure
.input(
z.object({
id: z.string()
})
)
.mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const buildId = await deployApplication(id, teamId, true);
return {
buildId
};
}),
delete: privateProcedure
.input(z.object({ force: z.boolean(), id: z.string() }))
.mutation(async ({ ctx, input }) => {
const { id, force } = input;
const teamId = ctx.user?.teamId;
const application = await prisma.application.findUnique({
where: { id },
include: { destinationDocker: true }
});
if (!force && application?.destinationDockerId && 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 .}}'`
});
if (containers) {
const containersArray = containers.trim().split('\n');
for (const container of containersArray) {
const containerObj = JSON.parse(container);
const id = containerObj.ID;
await removeContainer({ id, dockerId: application.destinationDocker.id });
}
}
}
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
await prisma.build.deleteMany({ where: { applicationId: id } });
await prisma.secret.deleteMany({ where: { applicationId: id } });
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } });
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } });
if (teamId === '0') {
await prisma.application.deleteMany({ where: { id } });
} else {
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
}
return {};
})
});

View File

@@ -1,84 +0,0 @@
import { z } from 'zod';
import { privateProcedure, router } from '../trpc';
import { decrypt } from '../../lib/common';
import { prisma } from '../../prisma';
import { executeCommand } from '../../lib/executeCommand';
import { stopDatabaseContainer, stopTcpHttpProxy } from '../../lib/docker';
export const databasesRouter = router({
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
const id = input.id;
const teamId = ctx.user?.teamId;
let isRunning = false;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database) {
const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) {
try {
const { stdout } = await executeCommand({
dockerId: destinationDocker.id,
command: `docker inspect --format '{{json .State}}' ${id}`
});
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
}
return {
isRunning
};
}),
cleanup: privateProcedure.query(async ({ ctx }) => {
const teamId = ctx.user?.teamId;
let databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
for (const database of databases) {
if (!database?.version) {
const { id } = database;
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 } });
await prisma.database.delete({ where: { id } });
}
}
return {};
}),
delete: privateProcedure
.input(z.object({ id: z.string(), force: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const { id, force } = input;
const teamId = ctx.user?.teamId;
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);
}
}
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
return {};
})
});

View File

@@ -1,171 +0,0 @@
import { z } from 'zod';
import { privateProcedure, router } from '../trpc';
import { decrypt, getTemplates, removeService } from '../../lib/common';
import { prisma } from '../../prisma';
import { executeCommand } from '../../lib/executeCommand';
export const servicesRouter = router({
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
const id = input.id;
const teamId = ctx.user?.teamId;
if (!teamId) {
throw { status: 400, message: 'Team not found.' };
}
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId } = service;
let payload = {};
if (destinationDockerId) {
const { stdout: containers } = await executeCommand({
dockerId: service.destinationDocker.id,
command: `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
});
if (containers) {
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0 && containersArray[0] !== '') {
const templates = await getTemplates();
let template = templates.find((t: { type: string }) => t.type === service.type);
const templateStr = JSON.stringify(template);
if (templateStr) {
template = JSON.parse(templateStr.replaceAll('$$id', service.id));
}
for (const container of containersArray) {
let isRunning = false;
let isExited = false;
let isRestarting = false;
let isExcluded = false;
const containerObj = JSON.parse(container);
const exclude = template?.services[containerObj.Names]?.exclude;
if (exclude) {
payload[containerObj.Names] = {
status: {
isExcluded: true,
isRunning: false,
isExited: false,
isRestarting: false
}
};
continue;
}
const status = containerObj.State;
if (status === 'running') {
isRunning = true;
}
if (status === 'exited') {
isExited = true;
}
if (status === 'restarting') {
isRestarting = true;
}
payload[containerObj.Names] = {
status: {
isExcluded,
isRunning,
isExited,
isRestarting
}
};
}
}
}
}
return payload;
}),
cleanup: privateProcedure.query(async ({ ctx }) => {
const teamId = ctx.user?.teamId;
let services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, teams: true }
});
for (const service of services) {
if (!service.fqdn) {
if (service.destinationDockerId) {
const { stdout: containers } = await executeCommand({
dockerId: service.destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}`
});
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({
dockerId: service.destinationDockerId,
command: `docker stop -t 0 ${container}`
});
await executeCommand({
dockerId: service.destinationDockerId,
command: `docker rm --force ${container}`
});
}
}
}
}
await removeService({ id: service.id });
}
}
}),
delete: privateProcedure
.input(z.object({ force: z.boolean(), id: z.string() }))
.mutation(async ({ input }) => {
// todo: check if user is allowed to delete service
const { id } = input;
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.serviceSetting.deleteMany({ where: { serviceId: id } });
await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } });
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
await prisma.fider.deleteMany({ where: { serviceId: id } });
await prisma.ghost.deleteMany({ where: { serviceId: id } });
await prisma.umami.deleteMany({ where: { serviceId: id } });
await prisma.hasura.deleteMany({ where: { serviceId: id } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.glitchTip.deleteMany({ where: { serviceId: id } });
await prisma.moodle.deleteMany({ where: { serviceId: id } });
await prisma.appwrite.deleteMany({ where: { serviceId: id } });
await prisma.searxng.deleteMany({ where: { serviceId: id } });
await prisma.weblate.deleteMany({ where: { serviceId: id } });
await prisma.taiga.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } });
return {};
})
});
export async function getServiceFromDB({
id,
teamId
}: {
id: string;
teamId: string;
}): Promise<any> {
const settings = await prisma.setting.findFirst();
const body = await prisma.service.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: {
destinationDocker: true,
persistentStorage: true,
serviceSecret: true,
serviceSetting: true,
wordpress: true,
plausibleAnalytics: true
}
});
if (!body) {
return null;
}
// body.type = fixType(body.type);
if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
if (body.wordpress) {
body.wordpress.ftpPassword = decrypt(body.wordpress.ftpPassword);
}
return { ...body, settings };
}

View File

@@ -42,9 +42,13 @@
"@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"
"superjson": "1.11.0",
"svelte-select": "4.4.7"
}
}

View File

@@ -7,3 +7,6 @@ declare namespace App {
// interface Error {}
// interface Platform {}
}
declare const GITPOD_WORKSPACE_URL: string;
declare const CODESANDBOX_HOST: string;

View File

@@ -0,0 +1,210 @@
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
};

View File

@@ -0,0 +1 @@
<span class="badge bg-coollabs-gradient rounded text-white font-normal"> BETA </span>

View File

@@ -0,0 +1,156 @@
<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>

View File

@@ -0,0 +1,44 @@
<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}

View File

@@ -0,0 +1,38 @@
<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>

View File

@@ -0,0 +1,10 @@
<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>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1,87 @@
<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}

View File

@@ -0,0 +1,6 @@
<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>

View File

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 486 B

View File

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

Some files were not shown because too many files have changed in this diff Show More