mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-01 12:33:45 +00:00
Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6311627899 | ||
|
|
37cea5fb61 | ||
|
|
655a8cd60d | ||
|
|
4c8babc96a | ||
|
|
612bacebed | ||
|
|
ade7c8566d | ||
|
|
19553ce5c8 | ||
|
|
18ed2527e8 | ||
|
|
b0652bc884 | ||
|
|
15c9ad23fe | ||
|
|
578bb12562 | ||
|
|
f82cfda07f | ||
|
|
9e52b2788d | ||
|
|
2e56a113d9 | ||
|
|
4722d777e6 | ||
|
|
2141d54ae0 | ||
|
|
e346225136 | ||
|
|
012d4dae56 | ||
|
|
b4d9fe70af | ||
|
|
85e83b5441 | ||
|
|
6b2a453b8f | ||
|
|
27021538d8 | ||
|
|
8b57a2b055 | ||
|
|
75dd894685 | ||
|
|
9101ef8774 | ||
|
|
5932540630 | ||
|
|
ec376b2e47 | ||
|
|
a176562ad0 | ||
|
|
becf37b676 | ||
|
|
9b5efab8f8 | ||
|
|
e98a8ba599 | ||
|
|
7ddac50008 | ||
|
|
9837ae359f | ||
|
|
710a829dcb | ||
|
|
ccd84fa454 | ||
|
|
335b36d3a9 | ||
|
|
2be30fae00 | ||
|
|
db5cd21884 | ||
|
|
bfd3020031 | ||
|
|
344c36997a | ||
|
|
dfd9272b70 | ||
|
|
359f4520f5 | ||
|
|
aecf014f4e | ||
|
|
d2a89ddf84 | ||
|
|
c01fe153ae | ||
|
|
4f4a838799 | ||
|
|
ac6f2567eb | ||
|
|
05a5816ac6 | ||
|
|
9c8f6e9195 | ||
|
|
2fd001f6d2 | ||
|
|
d641d32413 | ||
|
|
18064ef6a2 | ||
|
|
5cb9216add | ||
|
|
91c36dc810 | ||
|
|
6efb02fa32 | ||
|
|
97313e4180 | ||
|
|
568ab24fd9 | ||
|
|
5a745efcd3 | ||
|
|
c651570e62 | ||
|
|
8980598085 | ||
|
|
c07c742feb | ||
|
|
1053abb9a9 | ||
|
|
2c9e57cbb1 | ||
|
|
c6eaa2c8a6 | ||
|
|
5ab5e913ee | ||
|
|
cea53ca476 | ||
|
|
58af09114b | ||
|
|
c4c0417e2d | ||
|
|
74f90e6947 | ||
|
|
ad5c339780 | ||
|
|
305823db00 | ||
|
|
baf58b298f | ||
|
|
c37367d018 | ||
|
|
1c98796e64 | ||
|
|
e686d9a6ea | ||
|
|
a1936b9d59 | ||
|
|
834f9c9337 | ||
|
|
615f8cfd3b | ||
|
|
8ed134105f | ||
|
|
5d6169b270 | ||
|
|
e83de8b938 | ||
|
|
ee55e039b2 | ||
|
|
086dd89144 | ||
|
|
68e5d4dd2c | ||
|
|
55a35c6bec | ||
|
|
d09b4885fe | ||
|
|
a46773e6d8 | ||
|
|
a422d0220c | ||
|
|
e5eba8430a | ||
|
|
3d235dc316 | ||
|
|
80d3b4be8c | ||
|
|
fe8b7480df | ||
|
|
cebfc3aaa0 | ||
|
|
f778b5a12d | ||
|
|
2244050160 | ||
|
|
9284e42b62 | ||
|
|
ee40120496 | ||
|
|
30cd2149ea | ||
|
|
395df36d57 | ||
|
|
79597ea0e5 | ||
|
|
283f39270a | ||
|
|
7d892bb19d | ||
|
|
a025f124f3 | ||
|
|
84f7287bf8 | ||
|
|
a58544b502 | ||
|
|
4d26175ebe | ||
|
|
78f0e6ff6b | ||
|
|
3af97af634 | ||
|
|
2c2663c8a4 | ||
|
|
1122b8a2f7 | ||
|
|
5b9f38948b | ||
|
|
507eb3b424 | ||
|
|
56fbc0ed6c | ||
|
|
7aaad314e3 | ||
|
|
356949dd54 | ||
|
|
9878baca53 | ||
|
|
9cbc7c2939 | ||
|
|
4680b63911 | ||
|
|
ce4a2d95f2 | ||
|
|
b2e048de8d | ||
|
|
d25a9d7515 | ||
|
|
dc130d3705 | ||
|
|
2391850218 | ||
|
|
c8f7ca920e | ||
|
|
e3e39af6fb | ||
|
|
f38114f5a5 | ||
|
|
1ee9d041df | ||
|
|
9c6f412f04 | ||
|
|
4fa0f2d04a | ||
|
|
e566a66ea4 | ||
|
|
58a42abc67 | ||
|
|
5676bd9d0d | ||
|
|
9691010e7b | ||
|
|
d19be3ad52 | ||
|
|
ec3cbf788b | ||
|
|
1282fd0b76 | ||
|
|
93430e5607 | ||
|
|
14201f4052 | ||
|
|
47979bf16d | ||
|
|
29530f3b17 | ||
|
|
af548e6ef8 | ||
|
|
ed24a9c990 | ||
|
|
0d51b04d79 | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 |
14
.github/workflows/pocketbase-release.yml
vendored
14
.github/workflows/pocketbase-release.yml
vendored
@@ -3,11 +3,11 @@ name: pocketbase-release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- "others/pocketbase"
|
- "others/pocketbase/*"
|
||||||
- ".github/workflows/pocketbase-release.yml"
|
- ".github/workflows/pocketbase-release.yml"
|
||||||
branches:
|
branches:
|
||||||
- next
|
- next
|
||||||
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
arm64:
|
arm64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
context: others/pocketbase/
|
context: others/pocketbase/
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/pocketbase:0.8.0-arm64
|
tags: coollabsio/pocketbase:0.11.0-arm64
|
||||||
amd64:
|
amd64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
context: others/pocketbase/
|
context: others/pocketbase/
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/pocketbase:0.8.0-amd64
|
tags: coollabsio/pocketbase:0.11.0-amd64
|
||||||
aarch64:
|
aarch64:
|
||||||
runs-on: [self-hosted, arm64]
|
runs-on: [self-hosted, arm64]
|
||||||
steps:
|
steps:
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
context: others/pocketbase/
|
context: others/pocketbase/
|
||||||
platforms: linux/aarch64
|
platforms: linux/aarch64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/pocketbase:0.8.0-aarch64
|
tags: coollabsio/pocketbase:0.11.0-aarch64
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [amd64, arm64, aarch64]
|
needs: [amd64, arm64, aarch64]
|
||||||
@@ -89,5 +89,5 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
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 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.8.0
|
docker manifest push coollabsio/pocketbase:0.11.0
|
||||||
|
|||||||
8
.github/workflows/production-release.yml
vendored
8
.github/workflows/production-release.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
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-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||||
aarch64:
|
aarch64:
|
||||||
@@ -103,8 +103,10 @@ jobs:
|
|||||||
id: package-version
|
id: package-version
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
docker buildx imagetools create --append 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 manifest push 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
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
5
.github/workflows/staging-release.yml
vendored
5
.github/workflows/staging-release.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: coollabsio/coolify:next-amd64
|
tags: coollabsio/coolify:next
|
||||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||||
merge-manifest:
|
merge-manifest:
|
||||||
@@ -85,8 +85,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Create & publish manifest
|
- name: Create & publish manifest
|
||||||
run: |
|
run: |
|
||||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
docker buildx imagetools create --append coollabsio/coolify:next-arm64 --tag coollabsio/coolify:next
|
||||||
docker manifest push coollabsio/coolify:next
|
|
||||||
- uses: sarisia/actions-status-discord@v1
|
- uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
build
|
/apps/ui/build
|
||||||
|
/build
|
||||||
.svelte-kit
|
.svelte-kit
|
||||||
package
|
package
|
||||||
.env
|
.env
|
||||||
@@ -11,9 +12,10 @@ dist
|
|||||||
apps/api/db/*.db
|
apps/api/db/*.db
|
||||||
apps/api/db/migration.db-journal
|
apps/api/db/migration.db-journal
|
||||||
apps/api/core*
|
apps/api/core*
|
||||||
|
apps/server/build
|
||||||
apps/backup/backups/*
|
apps/backup/backups/*
|
||||||
!apps/backup/backups/.gitkeep
|
!apps/backup/backups/.gitkeep
|
||||||
logs
|
/logs
|
||||||
others/certificates
|
others/certificates
|
||||||
backups/*
|
backups/*
|
||||||
!backups/.gitkeep
|
!backups/.gitkeep
|
||||||
|
|||||||
2
apps/api/.gitignore
vendored
2
apps/api/.gitignore
vendored
@@ -9,3 +9,5 @@ package
|
|||||||
dist
|
dist
|
||||||
dev.db
|
dev.db
|
||||||
client
|
client
|
||||||
|
testTemplate.yaml
|
||||||
|
testTags.json
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,229 @@
|
|||||||
- templateVersion: 1.0.0
|
- 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/
|
documentation: https://pocketbase.io/docs/
|
||||||
type: pocketbase
|
type: pocketbase
|
||||||
name: Pocketbase
|
name: Pocketbase
|
||||||
@@ -124,12 +348,12 @@
|
|||||||
description: ""
|
description: ""
|
||||||
- id: $$config_disable_auth
|
- id: $$config_disable_auth
|
||||||
name: DISABLE_AUTH
|
name: DISABLE_AUTH
|
||||||
label: Disable Authentication
|
label: Authentication
|
||||||
defaultValue: "false"
|
defaultValue: "false"
|
||||||
description: ""
|
description: ""
|
||||||
- id: $$config_disable_registration
|
- id: $$config_disable_registration
|
||||||
name: DISABLE_REGISTRATION
|
name: DISABLE_REGISTRATION
|
||||||
label: Disable Registration
|
label: Registration
|
||||||
defaultValue: "true"
|
defaultValue: "true"
|
||||||
description: ""
|
description: ""
|
||||||
- id: $$config_postgres_user
|
- id: $$config_postgres_user
|
||||||
@@ -157,7 +381,7 @@
|
|||||||
defaultValue: plausible.js
|
defaultValue: plausible.js
|
||||||
description: This is the default script name.
|
description: This is the default script name.
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: "1.17"
|
defaultVersion: "1.18"
|
||||||
documentation: https://docs.gitea.io
|
documentation: https://docs.gitea.io
|
||||||
type: gitea
|
type: gitea
|
||||||
name: Gitea
|
name: Gitea
|
||||||
@@ -332,12 +556,12 @@
|
|||||||
volumes:
|
volumes:
|
||||||
- $$id-lavalink:/lavalink
|
- $$id-lavalink:/lavalink
|
||||||
ports:
|
ports:
|
||||||
- "2333"
|
- $$config_port
|
||||||
files:
|
files:
|
||||||
- location: /opt/Lavalink/application.yml
|
- location: /opt/Lavalink/application.yml
|
||||||
content: >-
|
content: >-
|
||||||
server:
|
server:
|
||||||
port: $$config_port
|
port: 2333
|
||||||
address: 0.0.0.0
|
address: 0.0.0.0
|
||||||
lavalink:
|
lavalink:
|
||||||
server:
|
server:
|
||||||
@@ -364,18 +588,13 @@
|
|||||||
max-file-size: 1GB
|
max-file-size: 1GB
|
||||||
max-history: 30
|
max-history: 30
|
||||||
variables:
|
variables:
|
||||||
- id: $$config_port
|
|
||||||
name: PORT
|
|
||||||
label: Port
|
|
||||||
defaultValue: "2333"
|
|
||||||
required: true
|
|
||||||
- id: $$secret_password
|
- id: $$secret_password
|
||||||
name: PASSWORD
|
name: PASSWORD
|
||||||
label: Password
|
label: Password
|
||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
required: true
|
required: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v1.8.9
|
defaultVersion: v1.9.3
|
||||||
documentation: https://docs.appsmith.com/getting-started/setup/instance-configuration/
|
documentation: https://docs.appsmith.com/getting-started/setup/instance-configuration/
|
||||||
type: appsmith
|
type: appsmith
|
||||||
name: Appsmith
|
name: Appsmith
|
||||||
@@ -408,7 +627,7 @@
|
|||||||
defaultValue: "true"
|
defaultValue: "true"
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.57.4
|
defaultVersion: 0.58.4
|
||||||
documentation: https://hub.docker.com/r/zadam/trilium
|
documentation: https://hub.docker.com/r/zadam/trilium
|
||||||
description: "A hierarchical note taking application with focus on building large personal knowledge bases."
|
description: "A hierarchical note taking application with focus on building large personal knowledge bases."
|
||||||
labels:
|
labels:
|
||||||
@@ -428,7 +647,7 @@
|
|||||||
- "8080"
|
- "8080"
|
||||||
variables: []
|
variables: []
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 1.18.5
|
defaultVersion: 1.19.4
|
||||||
documentation: https://hub.docker.com/r/louislam/uptime-kuma
|
documentation: https://hub.docker.com/r/louislam/uptime-kuma
|
||||||
description: A free & fancy self-hosted monitoring tool.
|
description: A free & fancy self-hosted monitoring tool.
|
||||||
labels:
|
labels:
|
||||||
@@ -445,7 +664,7 @@
|
|||||||
- "3001"
|
- "3001"
|
||||||
variables: []
|
variables: []
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: "5.8"
|
defaultVersion: "6.0"
|
||||||
documentation: https://hub.docker.com/r/silviof/docker-languagetool
|
documentation: https://hub.docker.com/r/silviof/docker-languagetool
|
||||||
description: "A multilingual grammar, style and spell checker."
|
description: "A multilingual grammar, style and spell checker."
|
||||||
type: languagetool
|
type: languagetool
|
||||||
@@ -460,7 +679,7 @@
|
|||||||
- "8010"
|
- "8010"
|
||||||
variables: []
|
variables: []
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 1.26.0
|
defaultVersion: 1.27.0
|
||||||
documentation: https://hub.docker.com/r/vaultwarden/server
|
documentation: https://hub.docker.com/r/vaultwarden/server
|
||||||
description: "Bitwarden compatible server written in Rust."
|
description: "Bitwarden compatible server written in Rust."
|
||||||
type: vaultwarden
|
type: vaultwarden
|
||||||
@@ -478,7 +697,7 @@
|
|||||||
- "80"
|
- "80"
|
||||||
variables: []
|
variables: []
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 9.3.1
|
defaultVersion: 9.3.2
|
||||||
documentation: https://hub.docker.com/r/grafana/grafana
|
documentation: https://hub.docker.com/r/grafana/grafana
|
||||||
type: grafana
|
type: grafana
|
||||||
name: Grafana
|
name: Grafana
|
||||||
@@ -499,7 +718,7 @@
|
|||||||
- "3000"
|
- "3000"
|
||||||
variables: []
|
variables: []
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 1.1.2
|
defaultVersion: 1.2.0
|
||||||
documentation: https://appwrite.io/docs
|
documentation: https://appwrite.io/docs
|
||||||
type: appwrite
|
type: appwrite
|
||||||
name: Appwrite
|
name: Appwrite
|
||||||
@@ -1669,7 +1888,7 @@
|
|||||||
defaultValue: weblate
|
defaultValue: weblate
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 2022.12.12-966e9c3c
|
defaultVersion: 2023.01.15-52d41559
|
||||||
documentation: https://docs.searxng.org/
|
documentation: https://docs.searxng.org/
|
||||||
type: searxng
|
type: searxng
|
||||||
name: SearXNG
|
name: SearXNG
|
||||||
@@ -1742,7 +1961,7 @@
|
|||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v3.0.0
|
defaultVersion: v3.0.2
|
||||||
documentation: https://glitchtip.com/documentation
|
documentation: https://glitchtip.com/documentation
|
||||||
type: glitchtip
|
type: glitchtip
|
||||||
name: GlitchTip
|
name: GlitchTip
|
||||||
@@ -1964,7 +2183,7 @@
|
|||||||
defaultValue: glitchtip
|
defaultValue: glitchtip
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v2.16.0
|
defaultVersion: v2.16.1
|
||||||
documentation: https://hasura.io/docs/latest/index/
|
documentation: https://hasura.io/docs/latest/index/
|
||||||
type: hasura
|
type: hasura
|
||||||
name: Hasura
|
name: Hasura
|
||||||
@@ -2444,7 +2663,7 @@
|
|||||||
description: ""
|
description: ""
|
||||||
showOnConfiguration: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: v0.30.1
|
defaultVersion: v0.30.5
|
||||||
documentation: https://docs.meilisearch.com/learn/getting_started/quick_start.html
|
documentation: https://docs.meilisearch.com/learn/getting_started/quick_start.html
|
||||||
type: meilisearch
|
type: meilisearch
|
||||||
name: MeiliSearch
|
name: MeiliSearch
|
||||||
@@ -2474,7 +2693,7 @@
|
|||||||
showOnConfiguration: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
ignore: true
|
ignore: true
|
||||||
defaultVersion: latest
|
defaultVersion: 5.30.0
|
||||||
documentation: https://docs.ghost.org
|
documentation: https://docs.ghost.org
|
||||||
arch: amd64
|
arch: amd64
|
||||||
type: ghost-mariadb
|
type: ghost-mariadb
|
||||||
@@ -2592,7 +2811,7 @@
|
|||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: "5.25.3"
|
defaultVersion: 5.30.0
|
||||||
documentation: https://docs.ghost.org
|
documentation: https://docs.ghost.org
|
||||||
type: ghost-only
|
type: ghost-only
|
||||||
name: Ghost
|
name: Ghost
|
||||||
@@ -2656,7 +2875,7 @@
|
|||||||
placeholder: "ghost_db"
|
placeholder: "ghost_db"
|
||||||
required: true
|
required: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: "5.25.3"
|
defaultVersion: 5.30.0
|
||||||
documentation: https://docs.ghost.org
|
documentation: https://docs.ghost.org
|
||||||
type: ghost-mysql
|
type: ghost-mysql
|
||||||
name: Ghost
|
name: Ghost
|
||||||
@@ -2733,7 +2952,7 @@
|
|||||||
defaultValue: $$generate_password
|
defaultValue: $$generate_password
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: php8.1
|
defaultVersion: php8.2
|
||||||
documentation: https://wordpress.org/
|
documentation: https://wordpress.org/
|
||||||
type: wordpress
|
type: wordpress
|
||||||
name: WordPress
|
name: WordPress
|
||||||
@@ -2823,7 +3042,7 @@
|
|||||||
description: ""
|
description: ""
|
||||||
readOnly: true
|
readOnly: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: php8.1
|
defaultVersion: php8.2
|
||||||
documentation: https://wordpress.org/
|
documentation: https://wordpress.org/
|
||||||
type: wordpress-only
|
type: wordpress-only
|
||||||
name: WordPress
|
name: WordPress
|
||||||
@@ -2897,7 +3116,7 @@
|
|||||||
define('WP_DEBUG_DISPLAY', false);
|
define('WP_DEBUG_DISPLAY', false);
|
||||||
@ini_set('display_errors', 0);
|
@ini_set('display_errors', 0);
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 4.9.0
|
defaultVersion: 4.9.1
|
||||||
documentation: https://coder.com/docs/coder-oss/latest
|
documentation: https://coder.com/docs/coder-oss/latest
|
||||||
type: vscodeserver
|
type: vscodeserver
|
||||||
name: VSCode Server
|
name: VSCode Server
|
||||||
@@ -2912,7 +3131,6 @@
|
|||||||
depends_on: []
|
depends_on: []
|
||||||
image: "codercom/code-server:$$core_version"
|
image: "codercom/code-server:$$core_version"
|
||||||
volumes:
|
volumes:
|
||||||
- "$$id-config-data:/home/coder/.local/share/code-server"
|
|
||||||
- "$$id-vscodeserver-data:/home/coder"
|
- "$$id-vscodeserver-data:/home/coder"
|
||||||
- "$$id-keys-directory:/root/.ssh"
|
- "$$id-keys-directory:/root/.ssh"
|
||||||
- "$$id-theme-and-plugin-directory:/root/.local/share/code-server"
|
- "$$id-theme-and-plugin-directory:/root/.local/share/code-server"
|
||||||
@@ -2928,7 +3146,7 @@
|
|||||||
description: ""
|
description: ""
|
||||||
showOnConfiguration: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: RELEASE.2022-12-12T19-27-27Z
|
defaultVersion: RELEASE.2023-01-12T02-06-16Z
|
||||||
documentation: https://min.io/docs/minio
|
documentation: https://min.io/docs/minio
|
||||||
type: minio
|
type: minio
|
||||||
name: MinIO
|
name: MinIO
|
||||||
@@ -2987,7 +3205,7 @@
|
|||||||
description: ""
|
description: ""
|
||||||
showOnConfiguration: true
|
showOnConfiguration: true
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.21.1
|
defaultVersion: stable
|
||||||
documentation: https://fider.io/docs
|
documentation: https://fider.io/docs
|
||||||
type: fider
|
type: fider
|
||||||
name: Fider
|
name: Fider
|
||||||
@@ -3106,7 +3324,7 @@
|
|||||||
defaultValue: $$generate_username
|
defaultValue: $$generate_username
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.207.0
|
defaultVersion: 0.210.1
|
||||||
documentation: https://docs.n8n.io
|
documentation: https://docs.n8n.io
|
||||||
type: n8n
|
type: n8n
|
||||||
name: n8n.io
|
name: n8n.io
|
||||||
@@ -3137,7 +3355,7 @@
|
|||||||
defaultValue: $$generate_fqdn
|
defaultValue: $$generate_fqdn
|
||||||
description: ""
|
description: ""
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: stable
|
defaultVersion: v1.5.1
|
||||||
documentation: https://plausible.io/doc/
|
documentation: https://plausible.io/doc/
|
||||||
arch: amd64
|
arch: amd64
|
||||||
type: plausibleanalytics
|
type: plausibleanalytics
|
||||||
@@ -3250,12 +3468,12 @@
|
|||||||
description: ""
|
description: ""
|
||||||
- id: $$config_disable_auth
|
- id: $$config_disable_auth
|
||||||
name: DISABLE_AUTH
|
name: DISABLE_AUTH
|
||||||
label: Disable Authentication
|
label: Authentication
|
||||||
defaultValue: "false"
|
defaultValue: "false"
|
||||||
description: ""
|
description: ""
|
||||||
- id: $$config_disable_registration
|
- id: $$config_disable_registration
|
||||||
name: DISABLE_REGISTRATION
|
name: DISABLE_REGISTRATION
|
||||||
label: Disable Registration
|
label: Registration
|
||||||
defaultValue: "true"
|
defaultValue: "true"
|
||||||
description: ""
|
description: ""
|
||||||
- id: $$config_postgresql_username
|
- id: $$config_postgresql_username
|
||||||
@@ -3283,7 +3501,7 @@
|
|||||||
defaultValue: plausible.js
|
defaultValue: plausible.js
|
||||||
description: This is the default script name.
|
description: This is the default script name.
|
||||||
- templateVersion: 1.0.0
|
- templateVersion: 1.0.0
|
||||||
defaultVersion: 0.99.1
|
defaultVersion: 0.101.2
|
||||||
documentation: https://docs.nocodb.com
|
documentation: https://docs.nocodb.com
|
||||||
type: nocodb
|
type: nocodb
|
||||||
name: NocoDB
|
name: NocoDB
|
||||||
|
|||||||
@@ -16,31 +16,31 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@breejs/ts-worker": "2.0.0",
|
"@breejs/ts-worker": "2.0.0",
|
||||||
"@fastify/autoload": "5.5.0",
|
"@fastify/autoload": "5.7.0",
|
||||||
"@fastify/cookie": "8.3.0",
|
"@fastify/cookie": "8.3.0",
|
||||||
"@fastify/cors": "8.2.0",
|
"@fastify/cors": "8.2.0",
|
||||||
"@fastify/env": "4.1.0",
|
"@fastify/env": "4.2.0",
|
||||||
"@fastify/jwt": "6.3.3",
|
"@fastify/jwt": "6.5.0",
|
||||||
"@fastify/multipart": "7.3.0",
|
"@fastify/multipart": "7.3.0",
|
||||||
"@fastify/static": "6.5.1",
|
"@fastify/static": "6.6.0",
|
||||||
"@iarna/toml": "2.2.5",
|
"@iarna/toml": "2.2.5",
|
||||||
"@ladjs/graceful": "3.0.2",
|
"@ladjs/graceful": "3.2.1",
|
||||||
"@prisma/client": "4.6.1",
|
"@prisma/client": "4.8.1",
|
||||||
"@sentry/node": "7.21.1",
|
"@sentry/node": "7.30.0",
|
||||||
"@sentry/tracing": "7.21.1",
|
"@sentry/tracing": "7.30.0",
|
||||||
"axe": "11.0.0",
|
"axe": "11.2.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bree": "9.1.2",
|
"bree": "9.1.3",
|
||||||
"cabin": "11.0.1",
|
"cabin": "11.1.1",
|
||||||
"compare-versions": "5.0.1",
|
"compare-versions": "5.0.1",
|
||||||
"csv-parse": "5.3.2",
|
"csv-parse": "5.3.3",
|
||||||
"csvtojson": "2.0.10",
|
"csvtojson": "2.0.10",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"dayjs": "1.11.6",
|
"dayjs": "1.11.7",
|
||||||
"dockerode": "3.3.4",
|
"dockerode": "3.3.4",
|
||||||
"dotenv-extended": "2.9.0",
|
"dotenv-extended": "2.9.0",
|
||||||
"execa": "6.1.0",
|
"execa": "6.1.0",
|
||||||
"fastify": "4.10.2",
|
"fastify": "4.11.0",
|
||||||
"fastify-plugin": "4.3.0",
|
"fastify-plugin": "4.3.0",
|
||||||
"fastify-socket.io": "4.0.0",
|
"fastify-socket.io": "4.0.0",
|
||||||
"generate-password": "1.7.0",
|
"generate-password": "1.7.0",
|
||||||
@@ -48,36 +48,36 @@
|
|||||||
"is-ip": "5.0.0",
|
"is-ip": "5.0.0",
|
||||||
"is-port-reachable": "4.0.0",
|
"is-port-reachable": "4.0.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "9.0.0",
|
||||||
"minimist": "^1.2.7",
|
"minimist": "^1.2.7",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"node-os-utils": "1.3.7",
|
"node-os-utils": "1.3.7",
|
||||||
"p-all": "4.0.0",
|
"p-all": "4.0.0",
|
||||||
"p-throttle": "5.0.0",
|
"p-throttle": "5.0.0",
|
||||||
"prisma": "4.6.1",
|
"prisma": "4.8.1",
|
||||||
"public-ip": "6.0.1",
|
"public-ip": "6.0.1",
|
||||||
"pump": "3.0.0",
|
"pump": "3.0.0",
|
||||||
"shell-quote": "^1.7.4",
|
"shell-quote": "^1.7.4",
|
||||||
"socket.io": "4.5.3",
|
"socket.io": "4.5.4",
|
||||||
"ssh-config": "4.1.6",
|
"ssh-config": "4.2.0",
|
||||||
"strip-ansi": "7.0.1",
|
"strip-ansi": "7.0.1",
|
||||||
"unique-names-generator": "4.7.1"
|
"unique-names-generator": "4.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.11.9",
|
"@types/node": "18.11.18",
|
||||||
"@types/node-os-utils": "1.3.0",
|
"@types/node-os-utils": "1.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||||
"@typescript-eslint/parser": "5.44.0",
|
"@typescript-eslint/parser": "5.48.1",
|
||||||
"esbuild": "0.15.15",
|
"esbuild": "0.16.16",
|
||||||
"eslint": "8.28.0",
|
"eslint": "8.31.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.6.0",
|
||||||
"eslint-plugin-prettier": "4.2.1",
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
"nodemon": "2.0.20",
|
"nodemon": "2.0.20",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.8.2",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"tsconfig-paths": "4.1.0",
|
"tsconfig-paths": "4.1.2",
|
||||||
"types-fastify-socket.io": "0.0.1",
|
"types-fastify-socket.io": "0.0.1",
|
||||||
"typescript": "4.9.3"
|
"typescript": "4.9.4"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "node prisma/seed.js"
|
"seed": "node prisma/seed.js"
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_GitSource" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"type" TEXT,
|
||||||
|
"apiUrl" TEXT,
|
||||||
|
"htmlUrl" TEXT,
|
||||||
|
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||||
|
"customUser" TEXT NOT NULL DEFAULT 'git',
|
||||||
|
"organization" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"githubAppId" TEXT,
|
||||||
|
"gitlabAppId" TEXT,
|
||||||
|
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||||
|
DROP TABLE "GitSource";
|
||||||
|
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||||
|
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||||
|
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_ApplicationSettings" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"debug" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"previews" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"autodeploy" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"isBot" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isPublicRepository" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isDBBranching" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isHttp2" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
|
||||||
|
DROP TABLE "ApplicationSettings";
|
||||||
|
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
|
||||||
|
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
@@ -186,6 +186,7 @@ model ApplicationSettings {
|
|||||||
isPublicRepository Boolean @default(false)
|
isPublicRepository Boolean @default(false)
|
||||||
isDBBranching Boolean @default(false)
|
isDBBranching Boolean @default(false)
|
||||||
isCustomSSL Boolean @default(false)
|
isCustomSSL Boolean @default(false)
|
||||||
|
isHttp2 Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
@@ -325,6 +326,7 @@ model GitSource {
|
|||||||
apiUrl String?
|
apiUrl String?
|
||||||
htmlUrl String?
|
htmlUrl String?
|
||||||
customPort Int @default(22)
|
customPort Int @default(22)
|
||||||
|
customUser String @default("git")
|
||||||
organization String?
|
organization String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@@ -6,14 +6,27 @@ import cookie from '@fastify/cookie';
|
|||||||
import multipart from '@fastify/multipart';
|
import multipart from '@fastify/multipart';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import autoLoad from '@fastify/autoload';
|
import autoLoad from '@fastify/autoload';
|
||||||
import socketIO from 'fastify-socket.io'
|
import socketIO from 'fastify-socket.io';
|
||||||
import socketIOServer from './realtime'
|
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 { scheduler } from './lib/scheduler';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import Graceful from '@ladjs/graceful'
|
import Graceful from '@ladjs/graceful';
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||||
import { checkContainer } from './lib/docker';
|
import { checkContainer } from './lib/docker';
|
||||||
@@ -23,13 +36,13 @@ import * as Sentry from '@sentry/node';
|
|||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
config: {
|
config: {
|
||||||
COOLIFY_APP_ID: string,
|
COOLIFY_APP_ID: string;
|
||||||
COOLIFY_SECRET_KEY: string,
|
COOLIFY_SECRET_KEY: string;
|
||||||
COOLIFY_DATABASE_URL: string,
|
COOLIFY_DATABASE_URL: string;
|
||||||
COOLIFY_IS_ON: string,
|
COOLIFY_IS_ON: string;
|
||||||
COOLIFY_WHITE_LABELED: string,
|
COOLIFY_WHITE_LABELED: string;
|
||||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
COOLIFY_WHITE_LABELED_ICON: string | null;
|
||||||
COOLIFY_AUTO_UPDATE: string,
|
COOLIFY_AUTO_UPDATE: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +51,7 @@ const port = isDev ? 3001 : 3000;
|
|||||||
const host = '0.0.0.0';
|
const host = '0.0.0.0';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const settings = await prisma.setting.findFirst()
|
const settings = await prisma.setting.findFirst();
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
logger: settings?.isAPIDebuggingEnabled || false,
|
logger: settings?.isAPIDebuggingEnabled || false,
|
||||||
trustProxy: true
|
trustProxy: true
|
||||||
@@ -49,10 +62,10 @@ const host = '0.0.0.0';
|
|||||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||||
properties: {
|
properties: {
|
||||||
COOLIFY_APP_ID: {
|
COOLIFY_APP_ID: {
|
||||||
type: 'string',
|
type: 'string'
|
||||||
},
|
},
|
||||||
COOLIFY_SECRET_KEY: {
|
COOLIFY_SECRET_KEY: {
|
||||||
type: 'string',
|
type: 'string'
|
||||||
},
|
},
|
||||||
COOLIFY_DATABASE_URL: {
|
COOLIFY_DATABASE_URL: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -73,8 +86,7 @@ const host = '0.0.0.0';
|
|||||||
COOLIFY_AUTO_UPDATE: {
|
COOLIFY_AUTO_UPDATE: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'false'
|
default: 'false'
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const options = {
|
const options = {
|
||||||
@@ -103,13 +115,13 @@ const host = '0.0.0.0';
|
|||||||
fastify.register(autoLoad, {
|
fastify.register(autoLoad, {
|
||||||
dir: join(__dirname, 'routes')
|
dir: join(__dirname, 'routes')
|
||||||
});
|
});
|
||||||
fastify.register(cookie)
|
fastify.register(cookie);
|
||||||
fastify.register(cors);
|
fastify.register(cors);
|
||||||
fastify.register(socketIO, {
|
fastify.register(socketIO, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: isDev ? "*" : ''
|
origin: isDev ? '*' : ''
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// To detect allowed origins
|
// To detect allowed origins
|
||||||
// fastify.addHook('onRequest', async (request, reply) => {
|
// fastify.addHook('onRequest', async (request, reply) => {
|
||||||
// console.log(request.headers.host)
|
// console.log(request.headers.host)
|
||||||
@@ -131,10 +143,9 @@ const host = '0.0.0.0';
|
|||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fastify.listen({ port, host })
|
await fastify.listen({ port, host });
|
||||||
await socketIOServer(fastify)
|
await socketIOServer(fastify);
|
||||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||||
|
|
||||||
migrateServicesToNewTemplate();
|
migrateServicesToNewTemplate();
|
||||||
@@ -148,105 +159,125 @@ const host = '0.0.0.0';
|
|||||||
if (!scheduler.workers.has('deployApplication')) {
|
if (!scheduler.workers.has('deployApplication')) {
|
||||||
scheduler.run('deployApplication');
|
scheduler.run('deployApplication');
|
||||||
}
|
}
|
||||||
}, 2000)
|
}, 2000);
|
||||||
|
|
||||||
// autoUpdater
|
// autoUpdater
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await autoUpdater()
|
await autoUpdater();
|
||||||
}, 60000 * 15)
|
}, 60000 * 15);
|
||||||
|
|
||||||
// cleanupStorage
|
// cleanupStorage
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await cleanupStorage()
|
await cleanupStorage();
|
||||||
}, 60000 * 10)
|
}, 60000 * 15);
|
||||||
|
|
||||||
|
// Cleanup stucked containers (not defined in Coolify, but still running and managed by Coolify)
|
||||||
|
setInterval(async () => {
|
||||||
|
await cleanupStuckedContainers();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
// checkProxies, checkFluentBit & refresh templates
|
// checkProxies, checkFluentBit & refresh templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await checkProxies();
|
await checkProxies();
|
||||||
await checkFluentBit();
|
await checkFluentBit();
|
||||||
}, 60000)
|
}, 60000);
|
||||||
|
|
||||||
// Refresh and check templates
|
// Refresh and check templates
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTemplates()
|
await refreshTemplates();
|
||||||
}, 60000)
|
}, 60000);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await refreshTags()
|
await refreshTags();
|
||||||
}, 60000)
|
}, 60000);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(
|
||||||
await migrateServicesToNewTemplate()
|
async () => {
|
||||||
}, isDev ? 10000 : 60000)
|
await migrateServicesToNewTemplate();
|
||||||
|
},
|
||||||
|
isDev ? 10000 : 60000
|
||||||
|
);
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await copySSLCertificates();
|
await copySSLCertificates();
|
||||||
}, 10000)
|
}, 10000);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
getTagsTemplates(),
|
getTagsTemplates(),
|
||||||
getArch(),
|
getArch(),
|
||||||
getIPAddress(),
|
getIPAddress(),
|
||||||
configureRemoteDockers(),
|
configureRemoteDockers()
|
||||||
])
|
// cleanupStuckedContainers()
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
async function getIPAddress() {
|
async function getIPAddress() {
|
||||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
const { publicIpv4, publicIpv6 } = await import('public-ip');
|
||||||
try {
|
try {
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
if (!settings.ipv4) {
|
if (!settings.ipv4) {
|
||||||
const ipv4 = await publicIpv4({ timeout: 2000 })
|
const ipv4 = await publicIpv4({ timeout: 2000 });
|
||||||
console.log(`Getting public IPv4 address...`);
|
console.log(`Getting public IPv4 address...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.ipv6) {
|
if (!settings.ipv6) {
|
||||||
const ipv6 = await publicIpv6({ timeout: 2000 })
|
const ipv6 = await publicIpv6({ timeout: 2000 });
|
||||||
console.log(`Getting public IPv6 address...`);
|
console.log(`Getting public IPv6 address...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
|
||||||
}
|
}
|
||||||
|
} catch (error) {}
|
||||||
} catch (error) { }
|
|
||||||
}
|
}
|
||||||
async function getTagsTemplates() {
|
async function getTagsTemplates() {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
|
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
|
try {
|
||||||
await fs.writeFile('./tags.json', tags)
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
console.log('[004] Tags and templates loaded in dev mode...')
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
} else {
|
}
|
||||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
} catch (error) {}
|
||||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
try {
|
||||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
if (await fs.stat('./testTags.json')) {
|
||||||
await fs.writeFile('/app/tags.json', tags)
|
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||||
console.log('[004] Tags and templates loaded...')
|
if (testTags.length > 0) {
|
||||||
}
|
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
||||||
|
await fs.writeFile('./tags.json', tags);
|
||||||
|
console.log('[004] Tags and templates loaded in dev mode...');
|
||||||
|
} else {
|
||||||
|
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||||
|
const response = await got
|
||||||
|
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||||
|
.text();
|
||||||
|
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||||
|
await fs.writeFile('/app/tags.json', tags);
|
||||||
|
console.log('[004] Tags and templates loaded...');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Couldn't get latest templates.")
|
console.log("Couldn't get latest templates.");
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function initServer() {
|
async function initServer() {
|
||||||
const appId = process.env['COOLIFY_APP_ID'];
|
const appId = process.env['COOLIFY_APP_ID'];
|
||||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } })
|
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||||
try {
|
try {
|
||||||
if (settings.doNotTrack === true) {
|
if (settings.doNotTrack === true) {
|
||||||
console.log('[000] Telemetry disabled...')
|
console.log('[000] Telemetry disabled...');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (settings.sentryDSN !== sentryDSN) {
|
if (settings.sentryDSN !== sentryDSN) {
|
||||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } })
|
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
|
||||||
}
|
}
|
||||||
// Initialize Sentry
|
// Initialize Sentry
|
||||||
// Sentry.init({
|
// Sentry.init({
|
||||||
@@ -257,37 +288,83 @@ async function initServer() {
|
|||||||
// console.log('[000] Sentry initialized...')
|
// console.log('[000] Sentry initialized...')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log(`[001] Initializing server...`);
|
console.log(`[001] Initializing server...`);
|
||||||
await executeCommand({ command: `docker network create --attachable coolify` });
|
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
try {
|
try {
|
||||||
console.log(`[002] Cleanup stucked builds...`);
|
console.log(`[002] Cleanup stucked builds...`);
|
||||||
const isOlder = compareVersions('3.8.1', version);
|
const isOlder = compareVersions('3.8.1', version);
|
||||||
if (isOlder === 1) {
|
if (isOlder === 1) {
|
||||||
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
await prisma.build.updateMany({
|
||||||
|
where: { status: { in: ['running', 'queued'] } },
|
||||||
|
data: { status: 'failed' }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
try {
|
try {
|
||||||
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||||
await fs.rm('/tmp/build-sources', { recursive: true, force: true })
|
await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getArch() {
|
async function getArch() {
|
||||||
try {
|
try {
|
||||||
const settings = await prisma.setting.findFirst({})
|
const settings = await prisma.setting.findFirst({});
|
||||||
if (settings && !settings.arch) {
|
if (settings && !settings.arch) {
|
||||||
console.log(`Getting architecture...`);
|
console.log(`Getting architecture...`);
|
||||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } });
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cleanupStuckedContainers() {
|
||||||
|
try {
|
||||||
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
|
let enginesDone = new Set();
|
||||||
|
for (const destination of destinationDockers) {
|
||||||
|
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress))
|
||||||
|
return;
|
||||||
|
if (destination.engine) {
|
||||||
|
enginesDone.add(destination.engine);
|
||||||
|
}
|
||||||
|
if (destination.remoteIpAddress) {
|
||||||
|
if (!destination.remoteVerified) continue;
|
||||||
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
|
}
|
||||||
|
const { stdout: containers } = await executeCommand({
|
||||||
|
dockerId: destination.id,
|
||||||
|
command: `docker container ps -a --filter "label=coolify.managed=true" --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
|
if (containers) {
|
||||||
|
const containersArray = containers.trim().split('\n');
|
||||||
|
if (containersArray.length > 0) {
|
||||||
|
for (const container of containersArray) {
|
||||||
|
const containerId = container.split('-')[0];
|
||||||
|
const application = await prisma.application.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
const service = await prisma.service.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
const database = await prisma.database.findFirst({
|
||||||
|
where: { id: { startsWith: containerId } }
|
||||||
|
});
|
||||||
|
if (!application && !service && !database) {
|
||||||
|
await executeCommand({ command: `docker container rm -f ${container}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
async function configureRemoteDockers() {
|
async function configureRemoteDockers() {
|
||||||
try {
|
try {
|
||||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||||
@@ -296,37 +373,44 @@ async function configureRemoteDockers() {
|
|||||||
if (remoteDocker.length > 0) {
|
if (remoteDocker.length > 0) {
|
||||||
console.log(`Verifying Remote Docker Engines...`);
|
console.log(`Verifying Remote Docker Engines...`);
|
||||||
for (const docker of remoteDocker) {
|
for (const docker of remoteDocker) {
|
||||||
console.log('Verifying:', docker.remoteIpAddress)
|
console.log('Verifying:', docker.remoteIpAddress);
|
||||||
await verifyRemoteDockerEngineFn(docker.id);
|
await verifyRemoteDockerEngineFn(docker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoUpdater() {
|
async function autoUpdater() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
const currentVersion = version;
|
const currentVersion = version;
|
||||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
const { coolify } = await got
|
||||||
searchParams: {
|
.get('https://get.coollabs.io/versions.json', {
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
searchParams: {
|
||||||
version: currentVersion
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
}
|
version: currentVersion
|
||||||
}).json()
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
const latestVersion = coolify.main.version;
|
const latestVersion = coolify.main.version;
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
if (isUpdateAvailable === 1) {
|
if (isUpdateAvailable === 1) {
|
||||||
const activeCount = 0
|
const activeCount = 0;
|
||||||
if (activeCount === 0) {
|
if (activeCount === 0) {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
if (isAutoUpdateEnabled) {
|
if (isAutoUpdateEnabled) {
|
||||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
|
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` })
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
|
await executeCommand({
|
||||||
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` })
|
command: `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 {
|
} else {
|
||||||
console.log('Updating (not really in dev mode).');
|
console.log('Updating (not really in dev mode).');
|
||||||
@@ -334,7 +418,7 @@ async function autoUpdater() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,14 +429,18 @@ async function checkFluentBit() {
|
|||||||
const { id } = await prisma.destinationDocker.findFirst({
|
const { id } = await prisma.destinationDocker.findFirst({
|
||||||
where: { engine, network: 'coolify' }
|
where: { engine, network: 'coolify' }
|
||||||
});
|
});
|
||||||
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
|
const { found } = await checkContainer({
|
||||||
|
dockerId: id,
|
||||||
|
container: 'coolify-fluentbit',
|
||||||
|
remove: true
|
||||||
|
});
|
||||||
if (!found) {
|
if (!found) {
|
||||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||||
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function checkProxies() {
|
async function checkProxies() {
|
||||||
@@ -368,7 +456,7 @@ async function checkProxies() {
|
|||||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||||
});
|
});
|
||||||
if (localDocker) {
|
if (localDocker) {
|
||||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
portReachable = await isReachable(80, { host: ipv4 || ipv6 });
|
||||||
if (!portReachable) {
|
if (!portReachable) {
|
||||||
await startTraefikProxy(localDocker.id);
|
await startTraefikProxy(localDocker.id);
|
||||||
}
|
}
|
||||||
@@ -380,14 +468,14 @@ async function checkProxies() {
|
|||||||
if (remoteDocker.length > 0) {
|
if (remoteDocker.length > 0) {
|
||||||
for (const docker of remoteDocker) {
|
for (const docker of remoteDocker) {
|
||||||
if (docker.isCoolifyProxyUsed) {
|
if (docker.isCoolifyProxyUsed) {
|
||||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
portReachable = await isReachable(80, { host: docker.remoteIpAddress });
|
||||||
if (!portReachable) {
|
if (!portReachable) {
|
||||||
await startTraefikProxy(docker.id);
|
await startTraefikProxy(docker.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await createRemoteEngineConfiguration(docker.id)
|
await createRemoteEngineConfiguration(docker.id);
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TCP Proxies
|
// TCP Proxies
|
||||||
@@ -426,80 +514,109 @@ async function checkProxies() {
|
|||||||
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copySSLCertificates() {
|
async function copySSLCertificates() {
|
||||||
try {
|
try {
|
||||||
const pAll = await import('p-all');
|
const pAll = await import('p-all');
|
||||||
const actions = []
|
const actions = [];
|
||||||
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
const certificates = await prisma.certificate.findMany({ include: { team: true } });
|
||||||
const teamIds = certificates.map(c => c.teamId)
|
const teamIds = certificates.map((c) => c.teamId);
|
||||||
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
const destinations = await prisma.destinationDocker.findMany({
|
||||||
|
where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } }
|
||||||
|
});
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
const { id, key, cert } = certificate
|
const { id, key, cert } = certificate;
|
||||||
const decryptedKey = decrypt(key)
|
const decryptedKey = decrypt(key);
|
||||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey);
|
||||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
await fs.writeFile(`/tmp/${id}-cert.pem`, cert);
|
||||||
for (const destination of destinations) {
|
for (const destination of destinations) {
|
||||||
if (destination.remoteEngine) {
|
if (destination.remoteEngine) {
|
||||||
if (destination.remoteVerified) {
|
if (destination.remoteVerified) {
|
||||||
const { id: dockerId, remoteIpAddress } = destination
|
const { id: dockerId, remoteIpAddress } = destination;
|
||||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actions.push(async () => copyLocalCertificates(id))
|
actions.push(async () => copyLocalCertificates(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await pAll.default(actions, { concurrency: 1 })
|
await pAll.default(actions, { concurrency: 1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
|
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
||||||
try {
|
try {
|
||||||
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
|
await executeCommand({
|
||||||
await executeCommand({ sshCommand: true, shell: true, dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
|
command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`
|
||||||
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
});
|
||||||
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
await executeCommand({
|
||||||
|
sshCommand: true,
|
||||||
|
shell: true,
|
||||||
|
dockerId,
|
||||||
|
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
sshCommand: true,
|
||||||
|
dockerId,
|
||||||
|
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
sshCommand: true,
|
||||||
|
dockerId,
|
||||||
|
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error })
|
console.log({ error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function copyLocalCertificates(id: string) {
|
async function copyLocalCertificates(id: string) {
|
||||||
try {
|
try {
|
||||||
await executeCommand({ command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`, shell: true })
|
await executeCommand({
|
||||||
await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`,
|
||||||
await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
shell: true
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||||
|
});
|
||||||
|
await executeCommand({
|
||||||
|
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error })
|
console.log({ error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanupStorage() {
|
async function cleanupStorage() {
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||||
let enginesDone = new Set()
|
let enginesDone = new Set();
|
||||||
for (const destination of destinationDockers) {
|
for (const destination of destinationDockers) {
|
||||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
|
||||||
if (destination.engine) enginesDone.add(destination.engine)
|
if (destination.engine) {
|
||||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
enginesDone.add(destination.engine);
|
||||||
|
}
|
||||||
|
if (destination.remoteIpAddress) {
|
||||||
|
if (!destination.remoteVerified) continue;
|
||||||
|
enginesDone.add(destination.remoteIpAddress);
|
||||||
|
}
|
||||||
let lowDiskSpace = false;
|
let lowDiskSpace = false;
|
||||||
try {
|
try {
|
||||||
let stdout = null
|
let stdout = null;
|
||||||
if (!isDev) {
|
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;
|
stdout = output.stdout;
|
||||||
} else {
|
} else {
|
||||||
const output = await executeCommand({
|
const output = await executeCommand({
|
||||||
command:
|
command: `df -kPT /`
|
||||||
`df -kPT /`
|
|
||||||
});
|
});
|
||||||
stdout = output.stdout;
|
stdout = output.stdout;
|
||||||
}
|
}
|
||||||
@@ -531,7 +648,9 @@ async function cleanupStorage() {
|
|||||||
lowDiskSpace = true;
|
lowDiskSpace = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
if (lowDiskSpace) {
|
||||||
|
await cleanupDockerStorage(destination.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
decryptApplication,
|
decryptApplication,
|
||||||
isDev,
|
isDev,
|
||||||
pushToRegistry,
|
pushToRegistry,
|
||||||
executeCommand
|
executeCommand,
|
||||||
|
generateSecrets
|
||||||
} from '../lib/common';
|
} from '../lib/common';
|
||||||
import * as importers from '../lib/importers';
|
import * as importers from '../lib/importers';
|
||||||
import * as buildpacks from '../lib/buildPacks';
|
import * as buildpacks from '../lib/buildPacks';
|
||||||
@@ -140,33 +141,13 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [`PORT='${port}'`];
|
let envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
(s) => s.name === secret.name && s.isPRMRSecret
|
];
|
||||||
);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry;
|
const { url, username, password } = dockerRegistry;
|
||||||
@@ -197,7 +178,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
container_name: applicationId,
|
container_name: applicationId,
|
||||||
volumes,
|
volumes,
|
||||||
labels,
|
labels,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
environment: envs,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
expose: [port],
|
expose: [port],
|
||||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||||
@@ -215,7 +196,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug: true,
|
debug: true,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -438,6 +419,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
githubAppId: gitSource.githubApp?.id,
|
githubAppId: gitSource.githubApp?.id,
|
||||||
gitlabAppId: gitSource.gitlabApp?.id,
|
gitlabAppId: gitSource.gitlabApp?.id,
|
||||||
customPort: gitSource.customPort,
|
customPort: gitSource.customPort,
|
||||||
|
customUser: gitSource.customUser,
|
||||||
gitCommitHash,
|
gitCommitHash,
|
||||||
configuration,
|
configuration,
|
||||||
repository,
|
repository,
|
||||||
@@ -619,6 +601,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (buildPack === 'compose') {
|
if (buildPack === 'compose') {
|
||||||
|
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||||
try {
|
try {
|
||||||
const { stdout: containers } = await executeCommand({
|
const { stdout: containers } = await executeCommand({
|
||||||
dockerId: destinationDockerId,
|
dockerId: destinationDockerId,
|
||||||
@@ -648,7 +631,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
await prisma.build.update({
|
await prisma.build.update({
|
||||||
@@ -698,37 +681,17 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
const envs = [`PORT='${port}'`];
|
let envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [
|
||||||
if (pullmergeRequestId) {
|
...envs,
|
||||||
const isSecretFound = secrets.filter(
|
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||||
(s) => s.name === secret.name && s.isPRMRSecret
|
];
|
||||||
);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
|
||||||
if (dockerRegistry) {
|
if (dockerRegistry) {
|
||||||
const { url, username, password } = dockerRegistry;
|
const { url, username, password } = dockerRegistry;
|
||||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.map((volume) => {
|
||||||
return {
|
return {
|
||||||
@@ -744,7 +707,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
image: imageFound,
|
image: imageFound,
|
||||||
container_name: imageId,
|
container_name: imageId,
|
||||||
volumes,
|
volumes,
|
||||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
environment: envs,
|
||||||
labels,
|
labels,
|
||||||
depends_on: [],
|
depends_on: [],
|
||||||
expose: [port],
|
expose: [port],
|
||||||
@@ -763,7 +726,7 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug,
|
debug,
|
||||||
dockerId: destinationDocker.id,
|
dockerId: destinationDocker.id,
|
||||||
command: `docker compose --project-directory ${workdir} up -d`
|
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
decrypt,
|
decrypt,
|
||||||
encrypt,
|
encrypt,
|
||||||
executeCommand,
|
executeCommand,
|
||||||
|
generateSecrets,
|
||||||
generateTimestamp,
|
generateTimestamp,
|
||||||
getDomain,
|
getDomain,
|
||||||
isARM,
|
isARM,
|
||||||
@@ -653,7 +654,7 @@ export async function saveDockerRegistryCredentials({ url, username, password, w
|
|||||||
try {
|
try {
|
||||||
await fs.mkdir(`${workdir}/.docker`);
|
await fs.mkdir(`${workdir}/.docker`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
}
|
}
|
||||||
const payload = JSON.stringify({
|
const payload = JSON.stringify({
|
||||||
auths: {
|
auths: {
|
||||||
@@ -787,21 +788,8 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
@@ -811,7 +799,6 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
if (installCommand) {
|
if (installCommand) {
|
||||||
Dockerfile.push(`RUN ${installCommand}`);
|
Dockerfile.push(`RUN ${installCommand}`);
|
||||||
}
|
}
|
||||||
// Dockerfile.push(`ARG CACHEBUST=1`);
|
|
||||||
Dockerfile.push(`RUN ${buildCommand}`);
|
Dockerfile.push(`RUN ${buildCommand}`);
|
||||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||||
await buildImage({ ...data, isCache: true });
|
await buildImage({ ...data, isCache: true });
|
||||||
@@ -819,27 +806,13 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
|
|
||||||
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||||
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
||||||
|
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { defaultComposeConfiguration, executeCommand } from '../common';
|
import { defaultComposeConfiguration, executeCommand, generateSecrets } from '../common';
|
||||||
import { saveBuildLog } from './common';
|
import { saveBuildLog } from './common';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
|
|
||||||
@@ -25,30 +25,13 @@ export default async function (data) {
|
|||||||
if (!dockerComposeYaml.services) {
|
if (!dockerComposeYaml.services) {
|
||||||
throw 'No Services found in docker-compose file.';
|
throw 'No Services found in docker-compose file.';
|
||||||
}
|
}
|
||||||
const envs = [];
|
let envs = [];
|
||||||
|
let buildEnvs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||||
if (pullmergeRequestId) {
|
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||||
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
envs.push(`${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
envs.push(`${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
|
||||||
let envFound = false;
|
|
||||||
try {
|
|
||||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const composeVolumes = [];
|
const composeVolumes = [];
|
||||||
if (volumes.length > 0) {
|
if (volumes.length > 0) {
|
||||||
for (const volume of volumes) {
|
for (const volume of volumes) {
|
||||||
@@ -62,7 +45,34 @@ export default async function (data) {
|
|||||||
let networks = {};
|
let networks = {};
|
||||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||||
value['container_name'] = `${applicationId}-${key}`;
|
value['container_name'] = `${applicationId}-${key}`;
|
||||||
value['env_file'] = envFound ? [`${workdir}/.env`] : [];
|
|
||||||
|
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;
|
value['labels'] = labels;
|
||||||
// TODO: If we support separated volume for each service, we need to add it here
|
// TODO: If we support separated volume for each service, we need to add it here
|
||||||
if (value['volumes']?.length > 0) {
|
if (value['volumes']?.length > 0) {
|
||||||
@@ -106,13 +116,14 @@ export default async function (data) {
|
|||||||
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
||||||
}
|
}
|
||||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||||
|
|
||||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
debug,
|
debug,
|
||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker compose --project-directory ${workdir} pull`
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
@@ -120,7 +131,7 @@ export default async function (data) {
|
|||||||
buildId,
|
buildId,
|
||||||
applicationId,
|
applicationId,
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker compose --project-directory ${workdir} build --progress plain`
|
command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain`
|
||||||
});
|
});
|
||||||
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (depsFound) {
|
if (depsFound) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
export default async function (data) {
|
export default async function (data) {
|
||||||
@@ -13,19 +14,12 @@ export default async function (data) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.forEach((line, index) => {
|
||||||
if (
|
if (line.startsWith('FROM')) {
|
||||||
(pullmergeRequestId && secret.isPRMRSecret) ||
|
Dockerfile.splice(index + 1, 0, env);
|
||||||
(!pullmergeRequestId && !secret.isPRMRSecret)
|
|
||||||
) {
|
|
||||||
Dockerfile.forEach((line, index) => {
|
|
||||||
if (line.startsWith('FROM')) {
|
|
||||||
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageForLaravel, buildImage } from './common';
|
import { buildCacheImageForLaravel, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, applicationId, tag, buildId, port } = data;
|
const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
|
if (secrets.length > 0) {
|
||||||
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
|
Dockerfile.push(env);
|
||||||
|
});
|
||||||
|
}
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
||||||
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage, checkPnpm } from './common';
|
import { buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -20,21 +21,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isPnpm) {
|
if (isPnpm) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||||
@@ -13,21 +14,8 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
|||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildImage } from './common';
|
import { buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -18,21 +19,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
Dockerfile.push('WORKDIR /app');
|
Dockerfile.push('WORKDIR /app');
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
import { generateSecrets } from '../common';
|
||||||
import { buildCacheImageWithNode, buildImage } from './common';
|
import { buildCacheImageWithNode, buildImage } from './common';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
@@ -25,21 +26,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
}
|
}
|
||||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||||
if (secret.isBuildSecret) {
|
Dockerfile.push(env);
|
||||||
if (pullmergeRequestId) {
|
|
||||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
|
||||||
if (isSecretFound.length > 0) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${isSecretFound[0].value}'`);
|
|
||||||
} else {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!secret.isPRMRSecret) {
|
|
||||||
Dockerfile.push(`ARG ${secret.name}='${secret.value}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (buildCommand) {
|
if (buildCommand) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common
|
|||||||
import { scheduler } from './scheduler';
|
import { scheduler } from './scheduler';
|
||||||
import type { ExecaChildProcess } from 'execa';
|
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 isDev = process.env.NODE_ENV === 'development';
|
||||||
export const sentryDSN =
|
export const sentryDSN =
|
||||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
||||||
@@ -714,8 +714,10 @@ export async function startTraefikProxy(id: string): Promise<void> {
|
|||||||
--network coolify-infra \
|
--network coolify-infra \
|
||||||
-p "80:80" \
|
-p "80:80" \
|
||||||
-p "443:443" \
|
-p "443:443" \
|
||||||
|
${isDev ? '-p "8080:8080"' : ''} \
|
||||||
--name coolify-proxy \
|
--name coolify-proxy \
|
||||||
-d ${defaultTraefikImage} \
|
-d ${defaultTraefikImage} \
|
||||||
|
${isDev ? '--api.insecure=true' : ''} \
|
||||||
--entrypoints.web.address=:80 \
|
--entrypoints.web.address=:80 \
|
||||||
--entrypoints.web.forwardedHeaders.insecure=true \
|
--entrypoints.web.forwardedHeaders.insecure=true \
|
||||||
--entrypoints.websecure.address=:443 \
|
--entrypoints.websecure.address=:443 \
|
||||||
@@ -1712,70 +1714,24 @@ export function convertTolOldVolumeNames(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
export async function cleanupDockerStorage(dockerId) {
|
||||||
// Cleanup old coolify images
|
// Cleanup images that are not used by any container
|
||||||
try {
|
try {
|
||||||
let { stdout: images } = await executeCommand({
|
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
} 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({
|
// Prune coolify managed containers
|
||||||
where: { id: '0' }
|
try {
|
||||||
});
|
await executeCommand({
|
||||||
const { stdout: images } = await executeCommand({
|
|
||||||
dockerId,
|
dockerId,
|
||||||
command: `docker images|grep -v "<none>"|grep -v REPOSITORY|awk '{print $1, $2}'`,
|
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||||
shell: true
|
|
||||||
});
|
});
|
||||||
const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
|
} catch (error) {}
|
||||||
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
|
|
||||||
let deleteImage = [];
|
|
||||||
for (const image of imagesSet) {
|
|
||||||
let keepImage = [];
|
|
||||||
for (const image2 of imagesArray) {
|
|
||||||
if (image2.startsWith(image)) {
|
|
||||||
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
|
|
||||||
deleteImage.push(image2);
|
|
||||||
} else {
|
|
||||||
keepImage.push(image2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const image of deleteImage) {
|
|
||||||
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prune coolify managed containers
|
// Cleanup build caches
|
||||||
try {
|
try {
|
||||||
await executeCommand({
|
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||||
dockerId,
|
} catch (error) {}
|
||||||
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) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function persistentVolumes(id, persistentStorage, config) {
|
export function persistentVolumes(id, persistentStorage, config) {
|
||||||
@@ -1875,3 +1831,66 @@ export async function pushToRegistry(
|
|||||||
command: pushCommand
|
command: pushCommand
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseSecret(secret, isBuild) {
|
||||||
|
if (secret.value.includes('$')) {
|
||||||
|
secret.value = secret.value.replaceAll('$', '$$$$');
|
||||||
|
}
|
||||||
|
if (secret.value.includes('\\n')) {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}=${secret.value}`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}=${secret.value}`;
|
||||||
|
}
|
||||||
|
} else if (secret.value.includes(' ')) {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}='${secret.value}'`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}='${secret.value}'`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isBuild) {
|
||||||
|
return `ARG ${secret.name}=${secret.value}`;
|
||||||
|
} else {
|
||||||
|
return `${secret.name}=${secret.value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function generateSecrets(
|
||||||
|
secrets: Array<any>,
|
||||||
|
pullmergeRequestId: string,
|
||||||
|
isBuild = false,
|
||||||
|
port = null,
|
||||||
|
compose = false
|
||||||
|
): Array<string> {
|
||||||
|
const envs = [];
|
||||||
|
const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret);
|
||||||
|
const normalSecrets = secrets.filter((s) => !s.isPRMRSecret);
|
||||||
|
if (pullmergeRequestId && isPRMRSecret.length > 0) {
|
||||||
|
isPRMRSecret.forEach((secret) => {
|
||||||
|
if (isBuild && !secret.isBuildSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const build = isBuild && secret.isBuildSecret;
|
||||||
|
envs.push(parseSecret(secret, compose ? false : build));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!pullmergeRequestId && normalSecrets.length > 0) {
|
||||||
|
normalSecrets.forEach((secret) => {
|
||||||
|
if (isBuild && !secret.isBuildSecret) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const build = isBuild && secret.isBuildSecret;
|
||||||
|
envs.push(parseSecret(secret, compose ? false : build));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const portFound = envs.filter((env) => env.startsWith('PORT'));
|
||||||
|
if (portFound.length === 0 && port && !isBuild) {
|
||||||
|
envs.push(`PORT=${port}`);
|
||||||
|
}
|
||||||
|
const nodeEnv = envs.filter((env) => env.startsWith('NODE_ENV'));
|
||||||
|
if (nodeEnv.length === 0 && !isBuild) {
|
||||||
|
envs.push(`NODE_ENV=production`);
|
||||||
|
}
|
||||||
|
return envs;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export default async function ({
|
|||||||
buildId,
|
buildId,
|
||||||
privateSshKey,
|
privateSshKey,
|
||||||
customPort,
|
customPort,
|
||||||
forPublic
|
forPublic,
|
||||||
|
customUser,
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
@@ -25,6 +26,7 @@ export default async function ({
|
|||||||
privateSshKey: string;
|
privateSshKey: string;
|
||||||
customPort: number;
|
customPort: number;
|
||||||
forPublic: boolean;
|
forPublic: boolean;
|
||||||
|
customUser: string;
|
||||||
}): Promise<string> {
|
}): Promise<string> {
|
||||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||||
if (!forPublic) {
|
if (!forPublic) {
|
||||||
@@ -53,7 +55,7 @@ export default async function ({
|
|||||||
} else {
|
} else {
|
||||||
await executeCommand({
|
await executeCommand({
|
||||||
command:
|
command:
|
||||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
`git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,154 +1,170 @@
|
|||||||
import type { OnlyId } from "../../../../types";
|
import type { OnlyId } from '../../../../types';
|
||||||
|
|
||||||
export interface SaveApplication extends OnlyId {
|
export interface SaveApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
name: string,
|
name: string;
|
||||||
buildPack: string,
|
buildPack: string;
|
||||||
fqdn: string,
|
fqdn: string;
|
||||||
port: number,
|
port: number;
|
||||||
exposePort: number,
|
exposePort: number;
|
||||||
installCommand: string,
|
installCommand: string;
|
||||||
buildCommand: string,
|
buildCommand: string;
|
||||||
startCommand: string,
|
startCommand: string;
|
||||||
baseDirectory: string,
|
baseDirectory: string;
|
||||||
publishDirectory: string,
|
publishDirectory: string;
|
||||||
pythonWSGI: string,
|
pythonWSGI: string;
|
||||||
pythonModule: string,
|
pythonModule: string;
|
||||||
pythonVariable: string,
|
pythonVariable: string;
|
||||||
dockerFileLocation: string,
|
dockerFileLocation: string;
|
||||||
denoMainFile: string,
|
denoMainFile: string;
|
||||||
denoOptions: string,
|
denoOptions: string;
|
||||||
baseImage: string,
|
baseImage: string;
|
||||||
gitCommitHash: string,
|
gitCommitHash: string;
|
||||||
baseBuildImage: string,
|
baseBuildImage: string;
|
||||||
deploymentType: string,
|
deploymentType: string;
|
||||||
baseDatabaseBranch: string,
|
baseDatabaseBranch: string;
|
||||||
dockerComposeFile: string,
|
dockerComposeFile: string;
|
||||||
dockerComposeFileLocation: string,
|
dockerComposeFileLocation: string;
|
||||||
dockerComposeConfiguration: string,
|
dockerComposeConfiguration: string;
|
||||||
simpleDockerfile: string,
|
simpleDockerfile: string;
|
||||||
dockerRegistryImageName: string
|
dockerRegistryImageName: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSettings extends OnlyId {
|
export interface SaveApplicationSettings extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
|
Body: {
|
||||||
|
debug: boolean;
|
||||||
|
previews: boolean;
|
||||||
|
dualCerts: boolean;
|
||||||
|
autodeploy: boolean;
|
||||||
|
branch: string;
|
||||||
|
projectId: number;
|
||||||
|
isBot: boolean;
|
||||||
|
isDBBranching: boolean;
|
||||||
|
isCustomSSL: boolean;
|
||||||
|
isHttp2: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteApplication extends OnlyId {
|
export interface DeleteApplication extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: { force: boolean }
|
Body: { force: boolean };
|
||||||
}
|
}
|
||||||
export interface CheckDomain extends OnlyId {
|
export interface CheckDomain extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
}
|
}
|
||||||
export interface CheckDNS extends OnlyId {
|
export interface CheckDNS extends OnlyId {
|
||||||
Querystring: { domain: string; };
|
Querystring: { domain: string };
|
||||||
Body: {
|
Body: {
|
||||||
exposePort: number,
|
exposePort: number;
|
||||||
fqdn: string,
|
fqdn: string;
|
||||||
forceSave: boolean,
|
forceSave: boolean;
|
||||||
dualCerts: boolean
|
dualCerts: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeployApplication {
|
export interface DeployApplication {
|
||||||
Querystring: { domain: string }
|
Querystring: { domain: string };
|
||||||
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
|
||||||
}
|
}
|
||||||
export interface GetImages {
|
export interface GetImages {
|
||||||
Body: { buildPack: string, deploymentType: string }
|
Body: { buildPack: string; deploymentType: string };
|
||||||
}
|
}
|
||||||
export interface SaveApplicationSource extends OnlyId {
|
export interface SaveApplicationSource extends OnlyId {
|
||||||
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string }
|
Body: {
|
||||||
|
gitSourceId?: string | null;
|
||||||
|
forPublic?: boolean;
|
||||||
|
type?: string;
|
||||||
|
simpleDockerfile?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface CheckRepository extends OnlyId {
|
export interface CheckRepository extends OnlyId {
|
||||||
Querystring: { repository: string, branch: string }
|
Querystring: { repository: string; branch: string };
|
||||||
}
|
}
|
||||||
export interface SaveDestination extends OnlyId {
|
export interface SaveDestination extends OnlyId {
|
||||||
Body: { destinationId: string }
|
Body: { destinationId: string };
|
||||||
}
|
}
|
||||||
export interface SaveSecret extends OnlyId {
|
export interface SaveSecret extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
name: string,
|
name: string;
|
||||||
value: string,
|
value: string;
|
||||||
isBuildSecret: boolean,
|
isBuildSecret: boolean;
|
||||||
previewSecret: boolean,
|
previewSecret: boolean;
|
||||||
isNew: boolean
|
isNew: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteSecret extends OnlyId {
|
export interface DeleteSecret extends OnlyId {
|
||||||
Body: { name: string }
|
Body: { name: string };
|
||||||
}
|
}
|
||||||
export interface SaveStorage extends OnlyId {
|
export interface SaveStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
path: string;
|
||||||
newStorage: boolean,
|
newStorage: boolean;
|
||||||
storageId: string
|
storageId: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeleteStorage extends OnlyId {
|
export interface DeleteStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
path: string,
|
path: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetApplicationLogs {
|
export interface GetApplicationLogs {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
containerId: string
|
containerId: string;
|
||||||
}
|
};
|
||||||
Querystring: {
|
Querystring: {
|
||||||
since: number,
|
since: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetBuilds extends OnlyId {
|
export interface GetBuilds extends OnlyId {
|
||||||
Querystring: {
|
Querystring: {
|
||||||
buildId: string
|
buildId: string;
|
||||||
skip: number,
|
skip: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface GetBuildIdLogs {
|
export interface GetBuildIdLogs {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
buildId: string
|
buildId: string;
|
||||||
},
|
};
|
||||||
Querystring: {
|
Querystring: {
|
||||||
sequence: number
|
sequence: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface SaveDeployKey extends OnlyId {
|
export interface SaveDeployKey extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
deployKeyId: number
|
deployKeyId: number;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface CancelDeployment {
|
export interface CancelDeployment {
|
||||||
Body: {
|
Body: {
|
||||||
buildId: string,
|
buildId: string;
|
||||||
applicationId: string
|
applicationId: string;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface DeployApplication extends OnlyId {
|
export interface DeployApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
branch: string,
|
branch: string;
|
||||||
forceRebuild?: boolean
|
forceRebuild?: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StopPreviewApplication extends OnlyId {
|
export interface StopPreviewApplication extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface RestartPreviewApplication {
|
export interface RestartPreviewApplication {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
pullmergeRequestId: string | null,
|
pullmergeRequestId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export interface RestartApplication {
|
export interface RestartApplication {
|
||||||
Params: {
|
Params: {
|
||||||
id: string,
|
id: string;
|
||||||
},
|
};
|
||||||
Body: {
|
Body: {
|
||||||
imageId: string | null,
|
imageId: string | null;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
|
|||||||
Body: { type: string }
|
Body: { type: string }
|
||||||
}
|
}
|
||||||
export interface DeleteDatabase extends OnlyId {
|
export interface DeleteDatabase extends OnlyId {
|
||||||
Body: { force: string }
|
Body: { }
|
||||||
}
|
}
|
||||||
export interface SaveVersion extends OnlyId {
|
export interface SaveVersion extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { compareVersions } from "compare-versions";
|
import { compareVersions } from 'compare-versions';
|
||||||
import cuid from "cuid";
|
import cuid from 'cuid';
|
||||||
import bcrypt from "bcryptjs";
|
import bcrypt from 'bcryptjs';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import {
|
import {
|
||||||
@@ -13,12 +13,12 @@ import {
|
|||||||
uniqueName,
|
uniqueName,
|
||||||
version,
|
version,
|
||||||
sentryDSN,
|
sentryDSN,
|
||||||
executeCommand,
|
executeCommand
|
||||||
} from "../../../lib/common";
|
} from '../../../lib/common';
|
||||||
import { scheduler } from "../../../lib/scheduler";
|
import { scheduler } from '../../../lib/scheduler';
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import type { Login, Update } from ".";
|
import type { Login, Update } from '.';
|
||||||
import type { GetCurrentUser } from "./types";
|
import type { GetCurrentUser } from './types';
|
||||||
|
|
||||||
export async function hashPassword(password: string): Promise<string> {
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
const saltRounds = 15;
|
const saltRounds = 15;
|
||||||
@@ -29,9 +29,9 @@ export async function backup(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const { backupData } = request.params;
|
const { backupData } = request.params;
|
||||||
let std = null;
|
let std = null;
|
||||||
const [id, backupType, type, zipped, storage] = backupData.split(':')
|
const [id, backupType, type, zipped, storage] = backupData.split(':');
|
||||||
console.log(id, backupType, type, zipped, storage)
|
console.log(id, backupType, type, zipped, storage);
|
||||||
const database = await prisma.database.findUnique({ where: { id } })
|
const database = await prisma.database.findUnique({ where: { id } });
|
||||||
if (database) {
|
if (database) {
|
||||||
// await executeDockerCmd({
|
// await executeDockerCmd({
|
||||||
// dockerId: database.destinationDockerId,
|
// dockerId: database.destinationDockerId,
|
||||||
@@ -40,8 +40,7 @@ export async function backup(request: FastifyRequest) {
|
|||||||
std = await executeCommand({
|
std = await executeCommand({
|
||||||
dockerId: database.destinationDockerId,
|
dockerId: database.destinationDockerId,
|
||||||
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
|
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
if (std.stdout) {
|
if (std.stdout) {
|
||||||
return std.stdout;
|
return std.stdout;
|
||||||
@@ -58,9 +57,9 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
try {
|
try {
|
||||||
const { serverId } = request.body;
|
const { serverId } = request.body;
|
||||||
const destination = await prisma.destinationDocker.findUnique({
|
const destination = await prisma.destinationDocker.findUnique({
|
||||||
where: { id: serverId },
|
where: { id: serverId }
|
||||||
});
|
});
|
||||||
await cleanupDockerStorage(destination.id, true, true);
|
await cleanupDockerStorage(destination.id);
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -68,17 +67,25 @@ export async function cleanupManually(request: FastifyRequest) {
|
|||||||
}
|
}
|
||||||
export async function refreshTags() {
|
export async function refreshTags() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||||
await fs.writeFile('./tags.json', tags)
|
try {
|
||||||
|
if (await fs.stat('./testTags.json')) {
|
||||||
|
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||||
|
if (testTags.length > 0) {
|
||||||
|
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
await fs.writeFile('./tags.json', tags);
|
||||||
} else {
|
} else {
|
||||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||||
await fs.writeFile('/app/tags.json', tags)
|
await fs.writeFile('/app/tags.json', tags);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -88,17 +95,25 @@ export async function refreshTags() {
|
|||||||
}
|
}
|
||||||
export async function refreshTemplates() {
|
export async function refreshTemplates() {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
try {
|
try {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
try {
|
||||||
|
if (await fs.stat('./testTemplate.yaml')) {
|
||||||
|
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||||
|
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||||
} else {
|
} else {
|
||||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
const response = await got
|
||||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||||
|
.text();
|
||||||
|
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -107,28 +122,29 @@ export async function refreshTemplates() {
|
|||||||
}
|
}
|
||||||
export async function checkUpdate(request: FastifyRequest) {
|
export async function checkUpdate(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const { default: got } = await import('got')
|
const { default: got } = await import('got');
|
||||||
const isStaging =
|
const isStaging =
|
||||||
request.hostname === "staging.coolify.io" ||
|
request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io';
|
||||||
request.hostname === "arm.coolify.io";
|
|
||||||
const currentVersion = version;
|
const currentVersion = version;
|
||||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
const { coolify } = await got
|
||||||
searchParams: {
|
.get('https://get.coollabs.io/versions.json', {
|
||||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
searchParams: {
|
||||||
version: currentVersion
|
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||||
}
|
version: currentVersion
|
||||||
}).json()
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
const latestVersion = coolify.main.version;
|
const latestVersion = coolify.main.version;
|
||||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||||
if (isStaging) {
|
if (isStaging) {
|
||||||
return {
|
return {
|
||||||
isUpdateAvailable: true,
|
isUpdateAvailable: true,
|
||||||
latestVersion: "next",
|
latestVersion: 'next'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
||||||
latestVersion,
|
latestVersion
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -142,8 +158,13 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||||
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
||||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
|
await executeCommand({
|
||||||
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` });
|
command: `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 {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
await asyncSleep(2000);
|
await asyncSleep(2000);
|
||||||
@@ -156,12 +177,12 @@ export async function update(request: FastifyRequest<Update>) {
|
|||||||
export async function resetQueue(request: FastifyRequest<any>) {
|
export async function resetQueue(request: FastifyRequest<any>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (teamId === "0") {
|
if (teamId === '0') {
|
||||||
await prisma.build.updateMany({
|
await prisma.build.updateMany({
|
||||||
where: { status: { in: ["queued", "running"] } },
|
where: { status: { in: ['queued', 'running'] } },
|
||||||
data: { status: "canceled" },
|
data: { status: 'canceled' }
|
||||||
});
|
});
|
||||||
scheduler.workers.get("deployApplication").postMessage("cancel");
|
scheduler.workers.get('deployApplication').postMessage('cancel');
|
||||||
}
|
}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -170,7 +191,7 @@ export async function resetQueue(request: FastifyRequest<any>) {
|
|||||||
export async function restartCoolify(request: FastifyRequest<any>) {
|
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||||
try {
|
try {
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
if (teamId === "0") {
|
if (teamId === '0') {
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
await executeCommand({ command: `docker restart coolify` });
|
await executeCommand({ command: `docker restart coolify` });
|
||||||
return {};
|
return {};
|
||||||
@@ -180,7 +201,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
|||||||
}
|
}
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message: "You are not authorized to restart Coolify.",
|
message: 'You are not authorized to restart Coolify.'
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
@@ -192,43 +213,52 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
const teamId = request.user.teamId;
|
const teamId = request.user.teamId;
|
||||||
let applications = await prisma.application.findMany({
|
let applications = await prisma.application.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { settings: true, destinationDocker: true, teams: true },
|
include: { settings: true, destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const databases = await prisma.database.findMany({
|
const databases = await prisma.database.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { settings: true, destinationDocker: true, teams: true },
|
include: { settings: true, destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const services = await prisma.service.findMany({
|
const services = await prisma.service.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { destinationDocker: true, teams: true },
|
include: { destinationDocker: true, teams: true }
|
||||||
});
|
});
|
||||||
const gitSources = await prisma.gitSource.findMany({
|
const gitSources = await prisma.gitSource.findMany({
|
||||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
where: {
|
||||||
include: { teams: true },
|
OR: [
|
||||||
|
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
|
{ isSystemWide: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
const destinations = await prisma.destinationDocker.findMany({
|
const destinations = await prisma.destinationDocker.findMany({
|
||||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
const settings = await listSettings();
|
const settings = await listSettings();
|
||||||
|
|
||||||
let foundUnconfiguredApplication = false;
|
let foundUnconfiguredApplication = false;
|
||||||
for (const application of applications) {
|
for (const application of applications) {
|
||||||
if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
|
if (
|
||||||
foundUnconfiguredApplication = true
|
((!application.buildPack || !application.branch) && !application.simpleDockerfile) ||
|
||||||
|
!application.destinationDockerId ||
|
||||||
|
(!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose')
|
||||||
|
) {
|
||||||
|
foundUnconfiguredApplication = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let foundUnconfiguredService = false;
|
let foundUnconfiguredService = false;
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
if (!service.fqdn) {
|
if (!service.fqdn) {
|
||||||
foundUnconfiguredService = true
|
foundUnconfiguredService = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let foundUnconfiguredDatabase = false;
|
let foundUnconfiguredDatabase = false;
|
||||||
for (const database of databases) {
|
for (const database of databases) {
|
||||||
if (!database.version) {
|
if (!database.version) {
|
||||||
foundUnconfiguredDatabase = true
|
foundUnconfiguredDatabase = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -240,101 +270,94 @@ export async function showDashboard(request: FastifyRequest) {
|
|||||||
services,
|
services,
|
||||||
gitSources,
|
gitSources,
|
||||||
destinations,
|
destinations,
|
||||||
settings,
|
settings
|
||||||
};
|
};
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message });
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(
|
export async function login(request: FastifyRequest<Login>, reply: FastifyReply) {
|
||||||
request: FastifyRequest<Login>,
|
|
||||||
reply: FastifyReply
|
|
||||||
) {
|
|
||||||
if (request.user) {
|
if (request.user) {
|
||||||
return reply.redirect("/dashboard");
|
return reply.redirect('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
const { email, password, isLogin } = request.body || {};
|
const { email, password, isLogin } = request.body || {};
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
throw { status: 500, message: "Email and password are required." };
|
throw { status: 500, message: 'Email and password are required.' };
|
||||||
}
|
}
|
||||||
const users = await prisma.user.count();
|
const users = await prisma.user.count();
|
||||||
const userFound = await prisma.user.findUnique({
|
const userFound = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
include: { teams: true, permission: true },
|
include: { teams: true, permission: true },
|
||||||
rejectOnNotFound: false,
|
rejectOnNotFound: false
|
||||||
});
|
});
|
||||||
if (!userFound && isLogin) {
|
if (!userFound && isLogin) {
|
||||||
throw { status: 500, message: "User not found." };
|
throw { status: 500, message: 'User not found.' };
|
||||||
}
|
}
|
||||||
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
||||||
let uid = cuid();
|
let uid = cuid();
|
||||||
let permission = "read";
|
let permission = 'read';
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
|
|
||||||
if (users === 0) {
|
if (users === 0) {
|
||||||
await prisma.setting.update({
|
await prisma.setting.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isRegistrationEnabled: false },
|
data: { isRegistrationEnabled: false }
|
||||||
});
|
});
|
||||||
uid = "0";
|
uid = '0';
|
||||||
}
|
}
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
if (userFound.type === "email") {
|
if (userFound.type === 'email') {
|
||||||
if (userFound.password === "RESETME") {
|
if (userFound.password === 'RESETME') {
|
||||||
const hashedPassword = await hashPassword(password);
|
const hashedPassword = await hashPassword(password);
|
||||||
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||||
if (userFound.id === "0") {
|
if (userFound.id === '0') {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: "RESETME" },
|
data: { password: 'RESETME' }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: "RESETTIMEOUT" },
|
data: { password: 'RESETTIMEOUT' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message:
|
message: 'Password reset link has expired. Please request a new one.'
|
||||||
"Password reset link has expired. Please request a new one.",
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: { email: userFound.email },
|
where: { email: userFound.email },
|
||||||
data: { password: hashedPassword },
|
data: { password: hashedPassword }
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
userId: userFound.id,
|
userId: userFound.id,
|
||||||
teamId: userFound.id,
|
teamId: userFound.id,
|
||||||
permission: userFound.permission,
|
permission: userFound.permission,
|
||||||
isAdmin: true,
|
isAdmin: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const passwordMatch = await bcrypt.compare(
|
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||||
password,
|
|
||||||
userFound.password
|
|
||||||
);
|
|
||||||
if (!passwordMatch) {
|
if (!passwordMatch) {
|
||||||
throw {
|
throw {
|
||||||
status: 500,
|
status: 500,
|
||||||
message: "Wrong password or email address.",
|
message: 'Wrong password or email address.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
uid = userFound.id;
|
uid = userFound.id;
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
permission = "owner";
|
permission = 'owner';
|
||||||
isAdmin = true;
|
isAdmin = true;
|
||||||
if (!isRegistrationEnabled) {
|
if (!isRegistrationEnabled) {
|
||||||
throw {
|
throw {
|
||||||
status: 404,
|
status: 404,
|
||||||
message: "Registration disabled by administrator.",
|
message: 'Registration disabled by administrator.'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const hashedPassword = await hashPassword(password);
|
const hashedPassword = await hashPassword(password);
|
||||||
@@ -344,17 +367,17 @@ export async function login(
|
|||||||
id: uid,
|
id: uid,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
type: "email",
|
type: 'email',
|
||||||
teams: {
|
teams: {
|
||||||
create: {
|
create: {
|
||||||
id: uid,
|
id: uid,
|
||||||
name: uniqueName(),
|
name: uniqueName(),
|
||||||
destinationDocker: { connect: { network: "coolify" } },
|
destinationDocker: { connect: { network: 'coolify' } }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
permission: { create: { teamId: uid, permission: "owner" } },
|
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||||
},
|
},
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
@@ -362,16 +385,16 @@ export async function login(
|
|||||||
id: uid,
|
id: uid,
|
||||||
email,
|
email,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
type: "email",
|
type: 'email',
|
||||||
teams: {
|
teams: {
|
||||||
create: {
|
create: {
|
||||||
id: uid,
|
id: uid,
|
||||||
name: uniqueName(),
|
name: uniqueName()
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
permission: { create: { teamId: uid, permission: "owner" } },
|
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||||
},
|
},
|
||||||
include: { teams: true },
|
include: { teams: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,23 +402,20 @@ export async function login(
|
|||||||
userId: uid,
|
userId: uid,
|
||||||
teamId: uid,
|
teamId: uid,
|
||||||
permission,
|
permission,
|
||||||
isAdmin,
|
isAdmin
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCurrentUser(
|
export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
|
||||||
request: FastifyRequest<GetCurrentUser>,
|
|
||||||
fastify
|
|
||||||
) {
|
|
||||||
let token = null;
|
let token = null;
|
||||||
const { teamId } = request.query;
|
const { teamId } = request.query;
|
||||||
try {
|
try {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: request.user.userId },
|
where: { id: request.user.userId }
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw "User not found";
|
throw 'User not found';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw { status: 401, message: error };
|
throw { status: 401, message: error };
|
||||||
@@ -404,17 +424,15 @@ export async function getCurrentUser(
|
|||||||
try {
|
try {
|
||||||
const user = await prisma.user.findFirst({
|
const user = await prisma.user.findFirst({
|
||||||
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
||||||
include: { teams: true, permission: true },
|
include: { teams: true, permission: true }
|
||||||
});
|
});
|
||||||
if (user) {
|
if (user) {
|
||||||
const permission = user.permission.find(
|
const permission = user.permission.find((p) => p.teamId === teamId).permission;
|
||||||
(p) => p.teamId === teamId
|
|
||||||
).permission;
|
|
||||||
const payload = {
|
const payload = {
|
||||||
...request.user,
|
...request.user,
|
||||||
teamId,
|
teamId,
|
||||||
permission: permission || null,
|
permission: permission || null,
|
||||||
isAdmin: permission === "owner" || permission === "admin",
|
isAdmin: permission === 'owner' || permission === 'admin'
|
||||||
};
|
};
|
||||||
token = fastify.jwt.sign(payload);
|
token = fastify.jwt.sign(payload);
|
||||||
}
|
}
|
||||||
@@ -422,12 +440,14 @@ export async function getCurrentUser(
|
|||||||
// No new token -> not switching teams
|
// No new token -> not switching teams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
|
const pendingInvitations = await prisma.teamInvitation.findMany({
|
||||||
|
where: { uid: request.user.userId }
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
settings: await prisma.setting.findUnique({ where: { id: "0" } }),
|
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||||
sentryDSN,
|
sentryDSN,
|
||||||
pendingInvitations,
|
pendingInvitations,
|
||||||
token,
|
token,
|
||||||
...request.user,
|
...request.user
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,11 @@ export async function listSources(request: FastifyRequest) {
|
|||||||
export async function saveSource(request, reply) {
|
export async function saveSource(request, reply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
|
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
|
||||||
if (customPort) customPort = Number(customPort)
|
if (customPort) customPort = Number(customPort)
|
||||||
await prisma.gitSource.update({
|
await prisma.gitSource.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
|
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||||
});
|
});
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@@ -48,6 +48,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
|
|||||||
apiUrl: null,
|
apiUrl: null,
|
||||||
organization: null,
|
organization: null,
|
||||||
customPort: 22,
|
customPort: 22,
|
||||||
|
customUser: 'git',
|
||||||
},
|
},
|
||||||
settings
|
settings
|
||||||
}
|
}
|
||||||
@@ -133,7 +134,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
|||||||
try {
|
try {
|
||||||
const { id } = request.params
|
const { id } = request.params
|
||||||
const { teamId } = request.user
|
const { teamId } = request.user
|
||||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
|
||||||
request.body
|
request.body
|
||||||
|
|
||||||
if (oauthId) oauthId = Number(oauthId);
|
if (oauthId) oauthId = Number(oauthId);
|
||||||
@@ -142,7 +143,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
|||||||
|
|
||||||
if (id === 'new') {
|
if (id === 'new') {
|
||||||
const newId = cuid()
|
const newId = cuid()
|
||||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
|
||||||
await prisma.gitlabApp.create({
|
await prisma.gitlabApp.create({
|
||||||
data: {
|
data: {
|
||||||
teams: { connect: { id: teamId } },
|
teams: { connect: { id: teamId } },
|
||||||
@@ -158,7 +159,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
|||||||
id: newId
|
id: newId
|
||||||
}
|
}
|
||||||
} else {
|
} 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({
|
await prisma.gitlabApp.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
|
|||||||
appSecret: string,
|
appSecret: string,
|
||||||
groupName: string,
|
groupName: string,
|
||||||
customPort: number,
|
customPort: number,
|
||||||
|
customUser: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface CheckGitLabOAuthId extends OnlyId {
|
export interface CheckGitLabOAuthId extends OnlyId {
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from 'fastify';
|
||||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common";
|
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
|
||||||
import { getTemplates } from "../../../lib/services";
|
import { getTemplates } from '../../../lib/services';
|
||||||
import { OnlyId } from "../../../types";
|
import { OnlyId } from '../../../types';
|
||||||
|
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||||
|
|
||||||
function generateServices(serviceId, containerId, port) {
|
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||||
|
if (isHttp2) {
|
||||||
|
return {
|
||||||
|
[serviceId]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`${serviceId}-http2`]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `h2c://${containerId}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
[serviceId]: {
|
[serviceId]: {
|
||||||
loadbalancer: {
|
loadbalancer: {
|
||||||
@@ -14,43 +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 = {
|
let http: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let https: any = {
|
let https: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
tls: {
|
tls: {
|
||||||
certresolver: 'letsencrypt'
|
certresolver: 'letsencrypt'
|
||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let httpWWW: any = {
|
let httpWWW: any = {
|
||||||
entrypoints: ['web'],
|
entrypoints: ['web'],
|
||||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
let httpsWWW: any = {
|
let httpsWWW: any = {
|
||||||
entrypoints: ['websecure'],
|
entrypoints: ['websecure'],
|
||||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
rule: ruleWWW,
|
||||||
service: `${serviceId}`,
|
service: `${serviceId}`,
|
||||||
priority: 2,
|
priority: 2,
|
||||||
tls: {
|
tls: {
|
||||||
certresolver: 'letsencrypt'
|
certresolver: 'letsencrypt'
|
||||||
},
|
},
|
||||||
middlewares: []
|
middlewares: []
|
||||||
}
|
};
|
||||||
// 2. http + non-www only
|
// 2. http + non-www only
|
||||||
if (!isHttps && !isWWW) {
|
if (!isHttps && !isWWW) {
|
||||||
https.middlewares.push('redirect-to-http');
|
https.middlewares.push('redirect-to-http');
|
||||||
@@ -58,8 +95,8 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
|
|
||||||
httpWWW.middlewares.push('redirect-to-non-www');
|
httpWWW.middlewares.push('redirect-to-non-www');
|
||||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||||
delete https.tls
|
delete https.tls;
|
||||||
delete httpsWWW.tls
|
delete httpsWWW.tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. http + www only
|
// 3. http + www only
|
||||||
@@ -69,8 +106,8 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
|
|
||||||
http.middlewares.push('redirect-to-www');
|
http.middlewares.push('redirect-to-www');
|
||||||
https.middlewares.push('redirect-to-www');
|
https.middlewares.push('redirect-to-www');
|
||||||
delete https.tls
|
delete https.tls;
|
||||||
delete httpsWWW.tls
|
delete httpsWWW.tls;
|
||||||
}
|
}
|
||||||
// 5. https + non-www only
|
// 5. https + non-www only
|
||||||
if (isHttps && !isWWW) {
|
if (isHttps && !isWWW) {
|
||||||
@@ -86,17 +123,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
} else {
|
} else {
|
||||||
https.tls = true;
|
https.tls = true;
|
||||||
delete httpsWWW.tls.certresolver
|
delete httpsWWW.tls.certresolver;
|
||||||
httpsWWW.tls.domains = {
|
httpsWWW.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isDualCerts) {
|
if (!isDualCerts) {
|
||||||
delete httpsWWW.tls.certresolver
|
delete httpsWWW.tls.certresolver;
|
||||||
httpsWWW.tls.domains = {
|
httpsWWW.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,26 +151,59 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
|||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
} else {
|
} else {
|
||||||
httpsWWW.tls = true;
|
httpsWWW.tls = true;
|
||||||
delete https.tls.certresolver
|
delete https.tls.certresolver;
|
||||||
https.tls.domains = {
|
https.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isDualCerts) {
|
if (!isDualCerts) {
|
||||||
delete https.tls.certresolver
|
delete https.tls.certresolver;
|
||||||
https.tls.domains = {
|
https.tls.domains = {
|
||||||
main: domain
|
main: domain
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 {
|
return {
|
||||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||||
const traefik = {
|
const traefik = {
|
||||||
@@ -174,26 +244,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const coolifySettings = await prisma.setting.findFirst();
|
const coolifySettings = await prisma.setting.findFirst();
|
||||||
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
||||||
traefik.http.routers['catchall-http'] = {
|
traefik.http.routers['catchall-http'] = {
|
||||||
entrypoints: ["web"],
|
entrypoints: ['web'],
|
||||||
rule: "HostRegexp(`{catchall:.*}`)",
|
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||||
service: "noop",
|
service: 'noop',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
middlewares: ["redirect-regexp"]
|
middlewares: ['redirect-regexp']
|
||||||
}
|
};
|
||||||
traefik.http.routers['catchall-https'] = {
|
traefik.http.routers['catchall-https'] = {
|
||||||
entrypoints: ["websecure"],
|
entrypoints: ['websecure'],
|
||||||
rule: "HostRegexp(`{catchall:.*}`)",
|
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||||
service: "noop",
|
service: 'noop',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
middlewares: ["redirect-regexp"]
|
middlewares: ['redirect-regexp']
|
||||||
}
|
};
|
||||||
traefik.http.middlewares['redirect-regexp'] = {
|
traefik.http.middlewares['redirect-regexp'] = {
|
||||||
redirectregex: {
|
redirectregex: {
|
||||||
regex: '(.*)',
|
regex: '(.*)',
|
||||||
replacement: coolifySettings.proxyDefaultRedirect,
|
replacement: coolifySettings.proxyDefaultRedirect,
|
||||||
permanent: false
|
permanent: false
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
traefik.http.services['noop'] = {
|
traefik.http.services['noop'] = {
|
||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
servers: [
|
||||||
@@ -202,25 +272,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
const sslpath = '/etc/traefik/acme/custom';
|
const sslpath = '/etc/traefik/acme/custom';
|
||||||
|
|
||||||
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
let certificates = await prisma.certificate.findMany({
|
||||||
|
where: {
|
||||||
|
team: {
|
||||||
|
applications: { some: { settings: { isCustomSSL: true } } },
|
||||||
|
destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (remote) {
|
if (remote) {
|
||||||
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
certificates = await prisma.certificate.findMany({
|
||||||
|
where: {
|
||||||
|
team: {
|
||||||
|
applications: { some: { settings: { isCustomSSL: true } } },
|
||||||
|
destinationDocker: {
|
||||||
|
some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsedCertificates = []
|
let parsedCertificates = [];
|
||||||
for (const certificate of certificates) {
|
for (const certificate of certificates) {
|
||||||
parsedCertificates.push({
|
parsedCertificates.push({
|
||||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
if (parsedCertificates.length > 0) {
|
if (parsedCertificates.length > 0) {
|
||||||
traefik.tls.certificates = parsedCertificates
|
traefik.tls.certificates = parsedCertificates;
|
||||||
}
|
}
|
||||||
|
|
||||||
let applications = [];
|
let applications = [];
|
||||||
@@ -236,7 +322,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
serviceSetting: true,
|
serviceSetting: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' }
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
@@ -251,23 +337,25 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
destinationDocker: true,
|
destinationDocker: true,
|
||||||
persistentStorage: true,
|
persistentStorage: true,
|
||||||
serviceSecret: true,
|
serviceSecret: true,
|
||||||
serviceSetting: true,
|
serviceSetting: true
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (applications.length > 0) {
|
if (applications.length > 0) {
|
||||||
const dockerIds = new Set()
|
const dockerIds = new Set();
|
||||||
const runningContainers = {}
|
const runningContainers = {};
|
||||||
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||||
for (const dockerId of dockerIds) {
|
for (const dockerId of dockerIds) {
|
||||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
const { stdout: container } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
if (container) {
|
if (container) {
|
||||||
const containersArray = container.trim().split('\n');
|
const containersArray = container.trim().split('\n');
|
||||||
if (containersArray.length > 0) {
|
if (containersArray.length > 0) {
|
||||||
runningContainers[dockerId] = containersArray
|
runningContainers[dockerId] = containersArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,38 +377,54 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
if (
|
if (
|
||||||
!runningContainers[destinationDockerId] ||
|
!runningContainers[destinationDockerId] ||
|
||||||
runningContainers[destinationDockerId].length === 0 ||
|
runningContainers[destinationDockerId].length === 0 ||
|
||||||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id)).length === 0
|
runningContainers[destinationDockerId].filter((container) => container.startsWith(id))
|
||||||
|
.length === 0
|
||||||
) {
|
) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
if (buildPack === 'compose') {
|
if (buildPack === 'compose') {
|
||||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
const [key, value] = service
|
const [key, value] = service;
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
if (!value.fqdn || !value.port) {
|
if (!value.fqdn || !value.port) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { fqdn, port } = value
|
const { fqdn, port } = value;
|
||||||
const containerId = `${id}-${key}`
|
const containerId = `${id}-${key}`;
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false;
|
const isCustomSSL = false;
|
||||||
const dualCerts = false;
|
const dualCerts = false;
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
|
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, containerId, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { previews, dualCerts, isCustomSSL } = settings;
|
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
||||||
const { network, id: dockerId } = destinationDocker;
|
const { network, id: dockerId } = destinationDocker;
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
@@ -329,12 +433,31 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL,
|
||||||
|
isHttp2
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||||
|
};
|
||||||
if (previews) {
|
if (previews) {
|
||||||
const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
const { stdout } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||||
|
});
|
||||||
if (stdout) {
|
if (stdout) {
|
||||||
const containers = stdout
|
const containers = stdout
|
||||||
.trim()
|
.trim()
|
||||||
@@ -343,44 +466,57 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
.map((c) => c.replace(/"/g, ''));
|
.map((c) => c.replace(/"/g, ''));
|
||||||
if (containers.length > 0) {
|
if (containers.length > 0) {
|
||||||
for (const container of containers) {
|
for (const container of containers) {
|
||||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator}${domain}`;
|
const previewDomain = `${container.split('-')[1]}${
|
||||||
|
coolifySettings.previewSeparator
|
||||||
|
}${domain}`;
|
||||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const serviceId = `${container}-${port || 'default'}`
|
const serviceId = `${container}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
previewDomain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, container, port, isHttp2)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
const dockerIds = new Set()
|
const dockerIds = new Set();
|
||||||
const runningContainers = {}
|
const runningContainers = {};
|
||||||
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||||
for (const dockerId of dockerIds) {
|
for (const dockerId of dockerIds) {
|
||||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
const { stdout: container } = await executeCommand({
|
||||||
|
dockerId,
|
||||||
|
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||||
|
});
|
||||||
if (container) {
|
if (container) {
|
||||||
const containersArray = container.trim().split('\n');
|
const containersArray = container.trim().split('\n');
|
||||||
if (containersArray.length > 0) {
|
if (containersArray.length > 0) {
|
||||||
runningContainers[dockerId] = containersArray
|
runningContainers[dockerId] = containersArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const service of services) {
|
for (const service of services) {
|
||||||
try {
|
try {
|
||||||
let {
|
let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
destinationDockerId,
|
|
||||||
dualCerts,
|
|
||||||
serviceSetting
|
|
||||||
} = service;
|
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -392,7 +528,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
runningContainers[destinationDockerId].length === 0 ||
|
runningContainers[destinationDockerId].length === 0 ||
|
||||||
!runningContainers[destinationDockerId].includes(id)
|
!runningContainers[destinationDockerId].includes(id)
|
||||||
) {
|
) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const templates = await getTemplates();
|
const templates = await getTemplates();
|
||||||
let found = templates.find((a) => a.type === type);
|
let found = templates.find((a) => a.type === type);
|
||||||
@@ -401,88 +537,144 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
}
|
}
|
||||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||||
for (const oneService of Object.keys(found.services)) {
|
for (const oneService of Object.keys(found.services)) {
|
||||||
const isDomainConfiguration = found?.services[oneService]?.proxy?.filter(p => p.domain) ?? [];
|
const isDomainAndProxyConfiguration =
|
||||||
if (isDomainConfiguration.length > 0) {
|
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||||
const { proxy } = found.services[oneService];
|
if (isDomainAndProxyConfiguration.length > 0) {
|
||||||
|
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||||
|
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||||
for (let configuration of proxy) {
|
for (let configuration of proxy) {
|
||||||
if (configuration.domain) {
|
if (configuration.domain) {
|
||||||
const setting = serviceSetting.find((a) => a.variableName === configuration.domain);
|
const setting = serviceSetting.find(
|
||||||
|
(a) => a.variableName === configuration.domain
|
||||||
|
);
|
||||||
if (setting) {
|
if (setting) {
|
||||||
configuration.domain = configuration.domain.replace(configuration.domain, setting.value);
|
configuration.domain = configuration.domain.replace(
|
||||||
|
configuration.domain,
|
||||||
|
setting.value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
const foundPortVariable = serviceSetting.find(
|
||||||
|
(a) => a.name.toLowerCase() === 'port'
|
||||||
|
);
|
||||||
if (foundPortVariable) {
|
if (foundPortVariable) {
|
||||||
configuration.port = foundPortVariable.value
|
configuration.port = foundPortVariable.value;
|
||||||
}
|
}
|
||||||
let port, pathPrefix, customDomain;
|
let port, pathPrefix, customDomain;
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
port = configuration?.port;
|
port = configuration?.port;
|
||||||
pathPrefix = configuration?.pathPrefix || '/';
|
pathPrefix = configuration?.pathPrefix || '/';
|
||||||
customDomain = configuration?.domain
|
customDomain = configuration?.domain;
|
||||||
}
|
}
|
||||||
if (customDomain) {
|
if (customDomain) {
|
||||||
fqdn = customDomain
|
fqdn = customDomain;
|
||||||
} else {
|
} else {
|
||||||
fqdn = service.fqdn
|
fqdn = service.fqdn;
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const isCustomSSL = false;
|
const isCustomSSL = false;
|
||||||
const serviceId = `${oneService}-${port || 'default'}`
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, oneService, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
||||||
for (let [index, port] of found.services[oneService].ports.entries()) {
|
for (let [index, port] of found.services[oneService].ports.entries()) {
|
||||||
if (port == 22) continue;
|
if (port == 22) continue;
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
const foundPortVariable = serviceSetting.find(
|
||||||
|
(a) => a.name.toLowerCase() === 'port'
|
||||||
|
);
|
||||||
if (foundPortVariable) {
|
if (foundPortVariable) {
|
||||||
port = foundPortVariable.value
|
port = foundPortVariable.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false
|
const isCustomSSL = false;
|
||||||
const serviceId = `${oneService}-${port || 'default'}`
|
const serviceId = `${oneService}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, id, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!remote) {
|
if (!remote) {
|
||||||
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
||||||
if (!fqdn) {
|
if (!fqdn) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
const isWWW = fqdn.includes('www.');
|
const isWWW = fqdn.includes('www.');
|
||||||
const id = isDev ? 'host.docker.internal' : 'coolify'
|
const id = isDev ? 'host.docker.internal' : 'coolify';
|
||||||
const container = isDev ? 'host.docker.internal' : 'coolify'
|
const container = isDev ? 'host.docker.internal' : 'coolify';
|
||||||
const port = 3000
|
const port = 3000;
|
||||||
const pathPrefix = '/'
|
const pathPrefix = '/';
|
||||||
const isCustomSSL = false
|
const isCustomSSL = false;
|
||||||
const serviceId = `${id}-${port || 'default'}`
|
const serviceId = `${id}-${port || 'default'}`;
|
||||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
traefik.http.routers = {
|
||||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
...traefik.http.routers,
|
||||||
|
...generateRouters(
|
||||||
|
serviceId,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
pathPrefix,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
dualCerts,
|
||||||
|
isCustomSSL
|
||||||
|
)
|
||||||
|
};
|
||||||
|
traefik.http.services = {
|
||||||
|
...traefik.http.services,
|
||||||
|
...generateServices(serviceId, container, port)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
} finally {
|
} finally {
|
||||||
if (Object.keys(traefik.http.routers).length === 0) {
|
if (Object.keys(traefik.http.routers).length === 0) {
|
||||||
traefik.http.routers = null;
|
traefik.http.routers = null;
|
||||||
@@ -496,9 +688,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
|||||||
|
|
||||||
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.query
|
const { id } = request.query;
|
||||||
if (id) {
|
if (id) {
|
||||||
const { privatePort, publicPort, type, address = id } = request.query
|
const { privatePort, publicPort, type, address = id } = request.query;
|
||||||
let traefik = {};
|
let traefik = {};
|
||||||
if (publicPort && type && privatePort) {
|
if (publicPort && type && privatePort) {
|
||||||
if (type === 'tcp') {
|
if (type === 'tcp') {
|
||||||
@@ -559,18 +751,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...traefik
|
...traefik
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw { status: 500 }
|
throw { status: 500 };
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
return errorHandler({ status, message })
|
return errorHandler({ status, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
|||||||
import { OtherProxyConfiguration } from './types';
|
import { OtherProxyConfiguration } from './types';
|
||||||
|
|
||||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) =>
|
||||||
|
otherProxyConfiguration(request)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
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
@@ -1,20 +0,0 @@
|
|||||||
import { addToast } from './store';
|
|
||||||
|
|
||||||
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
|
|
||||||
|
|
||||||
export function errorNotification(error: any | { message: string }): void {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
addToast({
|
|
||||||
message: error.message,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addToast({
|
|
||||||
message: error,
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export function getRndInteger(min: number, max: number) {
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
@@ -1,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();
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 {};
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -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 {};
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -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 };
|
|
||||||
}
|
|
||||||
@@ -42,9 +42,13 @@
|
|||||||
"@trpc/server": "10.1.0",
|
"@trpc/server": "10.1.0",
|
||||||
"cuid": "2.1.8",
|
"cuid": "2.1.8",
|
||||||
"daisyui": "2.41.0",
|
"daisyui": "2.41.0",
|
||||||
|
"dayjs": "1.11.6",
|
||||||
"flowbite-svelte": "0.28.0",
|
"flowbite-svelte": "0.28.0",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.1",
|
||||||
|
"js-yaml": "4.1.0",
|
||||||
|
"p-limit": "4.0.0",
|
||||||
"server": "workspace:*",
|
"server": "workspace:*",
|
||||||
"superjson": "1.11.0"
|
"superjson": "1.11.0",
|
||||||
|
"svelte-select": "4.4.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,3 +7,6 @@ declare namespace App {
|
|||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare const GITPOD_WORKSPACE_URL: string;
|
||||||
|
declare const CODESANDBOX_HOST: string;
|
||||||
210
apps/trpc-experimental/client/src/lib/common.ts
Normal file
210
apps/trpc-experimental/client/src/lib/common.ts
Normal 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
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<span class="badge bg-coollabs-gradient rounded text-white font-normal"> BETA </span>
|
||||||
@@ -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>
|
||||||
@@ -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}
|
||||||
@@ -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>
|
||||||
@@ -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 |
@@ -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}
|
||||||
@@ -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>
|
||||||
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
|
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
Reference in New Issue
Block a user