mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-31 12:34:03 +00:00
Compare commits
181 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 | ||
|
|
cb1d86d08b | ||
|
|
88f3f628ef | ||
|
|
295bea37bc | ||
|
|
bd7d756254 | ||
|
|
4261147fe8 | ||
|
|
a70adc5eb3 | ||
|
|
0d51b04d79 | ||
|
|
06d40b8a81 | ||
|
|
2358510cba | ||
|
|
e6d13cb7d7 | ||
|
|
39e21c3f36 | ||
|
|
8da900ee72 | ||
|
|
9f4e81a1a3 | ||
|
|
0b918c2f51 | ||
|
|
085cd2a314 | ||
|
|
98d2399568 | ||
|
|
515d9a0008 | ||
|
|
aece1fa7d3 | ||
|
|
abc614ecfd | ||
|
|
1180d3fdde | ||
|
|
1639d1725a | ||
|
|
5df1deecbc | ||
|
|
fe3c0cf76e | ||
|
|
cc0df0182c | ||
|
|
02c530dcbe | ||
|
|
379b1de64f | ||
|
|
f3ff324925 | ||
|
|
0f2160222f | ||
|
|
ce3750c51c | ||
|
|
72a7ea6e91 | ||
|
|
4ad7e1f8e6 | ||
|
|
2007ba0c3b | ||
|
|
2009dc11db | ||
|
|
62f2196a0c | ||
|
|
e63c65da4f | ||
|
|
570a082227 | ||
|
|
c445fc0f8a | ||
|
|
699493cf24 | ||
|
|
6c89686f31 |
@@ -8,7 +8,6 @@ package
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
local-serve
|
||||
apps/api/db/migration.db-journal
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
10
.github/ISSUE_TEMPLATE/--bug-report.yaml
vendored
@@ -9,13 +9,21 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form in English
|
||||
Thanks for taking the time to fill out this bug report! Please fill the form in English.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: input
|
||||
id: repository
|
||||
attributes:
|
||||
label: Example public repository
|
||||
description: "An example public git repository to reproduce the issue easily (if applicable)."
|
||||
placeholder: "ex: https://github.com/coollabsio/coolify"
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
|
||||
14
.github/workflows/pocketbase-release.yml
vendored
14
.github/workflows/pocketbase-release.yml
vendored
@@ -3,11 +3,11 @@ name: pocketbase-release
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "others/pocketbase"
|
||||
- "others/pocketbase/*"
|
||||
- ".github/workflows/pocketbase-release.yml"
|
||||
branches:
|
||||
- next
|
||||
|
||||
- main
|
||||
jobs:
|
||||
arm64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.8.0-arm64
|
||||
tags: coollabsio/pocketbase:0.11.0-arm64
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.8.0-amd64
|
||||
tags: coollabsio/pocketbase:0.11.0-amd64
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.8.0-aarch64
|
||||
tags: coollabsio/pocketbase:0.11.0-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [amd64, arm64, aarch64]
|
||||
@@ -89,5 +89,5 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/pocketbase:0.8.0 --amend coollabsio/pocketbase:0.8.0-amd64 --amend coollabsio/pocketbase:0.8.0-arm64 --amend coollabsio/pocketbase:0.8.0-aarch64
|
||||
docker manifest push coollabsio/pocketbase:0.8.0
|
||||
docker manifest create coollabsio/pocketbase:0.11.0 --amend coollabsio/pocketbase:0.11.0-amd64 --amend coollabsio/pocketbase:0.11.0-arm64 --amend coollabsio/pocketbase:0.11.0-aarch64
|
||||
docker manifest push coollabsio/pocketbase:0.11.0
|
||||
|
||||
8
.github/workflows/production-release.yml
vendored
8
.github/workflows/production-release.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64
|
||||
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
|
||||
aarch64:
|
||||
@@ -103,8 +103,10 @@ jobs:
|
||||
id: package-version
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
|
||||
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
docker buildx imagetools create --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 --tag coollabsio/coolify:${{steps.package-version.outputs.current-version}}
|
||||
docker tag coollabsio/coolify:${{steps.package-version.outputs.current-version}} coollabsio/coolify:latest
|
||||
docker push coollabsio/coolify:latest
|
||||
docker buildx imagetools create --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --append coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 --tag coollabsio/coolify:latest
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
5
.github/workflows/staging-release.yml
vendored
5
.github/workflows/staging-release.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/coolify:next-amd64
|
||||
tags: coollabsio/coolify:next
|
||||
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64
|
||||
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max
|
||||
merge-manifest:
|
||||
@@ -85,8 +85,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64
|
||||
docker manifest push coollabsio/coolify:next
|
||||
docker buildx imagetools create --append coollabsio/coolify:next-arm64 --tag coollabsio/coolify:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,20 +1,25 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
.pnpm-store
|
||||
build
|
||||
/apps/ui/build
|
||||
/build
|
||||
.svelte-kit
|
||||
package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
dist
|
||||
client
|
||||
apps/api/db/*.db
|
||||
apps/api/db/migration.db-journal
|
||||
apps/api/core*
|
||||
apps/server/build
|
||||
apps/backup/backups/*
|
||||
!apps/backup/backups/.gitkeep
|
||||
logs
|
||||
/logs
|
||||
others/certificates
|
||||
backups/*
|
||||
!backups/.gitkeep
|
||||
!backups/.gitkeep
|
||||
|
||||
# Trpc
|
||||
apps/server/db/*.db
|
||||
apps/server/db/*.db-journal
|
||||
4
apps/api/.gitignore
vendored
4
apps/api/.gitignore
vendored
@@ -8,4 +8,6 @@ package
|
||||
!.env.example
|
||||
dist
|
||||
dev.db
|
||||
client
|
||||
client
|
||||
testTemplate.yaml
|
||||
testTags.json
|
||||
BIN
apps/api/db/dev.db.bak
Normal file
BIN
apps/api/db/dev.db.bak
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,5 +1,229 @@
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: '0.8.0'
|
||||
defaultVersion: "9.22"
|
||||
documentation: https://docs.directus.io/getting-started/introduction.html
|
||||
type: directus-postgresql
|
||||
name: Directus
|
||||
subname: (PostgreSQL)
|
||||
description: >-
|
||||
Directus is a free and open-source headless CMS framework for managing custom SQL-based databases.
|
||||
labels:
|
||||
- CMS
|
||||
- headless
|
||||
services:
|
||||
$$id:
|
||||
name: Directus
|
||||
depends_on:
|
||||
- $$id-postgresql
|
||||
- $$id-redis
|
||||
image: directus/directus:$$core_version
|
||||
volumes:
|
||||
- $$id-uploads:/directus/uploads
|
||||
- $$id-database:/directus/database
|
||||
- $$id-extensions:/directus/extensions
|
||||
environment:
|
||||
- KEY=$$secret_key
|
||||
- SECRET=$$secret_secret
|
||||
- DB_CLIENT=pg
|
||||
- DB_CONNECTION_STRING=$$secret_db_connection_string
|
||||
- CACHE_ENABLED=true
|
||||
- CACHE_STORE=redis
|
||||
- CACHE_REDIS=$$secret_cache_redis
|
||||
- ADMIN_EMAIL=$$config_admin_email
|
||||
- ADMIN_PASSWORD=$$secret_admin_password
|
||||
- CACHE_AUTO_PURGE=true
|
||||
- PUBLIC_URL=$$config_public_url
|
||||
ports:
|
||||
- "8055"
|
||||
$$id-postgresql:
|
||||
name: Directus PostgreSQL
|
||||
depends_on: []
|
||||
image: postgres:14-alpine
|
||||
volumes:
|
||||
- $$id-postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=$$config_postgres_user
|
||||
- POSTGRES_PASSWORD=$$secret_postgres_password
|
||||
- POSTGRES_DB=$$config_postgres_db
|
||||
ports: []
|
||||
$$id-redis:
|
||||
name: Directus Redis
|
||||
depends_on: []
|
||||
image: redis:7.0.4-alpine
|
||||
command: "--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5"
|
||||
volumes:
|
||||
- "$$id-redis:/data"
|
||||
environment: []
|
||||
variables:
|
||||
- id: $$config_public_url
|
||||
name: PUBLIC_URL
|
||||
label: Public URL
|
||||
defaultValue: $$generate_fqdn
|
||||
description: ""
|
||||
- id: $$secret_db_connection_string
|
||||
name: DB_CONNECTION_STRING
|
||||
label: Directus Database Url
|
||||
defaultValue: postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db
|
||||
description: ""
|
||||
- id: $$config_postgres_db
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_DB
|
||||
label: Database
|
||||
defaultValue: directus
|
||||
description: ""
|
||||
- id: $$config_postgres_user
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_USER
|
||||
label: User
|
||||
defaultValue: $$generate_username
|
||||
description: ""
|
||||
- id: $$secret_postgres_password
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_PASSWORD
|
||||
label: Password
|
||||
defaultValue: $$generate_password
|
||||
description: ""
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_cache_redis
|
||||
name: CACHE_REDIS
|
||||
label: Redis Url
|
||||
defaultValue: redis://$$id-redis:6379
|
||||
description: ""
|
||||
- id: $$config_admin_email
|
||||
name: ADMIN_EMAIL
|
||||
label: Initial Admin Email
|
||||
defaultValue: "admin@example.com"
|
||||
description: "The email address of the first user that is automatically created. You can change it later in Directus."
|
||||
- id: $$secret_admin_password
|
||||
name: ADMIN_PASSWORD
|
||||
label: Initial Admin Password
|
||||
defaultValue: $$generate_password
|
||||
description: "The password of the first user that is automatically created."
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_key
|
||||
name: KEY
|
||||
label: Key
|
||||
defaultValue: $$generate_password
|
||||
description: "Unique identifier for the project."
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_secret
|
||||
name: SECRET
|
||||
label: Secret
|
||||
defaultValue: $$generate_password
|
||||
description: "Secret string for the project."
|
||||
showOnConfiguration: true
|
||||
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v1.3.8
|
||||
documentation: https://github.com/LibreTranslate/LibreTranslate
|
||||
description: Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup.
|
||||
type: libretranslate
|
||||
name: Libretranslate
|
||||
labels:
|
||||
- translator
|
||||
- argos
|
||||
- python
|
||||
- libretranslate
|
||||
services:
|
||||
$$id:
|
||||
name: Libretranslate
|
||||
image: libretranslate/libretranslate:$$core_version
|
||||
environment:
|
||||
- LT_HOST=0.0.0.0
|
||||
- LT_SUGGESTIONS=true
|
||||
- LT_CHAR_LIMIT=$$config_lt_char_limit
|
||||
- LT_REQ_LIMIT=$$config_lt_req_limit
|
||||
- LT_BATCH_LIMIT=$$config_lt_batch_limit
|
||||
- LT_GA_ID=$$config_lt_ga_id
|
||||
- LT_DISABLE_WEB_UI=$$config_lt_web_ui
|
||||
volumes:
|
||||
- $$id-libretranslate:/libretranslate
|
||||
ports:
|
||||
- "5000"
|
||||
variables:
|
||||
- id: $$config_lt_char_limit
|
||||
name: LT_CHAR_LIMIT
|
||||
label: Char limit
|
||||
defaultValue: "5000"
|
||||
description: "Set character limit."
|
||||
- id: $$config_lt_req_limit
|
||||
name: LT_REQ_LIMIT
|
||||
label: Request limit
|
||||
defaultValue: "5000"
|
||||
description: "Set maximum number of requests per minute per client."
|
||||
- id: $$config_lt_batch_limit
|
||||
name: LT_BATCH_LIMIT
|
||||
label: Batch Limit
|
||||
defaultValue: "5000"
|
||||
description: "Set maximum number of texts to translate in a batch request."
|
||||
- id: $$config_lt_ga_id
|
||||
name: LT_GA_ID
|
||||
label: Google Analytics ID
|
||||
defaultValue: ""
|
||||
description: "Enable Google Analytics on the API client page by providing an ID"
|
||||
- id: $$config_lt_web_ui
|
||||
name: LT_DISABLE_WEB_UI
|
||||
label: Web UI
|
||||
defaultValue: "false"
|
||||
description: "Disable or enable web ui. True or false."
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.8.0
|
||||
documentation: https://github.com/benbusby/whoogle-search
|
||||
type: whoogle
|
||||
name: Whoogle Search
|
||||
description: A self-hosted, ad-free, privacy-respecting metasearch engine
|
||||
labels:
|
||||
- search
|
||||
- google
|
||||
services:
|
||||
$$id:
|
||||
name: Whoogle Search
|
||||
documentation: https://github.com/benbusby/whoogle-search
|
||||
depends_on: []
|
||||
image: benbusby/whoogle-search:$$core_version
|
||||
cap_drop:
|
||||
- ALL
|
||||
environment:
|
||||
- WHOOGLE_USER=$$config_whoogle_username
|
||||
- WHOOGLE_PASS=$$secret_whoogle_password
|
||||
- WHOOGLE_CONFIG_PREFERENCES_KEY=$$secret_whoogle_preferences_key
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
ports:
|
||||
- "5000"
|
||||
variables:
|
||||
- id: $$config_whoogle_username
|
||||
name: WHOOGLE_USER
|
||||
label: Whoogle User
|
||||
defaultValue: $$generate_username
|
||||
description: "Username to log into Whoogle"
|
||||
- id: $$secret_whoogle_password
|
||||
name: WHOOGLE_PASSWORD
|
||||
label: Whoogle Password
|
||||
defaultValue: $$generate_password
|
||||
description: "Password to log into Whoogle"
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_whoogle_preferences_key
|
||||
name: WHOOGLE_CONFIG_PREFERENCES_KEY
|
||||
label: Whoogle preferences key
|
||||
defaultValue: $$generate_password
|
||||
description: "password to encrypt preferences"
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.1.3
|
||||
documentation: https://docs.openblocks.dev/
|
||||
type: openblocks
|
||||
name: Openblocks
|
||||
description: The Open Source Retool Alternative
|
||||
services:
|
||||
$$id:
|
||||
image: openblocksdev/openblocks-ce:$$core_version
|
||||
volumes:
|
||||
- $$id-stacks-data:/openblocks-stacks
|
||||
ports:
|
||||
- "3000"
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "0.11.0"
|
||||
documentation: https://pocketbase.io/docs/
|
||||
type: pocketbase
|
||||
name: Pocketbase
|
||||
@@ -12,7 +236,7 @@
|
||||
ports:
|
||||
- "8080"
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.5.0-rc.0
|
||||
defaultVersion: v1.5.1
|
||||
documentation: https://plausible.io/doc/
|
||||
type: plausibleanalytics-arm
|
||||
name: Plausible Analytics (ARM)
|
||||
@@ -124,12 +348,12 @@
|
||||
description: ""
|
||||
- id: $$config_disable_auth
|
||||
name: DISABLE_AUTH
|
||||
label: Disable Authentication
|
||||
label: Authentication
|
||||
defaultValue: "false"
|
||||
description: ""
|
||||
- id: $$config_disable_registration
|
||||
name: DISABLE_REGISTRATION
|
||||
label: Disable Registration
|
||||
label: Registration
|
||||
defaultValue: "true"
|
||||
description: ""
|
||||
- id: $$config_postgres_user
|
||||
@@ -157,7 +381,7 @@
|
||||
defaultValue: plausible.js
|
||||
description: This is the default script name.
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "1.17"
|
||||
defaultVersion: "1.18"
|
||||
documentation: https://docs.gitea.io
|
||||
type: gitea
|
||||
name: Gitea
|
||||
@@ -313,7 +537,7 @@
|
||||
defaultValue: keycloak
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v3.6
|
||||
defaultVersion: v3.7
|
||||
documentation: https://github.com/freyacodes/Lavalink
|
||||
description: Standalone audio sending node based on Lavaplayer.
|
||||
type: lavalink
|
||||
@@ -332,12 +556,12 @@
|
||||
volumes:
|
||||
- $$id-lavalink:/lavalink
|
||||
ports:
|
||||
- "2333"
|
||||
- $$config_port
|
||||
files:
|
||||
- location: /opt/Lavalink/application.yml
|
||||
content: >-
|
||||
server:
|
||||
port: $$config_port
|
||||
port: 2333
|
||||
address: 0.0.0.0
|
||||
lavalink:
|
||||
server:
|
||||
@@ -364,18 +588,13 @@
|
||||
max-file-size: 1GB
|
||||
max-history: 30
|
||||
variables:
|
||||
- id: $$config_port
|
||||
name: PORT
|
||||
label: Port
|
||||
defaultValue: "2333"
|
||||
required: true
|
||||
- id: $$secret_password
|
||||
name: PASSWORD
|
||||
label: Password
|
||||
defaultValue: $$generate_password
|
||||
required: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v1.8.6
|
||||
defaultVersion: v1.9.3
|
||||
documentation: https://docs.appsmith.com/getting-started/setup/instance-configuration/
|
||||
type: appsmith
|
||||
name: Appsmith
|
||||
@@ -408,7 +627,7 @@
|
||||
defaultValue: "true"
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.56.2
|
||||
defaultVersion: 0.58.4
|
||||
documentation: https://hub.docker.com/r/zadam/trilium
|
||||
description: "A hierarchical note taking application with focus on building large personal knowledge bases."
|
||||
labels:
|
||||
@@ -428,7 +647,7 @@
|
||||
- "8080"
|
||||
variables: []
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.9.2
|
||||
defaultVersion: 1.19.4
|
||||
documentation: https://hub.docker.com/r/louislam/uptime-kuma
|
||||
description: A free & fancy self-hosted monitoring tool.
|
||||
labels:
|
||||
@@ -445,7 +664,7 @@
|
||||
- "3001"
|
||||
variables: []
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "5.8"
|
||||
defaultVersion: "6.0"
|
||||
documentation: https://hub.docker.com/r/silviof/docker-languagetool
|
||||
description: "A multilingual grammar, style and spell checker."
|
||||
type: languagetool
|
||||
@@ -460,7 +679,7 @@
|
||||
- "8010"
|
||||
variables: []
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.26.0
|
||||
defaultVersion: 1.27.0
|
||||
documentation: https://hub.docker.com/r/vaultwarden/server
|
||||
description: "Bitwarden compatible server written in Rust."
|
||||
type: vaultwarden
|
||||
@@ -478,7 +697,7 @@
|
||||
- "80"
|
||||
variables: []
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 9.2.3
|
||||
defaultVersion: 9.3.2
|
||||
documentation: https://hub.docker.com/r/grafana/grafana
|
||||
type: grafana
|
||||
name: Grafana
|
||||
@@ -499,7 +718,7 @@
|
||||
- "3000"
|
||||
variables: []
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.0.3
|
||||
defaultVersion: 1.2.0
|
||||
documentation: https://appwrite.io/docs
|
||||
type: appwrite
|
||||
name: Appwrite
|
||||
@@ -646,6 +865,7 @@
|
||||
- "$$id-functions:/storage/functions"
|
||||
- "$$id-builds:/storage/builds"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
- "/tmp:/tmp:rw"
|
||||
entrypoint: executor
|
||||
"$$id-influxdb":
|
||||
image: appwrite/influxdb:1.5.0
|
||||
@@ -1668,7 +1888,7 @@
|
||||
defaultValue: weblate
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 2022.10.14-1a5b0965
|
||||
defaultVersion: 2023.01.15-52d41559
|
||||
documentation: https://docs.searxng.org/
|
||||
type: searxng
|
||||
name: SearXNG
|
||||
@@ -1741,7 +1961,7 @@
|
||||
defaultValue: $$generate_password
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v2.0.6
|
||||
defaultVersion: v3.0.2
|
||||
documentation: https://glitchtip.com/documentation
|
||||
type: glitchtip
|
||||
name: GlitchTip
|
||||
@@ -1963,7 +2183,7 @@
|
||||
defaultValue: glitchtip
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v2.13.0
|
||||
defaultVersion: v2.16.1
|
||||
documentation: https://hasura.io/docs/latest/index/
|
||||
type: hasura
|
||||
name: Hasura
|
||||
@@ -2030,7 +2250,7 @@
|
||||
defaultValue: hasura
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: postgresql-v1.38.0
|
||||
defaultVersion: postgresql-v1.39.5
|
||||
documentation: https://umami.is/docs/getting-started
|
||||
type: umami-postgresql
|
||||
name: Umami
|
||||
@@ -2074,10 +2294,10 @@
|
||||
"is_admin" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
PRIMARY KEY ("user_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "event" (
|
||||
"event_id" SERIAL NOT NULL,
|
||||
@@ -2087,10 +2307,10 @@
|
||||
"url" VARCHAR(500) NOT NULL,
|
||||
"event_type" VARCHAR(50) NOT NULL,
|
||||
"event_value" VARCHAR(50) NOT NULL,
|
||||
|
||||
|
||||
PRIMARY KEY ("event_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "pageview" (
|
||||
"view_id" SERIAL NOT NULL,
|
||||
@@ -2099,10 +2319,10 @@
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
"url" VARCHAR(500) NOT NULL,
|
||||
"referrer" VARCHAR(500),
|
||||
|
||||
|
||||
PRIMARY KEY ("view_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"session_id" SERIAL NOT NULL,
|
||||
@@ -2116,10 +2336,10 @@
|
||||
"screen" VARCHAR(11),
|
||||
"language" VARCHAR(35),
|
||||
"country" CHAR(2),
|
||||
|
||||
|
||||
PRIMARY KEY ("session_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "website" (
|
||||
"website_id" SERIAL NOT NULL,
|
||||
@@ -2129,73 +2349,73 @@
|
||||
"domain" VARCHAR(500),
|
||||
"share_id" VARCHAR(64),
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
PRIMARY KEY ("website_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "account.username_unique" ON "account"("username");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_created_at_idx" ON "event"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_session_id_idx" ON "event"("session_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_website_id_idx" ON "event"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_created_at_idx" ON "pageview"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_session_id_idx" ON "pageview"("session_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_created_at_idx" ON "pageview"("website_id", "created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_idx" ON "pageview"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_session_id_created_at_idx" ON "pageview"("website_id", "session_id", "created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "session.session_uuid_unique" ON "session"("session_uuid");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_created_at_idx" ON "session"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_website_id_idx" ON "session"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "website.website_uuid_unique" ON "website"("website_uuid");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "website.share_id_unique" ON "website"("share_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "website_user_id_idx" ON "website"("user_id");
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "pageview" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "pageview" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "website" ADD FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);
|
||||
variables:
|
||||
- id: $$secret_database_url
|
||||
@@ -2237,7 +2457,7 @@
|
||||
showOnConfiguration: true
|
||||
- templateVersion: 1.0.0
|
||||
ignore: true
|
||||
defaultVersion: postgresql-v1.38.0
|
||||
defaultVersion: postgresql-v1.39.5
|
||||
documentation: https://umami.is/docs/getting-started
|
||||
type: umami
|
||||
name: Umami
|
||||
@@ -2281,10 +2501,10 @@
|
||||
"is_admin" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
PRIMARY KEY ("user_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "event" (
|
||||
"event_id" SERIAL NOT NULL,
|
||||
@@ -2294,10 +2514,10 @@
|
||||
"url" VARCHAR(500) NOT NULL,
|
||||
"event_type" VARCHAR(50) NOT NULL,
|
||||
"event_value" VARCHAR(50) NOT NULL,
|
||||
|
||||
|
||||
PRIMARY KEY ("event_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "pageview" (
|
||||
"view_id" SERIAL NOT NULL,
|
||||
@@ -2306,10 +2526,10 @@
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
"url" VARCHAR(500) NOT NULL,
|
||||
"referrer" VARCHAR(500),
|
||||
|
||||
|
||||
PRIMARY KEY ("view_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "session" (
|
||||
"session_id" SERIAL NOT NULL,
|
||||
@@ -2323,10 +2543,10 @@
|
||||
"screen" VARCHAR(11),
|
||||
"language" VARCHAR(35),
|
||||
"country" CHAR(2),
|
||||
|
||||
|
||||
PRIMARY KEY ("session_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "website" (
|
||||
"website_id" SERIAL NOT NULL,
|
||||
@@ -2336,73 +2556,73 @@
|
||||
"domain" VARCHAR(500),
|
||||
"share_id" VARCHAR(64),
|
||||
"created_at" TIMESTAMPTZ(6) DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
|
||||
PRIMARY KEY ("website_id")
|
||||
);
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "account.username_unique" ON "account"("username");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_created_at_idx" ON "event"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_session_id_idx" ON "event"("session_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "event_website_id_idx" ON "event"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_created_at_idx" ON "pageview"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_session_id_idx" ON "pageview"("session_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_created_at_idx" ON "pageview"("website_id", "created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_idx" ON "pageview"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "pageview_website_id_session_id_created_at_idx" ON "pageview"("website_id", "session_id", "created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "session.session_uuid_unique" ON "session"("session_uuid");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_created_at_idx" ON "session"("created_at");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "session_website_id_idx" ON "session"("website_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "website.website_uuid_unique" ON "website"("website_uuid");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "website.share_id_unique" ON "website"("share_id");
|
||||
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "website_user_id_idx" ON "website"("user_id");
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "event" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "pageview" ADD FOREIGN KEY ("session_id") REFERENCES "session"("session_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "pageview" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "session" ADD FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "website" ADD FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
insert into account (username, password, is_admin) values ('admin', '$$hashed$$secret_admin_password', true);
|
||||
variables:
|
||||
- id: $$secret_database_url
|
||||
@@ -2443,7 +2663,7 @@
|
||||
description: ""
|
||||
showOnConfiguration: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v0.29.1
|
||||
defaultVersion: v0.30.5
|
||||
documentation: https://docs.meilisearch.com/learn/getting_started/quick_start.html
|
||||
type: meilisearch
|
||||
name: MeiliSearch
|
||||
@@ -2473,7 +2693,7 @@
|
||||
showOnConfiguration: true
|
||||
- templateVersion: 1.0.0
|
||||
ignore: true
|
||||
defaultVersion: latest
|
||||
defaultVersion: 5.30.0
|
||||
documentation: https://docs.ghost.org
|
||||
arch: amd64
|
||||
type: ghost-mariadb
|
||||
@@ -2591,7 +2811,7 @@
|
||||
defaultValue: $$generate_password
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "5.22"
|
||||
defaultVersion: 5.30.0
|
||||
documentation: https://docs.ghost.org
|
||||
type: ghost-only
|
||||
name: Ghost
|
||||
@@ -2655,7 +2875,7 @@
|
||||
placeholder: "ghost_db"
|
||||
required: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "5.22"
|
||||
defaultVersion: 5.30.0
|
||||
documentation: https://docs.ghost.org
|
||||
type: ghost-mysql
|
||||
name: Ghost
|
||||
@@ -2732,7 +2952,7 @@
|
||||
defaultValue: $$generate_password
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: php8.1
|
||||
defaultVersion: php8.2
|
||||
documentation: https://wordpress.org/
|
||||
type: wordpress
|
||||
name: WordPress
|
||||
@@ -2822,7 +3042,7 @@
|
||||
description: ""
|
||||
readOnly: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: php8.1
|
||||
defaultVersion: php8.2
|
||||
documentation: https://wordpress.org/
|
||||
type: wordpress-only
|
||||
name: WordPress
|
||||
@@ -2896,7 +3116,7 @@
|
||||
define('WP_DEBUG_DISPLAY', false);
|
||||
@ini_set('display_errors', 0);
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 4.7.1
|
||||
defaultVersion: 4.9.1
|
||||
documentation: https://coder.com/docs/coder-oss/latest
|
||||
type: vscodeserver
|
||||
name: VSCode Server
|
||||
@@ -2911,7 +3131,6 @@
|
||||
depends_on: []
|
||||
image: "codercom/code-server:$$core_version"
|
||||
volumes:
|
||||
- "$$id-config-data:/home/coder/.local/share/code-server"
|
||||
- "$$id-vscodeserver-data:/home/coder"
|
||||
- "$$id-keys-directory:/root/.ssh"
|
||||
- "$$id-theme-and-plugin-directory:/root/.local/share/code-server"
|
||||
@@ -2927,7 +3146,7 @@
|
||||
description: ""
|
||||
showOnConfiguration: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: RELEASE.2022-10-15T19-57-03Z
|
||||
defaultVersion: RELEASE.2023-01-12T02-06-16Z
|
||||
documentation: https://min.io/docs/minio
|
||||
type: minio
|
||||
name: MinIO
|
||||
@@ -2986,7 +3205,7 @@
|
||||
description: ""
|
||||
showOnConfiguration: true
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.21.1
|
||||
defaultVersion: stable
|
||||
documentation: https://fider.io/docs
|
||||
type: fider
|
||||
name: Fider
|
||||
@@ -3105,7 +3324,7 @@
|
||||
defaultValue: $$generate_username
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.198.1
|
||||
defaultVersion: 0.210.1
|
||||
documentation: https://docs.n8n.io
|
||||
type: n8n
|
||||
name: n8n.io
|
||||
@@ -3136,7 +3355,7 @@
|
||||
defaultValue: $$generate_fqdn
|
||||
description: ""
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: stable
|
||||
defaultVersion: v1.5.1
|
||||
documentation: https://plausible.io/doc/
|
||||
arch: amd64
|
||||
type: plausibleanalytics
|
||||
@@ -3249,12 +3468,12 @@
|
||||
description: ""
|
||||
- id: $$config_disable_auth
|
||||
name: DISABLE_AUTH
|
||||
label: Disable Authentication
|
||||
label: Authentication
|
||||
defaultValue: "false"
|
||||
description: ""
|
||||
- id: $$config_disable_registration
|
||||
name: DISABLE_REGISTRATION
|
||||
label: Disable Registration
|
||||
label: Registration
|
||||
defaultValue: "true"
|
||||
description: ""
|
||||
- id: $$config_postgresql_username
|
||||
@@ -3282,7 +3501,7 @@
|
||||
defaultValue: plausible.js
|
||||
description: This is the default script name.
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.98.1
|
||||
defaultVersion: 0.101.2
|
||||
documentation: https://docs.nocodb.com
|
||||
type: nocodb
|
||||
name: NocoDB
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
{
|
||||
"name": "api",
|
||||
"description": "Coolify's Fastify API",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||
"dev": "nodemon",
|
||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
||||
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.5.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.2.0",
|
||||
"@fastify/env": "4.1.0",
|
||||
"@fastify/jwt": "6.3.3",
|
||||
"@fastify/multipart": "7.3.0",
|
||||
"@fastify/static": "6.5.1",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@ladjs/graceful": "3.0.2",
|
||||
"@prisma/client": "4.6.1",
|
||||
"@sentry/node": "7.21.1",
|
||||
"@sentry/tracing": "7.21.1",
|
||||
"axe": "11.0.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bree": "9.1.2",
|
||||
"cabin": "11.0.1",
|
||||
"compare-versions": "5.0.1",
|
||||
"csv-parse": "5.3.2",
|
||||
"csvtojson": "2.0.10",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.6",
|
||||
"dockerode": "3.3.4",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"execa": "6.1.0",
|
||||
"fastify": "4.10.2",
|
||||
"fastify-plugin": "4.3.0",
|
||||
"fastify-socket.io": "4.0.0",
|
||||
"generate-password": "1.7.0",
|
||||
"got": "12.5.3",
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"minimist": "^1.2.7",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
"prisma": "4.6.1",
|
||||
"public-ip": "6.0.1",
|
||||
"pump": "3.0.0",
|
||||
"shell-quote": "^1.7.4",
|
||||
"socket.io": "4.5.3",
|
||||
"ssh-config": "4.1.6",
|
||||
"strip-ansi": "7.0.1",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/node-os-utils": "1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
||||
"@typescript-eslint/parser": "5.44.0",
|
||||
"esbuild": "0.15.15",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.20",
|
||||
"prettier": "2.7.1",
|
||||
"rimraf": "3.0.2",
|
||||
"tsconfig-paths": "4.1.0",
|
||||
"types-fastify-socket.io": "0.0.1",
|
||||
"typescript": "4.9.3"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "node prisma/seed.js"
|
||||
}
|
||||
}
|
||||
"name": "api",
|
||||
"description": "Coolify's Fastify API",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push && prisma generate",
|
||||
"db:seed": "prisma db seed",
|
||||
"db:studio": "prisma studio",
|
||||
"db:migrate": "COOLIFY_DATABASE_URL=file:../db/migration.db prisma migrate dev --skip-seed --name",
|
||||
"dev": "nodemon",
|
||||
"build": "rimraf build && esbuild `find src \\( -name '*.ts' \\)| grep -v client/` --platform=node --outdir=build --format=cjs",
|
||||
"format": "prettier --write 'src/**/*.{js,ts,json,md}'",
|
||||
"lint": "prettier --check 'src/**/*.{js,ts,json,md}' && eslint --ignore-path .eslintignore .",
|
||||
"start": "NODE_ENV=production pnpm prisma migrate deploy && pnpm prisma generate && pnpm prisma db seed && node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.7.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.2.0",
|
||||
"@fastify/env": "4.2.0",
|
||||
"@fastify/jwt": "6.5.0",
|
||||
"@fastify/multipart": "7.3.0",
|
||||
"@fastify/static": "6.6.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@ladjs/graceful": "3.2.1",
|
||||
"@prisma/client": "4.8.1",
|
||||
"@sentry/node": "7.30.0",
|
||||
"@sentry/tracing": "7.30.0",
|
||||
"axe": "11.2.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bree": "9.1.3",
|
||||
"cabin": "11.1.1",
|
||||
"compare-versions": "5.0.1",
|
||||
"csv-parse": "5.3.3",
|
||||
"csvtojson": "2.0.10",
|
||||
"cuid": "2.1.8",
|
||||
"dayjs": "1.11.7",
|
||||
"dockerode": "3.3.4",
|
||||
"dotenv-extended": "2.9.0",
|
||||
"execa": "6.1.0",
|
||||
"fastify": "4.11.0",
|
||||
"fastify-plugin": "4.3.0",
|
||||
"fastify-socket.io": "4.0.0",
|
||||
"generate-password": "1.7.0",
|
||||
"got": "12.5.3",
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"minimist": "^1.2.7",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
"prisma": "4.8.1",
|
||||
"public-ip": "6.0.1",
|
||||
"pump": "3.0.0",
|
||||
"shell-quote": "^1.7.4",
|
||||
"socket.io": "4.5.4",
|
||||
"ssh-config": "4.2.0",
|
||||
"strip-ansi": "7.0.1",
|
||||
"unique-names-generator": "4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.18",
|
||||
"@types/node-os-utils": "1.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.1",
|
||||
"@typescript-eslint/parser": "5.48.1",
|
||||
"esbuild": "0.16.16",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.20",
|
||||
"prettier": "2.8.2",
|
||||
"rimraf": "3.0.2",
|
||||
"tsconfig-paths": "4.1.2",
|
||||
"types-fastify-socket.io": "0.0.1",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"prisma": {
|
||||
"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)
|
||||
isDBBranching Boolean @default(false)
|
||||
isCustomSSL Boolean @default(false)
|
||||
isHttp2 Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
application Application @relation(fields: [applicationId], references: [id])
|
||||
@@ -325,6 +326,7 @@ model GitSource {
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
customPort Int @default(22)
|
||||
customUser String @default("git")
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -6,14 +6,27 @@ import cookie from '@fastify/cookie';
|
||||
import multipart from '@fastify/multipart';
|
||||
import path, { join } from 'path';
|
||||
import autoLoad from '@fastify/autoload';
|
||||
import socketIO from 'fastify-socket.io'
|
||||
import socketIOServer from './realtime'
|
||||
import socketIO from 'fastify-socket.io';
|
||||
import socketIOServer from './realtime';
|
||||
|
||||
import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
|
||||
import {
|
||||
cleanupDockerStorage,
|
||||
createRemoteEngineConfiguration,
|
||||
decrypt,
|
||||
executeCommand,
|
||||
generateDatabaseConfiguration,
|
||||
isDev,
|
||||
listSettings,
|
||||
prisma,
|
||||
sentryDSN,
|
||||
startTraefikProxy,
|
||||
startTraefikTCPProxy,
|
||||
version
|
||||
} from './lib/common';
|
||||
import { scheduler } from './lib/scheduler';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import Graceful from '@ladjs/graceful'
|
||||
import yaml from 'js-yaml'
|
||||
import Graceful from '@ladjs/graceful';
|
||||
import yaml from 'js-yaml';
|
||||
import fs from 'fs/promises';
|
||||
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
|
||||
import { checkContainer } from './lib/docker';
|
||||
@@ -23,13 +36,13 @@ import * as Sentry from '@sentry/node';
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
config: {
|
||||
COOLIFY_APP_ID: string,
|
||||
COOLIFY_SECRET_KEY: string,
|
||||
COOLIFY_DATABASE_URL: string,
|
||||
COOLIFY_IS_ON: string,
|
||||
COOLIFY_WHITE_LABELED: string,
|
||||
COOLIFY_WHITE_LABELED_ICON: string | null,
|
||||
COOLIFY_AUTO_UPDATE: string,
|
||||
COOLIFY_APP_ID: string;
|
||||
COOLIFY_SECRET_KEY: string;
|
||||
COOLIFY_DATABASE_URL: string;
|
||||
COOLIFY_IS_ON: string;
|
||||
COOLIFY_WHITE_LABELED: string;
|
||||
COOLIFY_WHITE_LABELED_ICON: string | null;
|
||||
COOLIFY_AUTO_UPDATE: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -38,7 +51,7 @@ const port = isDev ? 3001 : 3000;
|
||||
const host = '0.0.0.0';
|
||||
|
||||
(async () => {
|
||||
const settings = await prisma.setting.findFirst()
|
||||
const settings = await prisma.setting.findFirst();
|
||||
const fastify = Fastify({
|
||||
logger: settings?.isAPIDebuggingEnabled || false,
|
||||
trustProxy: true
|
||||
@@ -49,10 +62,10 @@ const host = '0.0.0.0';
|
||||
required: ['COOLIFY_SECRET_KEY', 'COOLIFY_DATABASE_URL', 'COOLIFY_IS_ON'],
|
||||
properties: {
|
||||
COOLIFY_APP_ID: {
|
||||
type: 'string',
|
||||
type: 'string'
|
||||
},
|
||||
COOLIFY_SECRET_KEY: {
|
||||
type: 'string',
|
||||
type: 'string'
|
||||
},
|
||||
COOLIFY_DATABASE_URL: {
|
||||
type: 'string',
|
||||
@@ -73,8 +86,7 @@ const host = '0.0.0.0';
|
||||
COOLIFY_AUTO_UPDATE: {
|
||||
type: 'string',
|
||||
default: 'false'
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
const options = {
|
||||
@@ -103,13 +115,13 @@ const host = '0.0.0.0';
|
||||
fastify.register(autoLoad, {
|
||||
dir: join(__dirname, 'routes')
|
||||
});
|
||||
fastify.register(cookie)
|
||||
fastify.register(cookie);
|
||||
fastify.register(cors);
|
||||
fastify.register(socketIO, {
|
||||
cors: {
|
||||
origin: isDev ? "*" : ''
|
||||
origin: isDev ? '*' : ''
|
||||
}
|
||||
})
|
||||
});
|
||||
// To detect allowed origins
|
||||
// fastify.addHook('onRequest', async (request, reply) => {
|
||||
// console.log(request.headers.host)
|
||||
@@ -131,10 +143,9 @@ const host = '0.0.0.0';
|
||||
// }
|
||||
// })
|
||||
|
||||
|
||||
try {
|
||||
await fastify.listen({ port, host })
|
||||
await socketIOServer(fastify)
|
||||
await fastify.listen({ port, host });
|
||||
await socketIOServer(fastify);
|
||||
console.log(`Coolify's API is listening on ${host}:${port}`);
|
||||
|
||||
migrateServicesToNewTemplate();
|
||||
@@ -148,105 +159,125 @@ const host = '0.0.0.0';
|
||||
if (!scheduler.workers.has('deployApplication')) {
|
||||
scheduler.run('deployApplication');
|
||||
}
|
||||
}, 2000)
|
||||
}, 2000);
|
||||
|
||||
// autoUpdater
|
||||
setInterval(async () => {
|
||||
await autoUpdater()
|
||||
}, 60000 * 15)
|
||||
await autoUpdater();
|
||||
}, 60000 * 15);
|
||||
|
||||
// cleanupStorage
|
||||
setInterval(async () => {
|
||||
await cleanupStorage()
|
||||
}, 60000 * 10)
|
||||
await cleanupStorage();
|
||||
}, 60000 * 15);
|
||||
|
||||
// Cleanup stucked containers (not defined in Coolify, but still running and managed by Coolify)
|
||||
setInterval(async () => {
|
||||
await cleanupStuckedContainers();
|
||||
}, 60000);
|
||||
|
||||
// checkProxies, checkFluentBit & refresh templates
|
||||
setInterval(async () => {
|
||||
await checkProxies();
|
||||
await checkFluentBit();
|
||||
}, 60000)
|
||||
}, 60000);
|
||||
|
||||
// Refresh and check templates
|
||||
setInterval(async () => {
|
||||
await refreshTemplates()
|
||||
}, 60000)
|
||||
await refreshTemplates();
|
||||
}, 60000);
|
||||
|
||||
setInterval(async () => {
|
||||
await refreshTags()
|
||||
}, 60000)
|
||||
await refreshTags();
|
||||
}, 60000);
|
||||
|
||||
setInterval(async () => {
|
||||
await migrateServicesToNewTemplate()
|
||||
}, isDev ? 10000 : 60000)
|
||||
setInterval(
|
||||
async () => {
|
||||
await migrateServicesToNewTemplate();
|
||||
},
|
||||
isDev ? 10000 : 60000
|
||||
);
|
||||
|
||||
setInterval(async () => {
|
||||
await copySSLCertificates();
|
||||
}, 10000)
|
||||
}, 10000);
|
||||
|
||||
await Promise.all([
|
||||
getTagsTemplates(),
|
||||
getArch(),
|
||||
getIPAddress(),
|
||||
configureRemoteDockers(),
|
||||
])
|
||||
|
||||
configureRemoteDockers()
|
||||
// cleanupStuckedContainers()
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
async function getIPAddress() {
|
||||
const { publicIpv4, publicIpv6 } = await import('public-ip')
|
||||
const { publicIpv4, publicIpv6 } = await import('public-ip');
|
||||
try {
|
||||
const settings = await listSettings();
|
||||
if (!settings.ipv4) {
|
||||
const ipv4 = await publicIpv4({ timeout: 2000 })
|
||||
const ipv4 = await publicIpv4({ timeout: 2000 });
|
||||
console.log(`Getting public IPv4 address...`);
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } });
|
||||
}
|
||||
|
||||
if (!settings.ipv6) {
|
||||
const ipv6 = await publicIpv6({ timeout: 2000 })
|
||||
const ipv6 = await publicIpv6({ timeout: 2000 });
|
||||
console.log(`Getting public IPv6 address...`);
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
|
||||
}
|
||||
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
}
|
||||
async function getTagsTemplates() {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const templates = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)))
|
||||
await fs.writeFile('./tags.json', tags)
|
||||
console.log('[004] Tags and templates loaded in dev mode...')
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||
await fs.writeFile('/app/tags.json', tags)
|
||||
console.log('[004] Tags and templates loaded...')
|
||||
}
|
||||
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
if (await fs.stat('./testTags.json')) {
|
||||
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||
if (testTags.length > 0) {
|
||||
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
console.log('[004] Tags and templates loaded in dev mode...');
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||
const response = await got
|
||||
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||
.text();
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||
await fs.writeFile('/app/tags.json', tags);
|
||||
console.log('[004] Tags and templates loaded...');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Couldn't get latest templates.")
|
||||
console.log(error)
|
||||
console.log("Couldn't get latest templates.");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function initServer() {
|
||||
const appId = process.env['COOLIFY_APP_ID'];
|
||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } })
|
||||
const settings = await prisma.setting.findUnique({ where: { id: '0' } });
|
||||
try {
|
||||
if (settings.doNotTrack === true) {
|
||||
console.log('[000] Telemetry disabled...')
|
||||
|
||||
console.log('[000] Telemetry disabled...');
|
||||
} else {
|
||||
if (settings.sentryDSN !== sentryDSN) {
|
||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } })
|
||||
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
|
||||
}
|
||||
// Initialize Sentry
|
||||
// Sentry.init({
|
||||
@@ -257,37 +288,83 @@ async function initServer() {
|
||||
// console.log('[000] Sentry initialized...')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
try {
|
||||
console.log(`[001] Initializing server...`);
|
||||
await executeCommand({ command: `docker network create --attachable coolify` });
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
try {
|
||||
console.log(`[002] Cleanup stucked builds...`);
|
||||
const isOlder = compareVersions('3.8.1', version);
|
||||
if (isOlder === 1) {
|
||||
await prisma.build.updateMany({ where: { status: { in: ['running', 'queued'] } }, data: { status: 'failed' } });
|
||||
await prisma.build.updateMany({
|
||||
where: { status: { in: ['running', 'queued'] } },
|
||||
data: { status: 'failed' }
|
||||
});
|
||||
}
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
try {
|
||||
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
|
||||
await fs.rm('/tmp/build-sources', { recursive: true, force: true })
|
||||
await fs.rm('/tmp/build-sources', { recursive: true, force: true });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function getArch() {
|
||||
try {
|
||||
const settings = await prisma.setting.findFirst({})
|
||||
const settings = await prisma.setting.findFirst({});
|
||||
if (settings && !settings.arch) {
|
||||
console.log(`Getting architecture...`);
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
|
||||
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } });
|
||||
}
|
||||
} catch (error) { }
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
async function cleanupStuckedContainers() {
|
||||
try {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set();
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress))
|
||||
return;
|
||||
if (destination.engine) {
|
||||
enginesDone.add(destination.engine);
|
||||
}
|
||||
if (destination.remoteIpAddress) {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destination.id,
|
||||
command: `docker container ps -a --filter "label=coolify.managed=true" --format '{{ .Names}}'`
|
||||
});
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
for (const container of containersArray) {
|
||||
const containerId = container.split('-')[0];
|
||||
const application = await prisma.application.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
const service = await prisma.service.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
const database = await prisma.database.findFirst({
|
||||
where: { id: { startsWith: containerId } }
|
||||
});
|
||||
if (!application && !service && !database) {
|
||||
await executeCommand({ command: `docker container rm -f ${container}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function configureRemoteDockers() {
|
||||
try {
|
||||
const remoteDocker = await prisma.destinationDocker.findMany({
|
||||
@@ -296,37 +373,44 @@ async function configureRemoteDockers() {
|
||||
if (remoteDocker.length > 0) {
|
||||
console.log(`Verifying Remote Docker Engines...`);
|
||||
for (const docker of remoteDocker) {
|
||||
console.log('Verifying:', docker.remoteIpAddress)
|
||||
console.log('Verifying:', docker.remoteIpAddress);
|
||||
await verifyRemoteDockerEngineFn(docker.id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function autoUpdater() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
const currentVersion = version;
|
||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
}).json()
|
||||
const { coolify } = await got
|
||||
.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
.json();
|
||||
const latestVersion = coolify.main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isUpdateAvailable === 1) {
|
||||
const activeCount = 0
|
||||
const activeCount = 0;
|
||||
if (activeCount === 0) {
|
||||
if (!isDev) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
if (isAutoUpdateEnabled) {
|
||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` })
|
||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
|
||||
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` })
|
||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log('Updating (not really in dev mode).');
|
||||
@@ -334,7 +418,7 @@ async function autoUpdater() {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,14 +429,18 @@ async function checkFluentBit() {
|
||||
const { id } = await prisma.destinationDocker.findFirst({
|
||||
where: { engine, network: 'coolify' }
|
||||
});
|
||||
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
|
||||
const { found } = await checkContainer({
|
||||
dockerId: id,
|
||||
container: 'coolify-fluentbit',
|
||||
remove: true
|
||||
});
|
||||
if (!found) {
|
||||
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` });
|
||||
await executeCommand({ command: `docker compose up -d fluent-bit` });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function checkProxies() {
|
||||
@@ -368,7 +456,7 @@ async function checkProxies() {
|
||||
where: { engine, network: 'coolify', isCoolifyProxyUsed: true }
|
||||
});
|
||||
if (localDocker) {
|
||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 })
|
||||
portReachable = await isReachable(80, { host: ipv4 || ipv6 });
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(localDocker.id);
|
||||
}
|
||||
@@ -380,14 +468,14 @@ async function checkProxies() {
|
||||
if (remoteDocker.length > 0) {
|
||||
for (const docker of remoteDocker) {
|
||||
if (docker.isCoolifyProxyUsed) {
|
||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress })
|
||||
portReachable = await isReachable(80, { host: docker.remoteIpAddress });
|
||||
if (!portReachable) {
|
||||
await startTraefikProxy(docker.id);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await createRemoteEngineConfiguration(docker.id)
|
||||
} catch (error) { }
|
||||
await createRemoteEngineConfiguration(docker.id);
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
// TCP Proxies
|
||||
@@ -426,80 +514,109 @@ async function checkProxies() {
|
||||
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
|
||||
// }
|
||||
// }
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
async function copySSLCertificates() {
|
||||
try {
|
||||
const pAll = await import('p-all');
|
||||
const actions = []
|
||||
const certificates = await prisma.certificate.findMany({ include: { team: true } })
|
||||
const teamIds = certificates.map(c => c.teamId)
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } } })
|
||||
const actions = [];
|
||||
const certificates = await prisma.certificate.findMany({ include: { team: true } });
|
||||
const teamIds = certificates.map((c) => c.teamId);
|
||||
const destinations = await prisma.destinationDocker.findMany({
|
||||
where: { isCoolifyProxyUsed: true, teams: { some: { id: { in: [...teamIds] } } } }
|
||||
});
|
||||
for (const certificate of certificates) {
|
||||
const { id, key, cert } = certificate
|
||||
const decryptedKey = decrypt(key)
|
||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey)
|
||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert)
|
||||
const { id, key, cert } = certificate;
|
||||
const decryptedKey = decrypt(key);
|
||||
await fs.writeFile(`/tmp/${id}-key.pem`, decryptedKey);
|
||||
await fs.writeFile(`/tmp/${id}-cert.pem`, cert);
|
||||
for (const destination of destinations) {
|
||||
if (destination.remoteEngine) {
|
||||
if (destination.remoteVerified) {
|
||||
const { id: dockerId, remoteIpAddress } = destination
|
||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress))
|
||||
const { id: dockerId, remoteIpAddress } = destination;
|
||||
actions.push(async () => copyRemoteCertificates(id, dockerId, remoteIpAddress));
|
||||
}
|
||||
} else {
|
||||
actions.push(async () => copyLocalCertificates(id))
|
||||
actions.push(async () => copyLocalCertificates(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
await pAll.default(actions, { concurrency: 1 })
|
||||
await pAll.default(actions, { concurrency: 1 });
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
} finally {
|
||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
|
||||
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
|
||||
}
|
||||
}
|
||||
|
||||
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
|
||||
try {
|
||||
await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
|
||||
await executeCommand({ sshCommand: true, shell: true, dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
|
||||
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeCommand({ sshCommand: true, dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeCommand({
|
||||
command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
shell: true,
|
||||
dockerId,
|
||||
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId,
|
||||
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
await executeCommand({
|
||||
sshCommand: true,
|
||||
dockerId,
|
||||
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
console.log({ error });
|
||||
}
|
||||
}
|
||||
async function copyLocalCertificates(id: string) {
|
||||
try {
|
||||
await executeCommand({ command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`, shell: true })
|
||||
await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
|
||||
await executeCommand({
|
||||
command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`,
|
||||
shell: true
|
||||
});
|
||||
await executeCommand({
|
||||
command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
await executeCommand({
|
||||
command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`
|
||||
});
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
console.log({ error });
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupStorage() {
|
||||
const destinationDockers = await prisma.destinationDocker.findMany();
|
||||
let enginesDone = new Set()
|
||||
let enginesDone = new Set();
|
||||
for (const destination of destinationDockers) {
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
|
||||
if (destination.engine) enginesDone.add(destination.engine)
|
||||
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
|
||||
|
||||
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
|
||||
if (destination.engine) {
|
||||
enginesDone.add(destination.engine);
|
||||
}
|
||||
if (destination.remoteIpAddress) {
|
||||
if (!destination.remoteVerified) continue;
|
||||
enginesDone.add(destination.remoteIpAddress);
|
||||
}
|
||||
let lowDiskSpace = false;
|
||||
try {
|
||||
let stdout = null
|
||||
let stdout = null;
|
||||
if (!isDev) {
|
||||
const output = await executeCommand({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`, shell: true })
|
||||
const output = await executeCommand({
|
||||
dockerId: destination.id,
|
||||
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
|
||||
shell: true
|
||||
});
|
||||
stdout = output.stdout;
|
||||
} else {
|
||||
const output = await executeCommand({
|
||||
command:
|
||||
`df -kPT /`
|
||||
command: `df -kPT /`
|
||||
});
|
||||
stdout = output.stdout;
|
||||
}
|
||||
@@ -531,7 +648,9 @@ async function cleanupStorage() {
|
||||
lowDiskSpace = true;
|
||||
}
|
||||
}
|
||||
} catch (error) { }
|
||||
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
|
||||
} catch (error) {}
|
||||
if (lowDiskSpace) {
|
||||
await cleanupDockerStorage(destination.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,26 @@ import crypto from 'crypto';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
import { copyBaseConfigurationFiles, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, saveDockerRegistryCredentials, setDefaultConfiguration } from '../lib/buildPacks/common';
|
||||
import { createDirectories, decrypt, defaultComposeConfiguration, getDomain, prisma, decryptApplication, isDev, pushToRegistry, executeCommand } from '../lib/common';
|
||||
import {
|
||||
copyBaseConfigurationFiles,
|
||||
makeLabelForSimpleDockerfile,
|
||||
makeLabelForStandaloneApplication,
|
||||
saveBuildLog,
|
||||
saveDockerRegistryCredentials,
|
||||
setDefaultConfiguration
|
||||
} from '../lib/buildPacks/common';
|
||||
import {
|
||||
createDirectories,
|
||||
decrypt,
|
||||
defaultComposeConfiguration,
|
||||
getDomain,
|
||||
prisma,
|
||||
decryptApplication,
|
||||
isDev,
|
||||
pushToRegistry,
|
||||
executeCommand,
|
||||
generateSecrets
|
||||
} from '../lib/common';
|
||||
import * as importers from '../lib/importers';
|
||||
import * as buildpacks from '../lib/buildPacks';
|
||||
|
||||
@@ -14,33 +32,55 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (message === 'error') throw new Error('oops');
|
||||
if (message === 'cancel') {
|
||||
parentPort.postMessage('cancelled');
|
||||
await prisma.$disconnect()
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
const pThrottle = await import('p-throttle')
|
||||
const pThrottle = await import('p-throttle');
|
||||
const throttle = pThrottle.default({
|
||||
limit: 1,
|
||||
interval: 2000
|
||||
});
|
||||
|
||||
|
||||
const th = throttle(async () => {
|
||||
try {
|
||||
const queuedBuilds = await prisma.build.findMany({ where: { status: { in: ['queued', 'running'] } }, orderBy: { createdAt: 'asc' } });
|
||||
const { concurrentBuilds } = await prisma.setting.findFirst({})
|
||||
const queuedBuilds = await prisma.build.findMany({
|
||||
where: { status: { in: ['queued', 'running'] } },
|
||||
orderBy: { createdAt: 'asc' }
|
||||
});
|
||||
const { concurrentBuilds } = await prisma.setting.findFirst({});
|
||||
if (queuedBuilds.length > 0) {
|
||||
parentPort.postMessage({ deploying: true });
|
||||
const concurrency = concurrentBuilds;
|
||||
const pAll = await import('p-all');
|
||||
const actions = []
|
||||
const actions = [];
|
||||
|
||||
for (const queueBuild of queuedBuilds) {
|
||||
actions.push(async () => {
|
||||
let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { dockerRegistry: true, destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
|
||||
let application = await prisma.application.findUnique({
|
||||
where: { id: queueBuild.applicationId },
|
||||
include: {
|
||||
dockerRegistry: true,
|
||||
destinationDocker: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
persistentStorage: true,
|
||||
secrets: true,
|
||||
settings: true,
|
||||
teams: true
|
||||
}
|
||||
});
|
||||
|
||||
let { id: buildId, type, gitSourceId, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild
|
||||
application = decryptApplication(application)
|
||||
let {
|
||||
id: buildId,
|
||||
type,
|
||||
gitSourceId,
|
||||
sourceBranch = null,
|
||||
pullmergeRequestId = null,
|
||||
previewApplicationId = null,
|
||||
forceRebuild,
|
||||
sourceRepository = null
|
||||
} = queueBuild;
|
||||
application = decryptApplication(application);
|
||||
|
||||
if (!gitSourceId && application.simpleDockerfile) {
|
||||
const {
|
||||
@@ -53,77 +93,71 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
exposePort,
|
||||
simpleDockerfile,
|
||||
dockerRegistry
|
||||
} = application
|
||||
} = application;
|
||||
const { workdir } = await createDirectories({ repository: applicationId, buildId });
|
||||
try {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: 'Building halted, restarting...',
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
if (storage.oldPath) {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-').replace('-app', '')}:${storage.path}`;
|
||||
return `${applicationId}${storage.path
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
|
||||
if (destinationDockerId) {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'running' }
|
||||
});
|
||||
try {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
|
||||
})
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
let envs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
envs = [
|
||||
...envs,
|
||||
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||
];
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
|
||||
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||
const { url, username, password } = dockerRegistry;
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
|
||||
const labels = makeLabelForSimpleDockerfile({
|
||||
applicationId,
|
||||
type,
|
||||
port: exposePort ? `${exposePort}:${port}` : port,
|
||||
port: exposePort ? `${exposePort}:${port}` : port
|
||||
});
|
||||
try {
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
@@ -138,17 +172,17 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
services: {
|
||||
[applicationId]: {
|
||||
build: {
|
||||
context: workdir,
|
||||
context: workdir
|
||||
},
|
||||
image: `${applicationId}:${buildId}`,
|
||||
container_name: applicationId,
|
||||
volumes,
|
||||
labels,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
environment: envs,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
...defaultComposeConfiguration(destinationDocker.network),
|
||||
...defaultComposeConfiguration(destinationDocker.network)
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
@@ -159,11 +193,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeCommand({ debug: true, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await executeCommand({
|
||||
debug: true,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -174,10 +212,9 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -190,7 +227,11 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: error.message,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
@@ -199,9 +240,13 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (application.dockerRegistryImageName) {
|
||||
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
|
||||
const imageName = application.dockerRegistryImageName.split(':')[0];
|
||||
await saveBuildLog({ line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`, buildId, applicationId: application.id });
|
||||
await pushToRegistry(application, workdir, buildId, imageName, customTag)
|
||||
await saveBuildLog({ line: "Success", buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
await pushToRegistry(application, workdir, buildId, imageName, customTag);
|
||||
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.stdout) {
|
||||
@@ -212,12 +257,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'success' }
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const originalApplicationId = application.id
|
||||
const originalApplicationId = application.id;
|
||||
const {
|
||||
id: applicationId,
|
||||
name,
|
||||
@@ -241,7 +289,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
deploymentType,
|
||||
gitCommitHash,
|
||||
dockerRegistry
|
||||
} = application
|
||||
} = application;
|
||||
|
||||
let {
|
||||
branch,
|
||||
@@ -257,7 +305,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
dockerComposeFileLocation,
|
||||
dockerComposeConfiguration,
|
||||
denoMainFile
|
||||
} = application
|
||||
} = application;
|
||||
|
||||
let imageId = applicationId;
|
||||
let domain = getDomain(fqdn);
|
||||
@@ -272,9 +320,11 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
let imageFoundRemotely = false;
|
||||
|
||||
if (pullmergeRequestId) {
|
||||
const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } })
|
||||
const previewApplications = await prisma.previewApplication.findMany({
|
||||
where: { applicationId: originalApplicationId, pullmergeRequestId }
|
||||
});
|
||||
if (previewApplications.length > 0) {
|
||||
previewApplicationId = previewApplications[0].id
|
||||
previewApplicationId = previewApplications[0].id;
|
||||
}
|
||||
// Previews, we need to get the source branch and set subdomain
|
||||
branch = sourceBranch;
|
||||
@@ -285,7 +335,11 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
const { workdir, repodir } = await createDirectories({ repository, buildId });
|
||||
try {
|
||||
if (queueBuild.status === 'running') {
|
||||
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: 'Building halted, restarting...',
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
|
||||
const currentHash = crypto
|
||||
@@ -323,15 +377,16 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
const volumes =
|
||||
persistentStorage?.map((storage) => {
|
||||
if (storage.oldPath) {
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-').replace('-app', '')}:${storage.path}`;
|
||||
return `${applicationId}${storage.path
|
||||
.replace(/\//gi, '-')
|
||||
.replace('-app', '')}:${storage.path}`;
|
||||
}
|
||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||
}) || [];
|
||||
|
||||
|
||||
try {
|
||||
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration)
|
||||
} catch (error) { }
|
||||
dockerComposeConfiguration = JSON.parse(dockerComposeConfiguration);
|
||||
} catch (error) {}
|
||||
let deployNeeded = true;
|
||||
let destinationType;
|
||||
|
||||
@@ -339,7 +394,10 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
destinationType = 'docker';
|
||||
}
|
||||
if (destinationType === 'docker') {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'running' }
|
||||
});
|
||||
|
||||
const configuration = await setDefaultConfiguration(application);
|
||||
|
||||
@@ -361,6 +419,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
customUser: gitSource.customUser,
|
||||
gitCommitHash,
|
||||
configuration,
|
||||
repository,
|
||||
@@ -381,10 +440,10 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
|
||||
}
|
||||
if (application.dockerRegistryImageName) {
|
||||
imageName = application.dockerRegistryImageName.split(':')[0]
|
||||
customTag = application.dockerRegistryImageName.split(':')[1] || tag
|
||||
imageName = application.dockerRegistryImageName.split(':')[0];
|
||||
customTag = application.dockerRegistryImageName.split(':')[1] || tag;
|
||||
} else {
|
||||
customTag = tag
|
||||
customTag = tag;
|
||||
imageName = applicationId;
|
||||
}
|
||||
|
||||
@@ -394,13 +453,17 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
|
||||
try {
|
||||
await prisma.build.update({ where: { id: buildId }, data: { commit } });
|
||||
} catch (err) { }
|
||||
} catch (err) {}
|
||||
|
||||
if (!pullmergeRequestId) {
|
||||
if (configHash !== currentHash) {
|
||||
deployNeeded = true;
|
||||
if (configHash) {
|
||||
await saveBuildLog({ line: 'Configuration changed', buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: 'Configuration changed',
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
} else {
|
||||
deployNeeded = false;
|
||||
@@ -413,30 +476,43 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker image inspect ${applicationId}:${tag}`
|
||||
})
|
||||
});
|
||||
imageFoundLocally = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry
|
||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||
const { url, username, password } = dockerRegistry;
|
||||
location = await saveDockerRegistryCredentials({
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
workdir
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await executeCommand({
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker ${location ? `--config ${location}` : ''} pull ${imageName}:${customTag}`
|
||||
})
|
||||
command: `docker ${
|
||||
location ? `--config ${location}` : ''
|
||||
} pull ${imageName}:${customTag}`
|
||||
});
|
||||
imageFoundRemotely = true;
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
let imageFound = `${applicationId}:${tag}`
|
||||
let imageFound = `${applicationId}:${tag}`;
|
||||
if (imageFoundRemotely) {
|
||||
imageFound = `${imageName}:${customTag}`
|
||||
imageFound = `${imageName}:${customTag}`;
|
||||
}
|
||||
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
|
||||
await copyBaseConfigurationFiles(
|
||||
buildPack,
|
||||
workdir,
|
||||
buildId,
|
||||
applicationId,
|
||||
baseImage
|
||||
);
|
||||
const labels = makeLabelForStandaloneApplication({
|
||||
applicationId,
|
||||
fqdn,
|
||||
@@ -455,7 +531,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
});
|
||||
if (forceRebuild) deployNeeded = true
|
||||
if (forceRebuild) deployNeeded = true;
|
||||
if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) {
|
||||
if (buildpacks[buildPack])
|
||||
await buildpacks[buildPack]({
|
||||
@@ -496,33 +572,53 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
baseImage,
|
||||
baseBuildImage,
|
||||
deploymentType,
|
||||
forceRebuild
|
||||
});
|
||||
else {
|
||||
await saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: `Build pack ${buildPack} not found`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
throw new Error(`Build pack ${buildPack} not found.`);
|
||||
}
|
||||
} else {
|
||||
if (imageFoundRemotely || deployNeeded) {
|
||||
await saveBuildLog({ line: `Container image ${imageFound} found in Docker Registry - reuising it`, buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: `Container image ${imageFound} found in Docker Registry - reuising it`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
} else {
|
||||
if (imageFoundLocally || deployNeeded) {
|
||||
await saveBuildLog({ line: `Container image ${imageFound} found locally - reuising it`, buildId, applicationId });
|
||||
await saveBuildLog({
|
||||
line: `Container image ${imageFound} found locally - reuising it`,
|
||||
buildId,
|
||||
applicationId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buildPack === 'compose') {
|
||||
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`;
|
||||
try {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
|
||||
})
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,17 +626,25 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
//
|
||||
}
|
||||
try {
|
||||
console.log({ debug })
|
||||
await executeCommand({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
data: { status: 'success' }
|
||||
});
|
||||
await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -551,55 +655,42 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
}
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
const { stdout: containers } = await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}`
|
||||
})
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${
|
||||
pullmergeRequestId ? imageId : applicationId
|
||||
}' --format {{.ID}}`
|
||||
});
|
||||
if (containers) {
|
||||
const containerArray = containers.split('\n');
|
||||
if (containerArray.length > 0) {
|
||||
for (const container of containerArray) {
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
|
||||
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker stop -t 0 ${container}`
|
||||
});
|
||||
await executeCommand({
|
||||
dockerId: destinationDockerId,
|
||||
command: `docker rm --force ${container}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
const envs = [
|
||||
`PORT=${port}`
|
||||
];
|
||||
let envs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
envs = [
|
||||
...envs,
|
||||
...generateSecrets(secrets, pullmergeRequestId, false, port)
|
||||
];
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||
}
|
||||
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
const { url, username, password } = dockerRegistry;
|
||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
try {
|
||||
const composeVolumes = volumes.map((volume) => {
|
||||
@@ -616,12 +707,12 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
image: imageFound,
|
||||
container_name: imageId,
|
||||
volumes,
|
||||
env_file: envFound ? [`${workdir}/.env`] : [],
|
||||
environment: envs,
|
||||
labels,
|
||||
depends_on: [],
|
||||
expose: [port],
|
||||
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
|
||||
...defaultComposeConfiguration(destinationDocker.network),
|
||||
...defaultComposeConfiguration(destinationDocker.network)
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
@@ -632,11 +723,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
volumes: Object.assign({}, ...composeVolumes)
|
||||
};
|
||||
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
|
||||
await executeCommand({ debug, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
|
||||
await executeCommand({
|
||||
debug,
|
||||
dockerId: destinationDocker.id,
|
||||
command: `docker compose --project-directory ${workdir} -f ${workdir}/docker-compose.yml up -d`
|
||||
});
|
||||
await saveBuildLog({ line: 'Deployed 🎉', buildId, applicationId });
|
||||
} catch (error) {
|
||||
await saveBuildLog({ line: error, buildId, applicationId });
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -648,14 +743,15 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
if (!pullmergeRequestId) await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
if (!pullmergeRequestId)
|
||||
await prisma.application.update({
|
||||
where: { id: applicationId },
|
||||
data: { configHash: currentHash }
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (foundBuild) {
|
||||
await prisma.build.update({
|
||||
where: { id: buildId },
|
||||
@@ -668,16 +764,24 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
await saveBuildLog({ line: error, buildId, applicationId: application.id });
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: error.message,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
}
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (application.dockerRegistryImageName && (!imageFoundRemotely || forceRebuild)) {
|
||||
await saveBuildLog({ line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`, buildId, applicationId: application.id });
|
||||
await pushToRegistry(application, workdir, tag, imageName, customTag)
|
||||
await saveBuildLog({ line: "Success", buildId, applicationId: application.id });
|
||||
await saveBuildLog({
|
||||
line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`,
|
||||
buildId,
|
||||
applicationId: application.id
|
||||
});
|
||||
await pushToRegistry(application, workdir, tag, imageName, customTag);
|
||||
await saveBuildLog({ line: 'Success', buildId, applicationId: application.id });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.stdout) {
|
||||
@@ -686,21 +790,20 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
if (error.stderr) {
|
||||
await saveBuildLog({ line: error.stderr, buildId, applicationId });
|
||||
}
|
||||
|
||||
} finally {
|
||||
await fs.rm(workdir, { recursive: true, force: true });
|
||||
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
|
||||
}
|
||||
});
|
||||
}
|
||||
await pAll.default(actions, { concurrency })
|
||||
await pAll.default(actions, { concurrency });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
})
|
||||
});
|
||||
while (true) {
|
||||
await th()
|
||||
await th();
|
||||
}
|
||||
} else process.exit(0);
|
||||
})();
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { base64Encode, decrypt, encrypt, executeCommand, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common";
|
||||
import {
|
||||
base64Encode,
|
||||
decrypt,
|
||||
encrypt,
|
||||
executeCommand,
|
||||
generateSecrets,
|
||||
generateTimestamp,
|
||||
getDomain,
|
||||
isARM,
|
||||
isDev,
|
||||
prisma,
|
||||
version
|
||||
} from '../common';
|
||||
import { promises as fs } from 'fs';
|
||||
import { day } from "../dayjs";
|
||||
import { day } from '../dayjs';
|
||||
|
||||
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
|
||||
const nodeBased = [
|
||||
@@ -17,7 +29,10 @@ const nodeBased = [
|
||||
'nextjs'
|
||||
];
|
||||
|
||||
export function setDefaultBaseImage(buildPack: string | null, deploymentType: string | null = null) {
|
||||
export function setDefaultBaseImage(
|
||||
buildPack: string | null,
|
||||
deploymentType: string | null = null
|
||||
) {
|
||||
const nodeVersions = [
|
||||
{
|
||||
value: 'node:lts',
|
||||
@@ -316,8 +331,8 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
{
|
||||
value: 'heroku/builder-classic:22',
|
||||
label: 'heroku/builder-classic:22'
|
||||
},
|
||||
]
|
||||
}
|
||||
];
|
||||
let payload: any = {
|
||||
baseImage: null,
|
||||
baseBuildImage: null,
|
||||
@@ -327,7 +342,9 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
if (nodeBased.includes(buildPack)) {
|
||||
if (deploymentType === 'static') {
|
||||
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM(process.arch) ? staticVersions.filter((version) => !version.value.includes('webdevops')) : staticVersions;
|
||||
payload.baseImages = isARM(process.arch)
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
} else {
|
||||
@@ -339,7 +356,9 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
}
|
||||
if (staticApps.includes(buildPack)) {
|
||||
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine';
|
||||
payload.baseImages = isARM(process.arch) ? staticVersions.filter((version) => !version.value.includes('webdevops')) : staticVersions;
|
||||
payload.baseImages = isARM(process.arch)
|
||||
? staticVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: staticVersions;
|
||||
payload.baseBuildImage = 'node:lts';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
@@ -357,12 +376,20 @@ export function setDefaultBaseImage(buildPack: string | null, deploymentType: st
|
||||
payload.baseImage = 'denoland/deno:latest';
|
||||
}
|
||||
if (buildPack === 'php') {
|
||||
payload.baseImage = isARM(process.arch) ? 'php:8.1-fpm-alpine' : 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch) ? phpVersions.filter((version) => !version.value.includes('webdevops')) : phpVersions
|
||||
payload.baseImage = isARM(process.arch)
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
}
|
||||
if (buildPack === 'laravel') {
|
||||
payload.baseImage = isARM(process.arch) ? 'php:8.1-fpm-alpine' : 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch) ? phpVersions.filter((version) => !version.value.includes('webdevops')) : phpVersions
|
||||
payload.baseImage = isARM(process.arch)
|
||||
? 'php:8.1-fpm-alpine'
|
||||
: 'webdevops/php-apache:8.2-alpine';
|
||||
payload.baseImages = isARM(process.arch)
|
||||
? phpVersions.filter((version) => !version.value.includes('webdevops'))
|
||||
: phpVersions;
|
||||
payload.baseBuildImage = 'node:18';
|
||||
payload.baseBuildImages = nodeVersions;
|
||||
}
|
||||
@@ -405,7 +432,8 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
|
||||
if (baseDirectory) {
|
||||
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
|
||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/') baseDirectory = baseDirectory.slice(0, -1);
|
||||
if (baseDirectory.endsWith('/') && baseDirectory !== '/')
|
||||
baseDirectory = baseDirectory.slice(0, -1);
|
||||
}
|
||||
if (dockerFileLocation) {
|
||||
if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`;
|
||||
@@ -414,8 +442,10 @@ export const setDefaultConfiguration = async (data: any) => {
|
||||
dockerFileLocation = '/Dockerfile';
|
||||
}
|
||||
if (dockerComposeFileLocation) {
|
||||
if (!dockerComposeFileLocation.startsWith('/')) dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||
if (dockerComposeFileLocation.endsWith('/')) dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||
if (!dockerComposeFileLocation.startsWith('/'))
|
||||
dockerComposeFileLocation = `/${dockerComposeFileLocation}`;
|
||||
if (dockerComposeFileLocation.endsWith('/'))
|
||||
dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1);
|
||||
} else {
|
||||
dockerComposeFileLocation = '/Dockerfile';
|
||||
}
|
||||
@@ -479,7 +509,6 @@ export const scanningTemplates = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const saveBuildLog = async ({
|
||||
line,
|
||||
buildId,
|
||||
@@ -491,7 +520,7 @@ export const saveBuildLog = async ({
|
||||
}): Promise<any> => {
|
||||
if (buildId === 'undefined' || buildId === 'null' || !buildId) return;
|
||||
if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return;
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
if (typeof line === 'object' && line) {
|
||||
if (line.shortMessage) {
|
||||
line = line.shortMessage + '\n' + line.stderr;
|
||||
@@ -504,7 +533,11 @@ export const saveBuildLog = async ({
|
||||
line = line.replace(regex, '<SENSITIVE_DATA_DELETED>@');
|
||||
}
|
||||
const addTimestamp = `[${generateTimestamp()}] ${line}`;
|
||||
const fluentBitUrl = isDev ? process.env.COOLIFY_CONTAINER_DEV === 'true' ? 'http://coolify-fluentbit:24224' : 'http://localhost:24224' : 'http://coolify-fluentbit:24224';
|
||||
const fluentBitUrl = isDev
|
||||
? process.env.COOLIFY_CONTAINER_DEV === 'true'
|
||||
? 'http://coolify-fluentbit:24224'
|
||||
: 'http://localhost:24224'
|
||||
: 'http://coolify-fluentbit:24224';
|
||||
|
||||
if (isDev && !process.env.COOLIFY_CONTAINER_DEV) {
|
||||
console.debug(`[${applicationId}] ${addTimestamp}`);
|
||||
@@ -514,15 +547,17 @@ export const saveBuildLog = async ({
|
||||
json: {
|
||||
line: encrypt(line)
|
||||
}
|
||||
})
|
||||
});
|
||||
} catch (error) {
|
||||
return await prisma.buildLog.create({
|
||||
data: {
|
||||
line: addTimestamp, buildId, time: Number(day().valueOf()), applicationId
|
||||
line: addTimestamp,
|
||||
buildId,
|
||||
time: Number(day().valueOf()),
|
||||
applicationId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export async function copyBaseConfigurationFiles(
|
||||
@@ -610,7 +645,7 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
|
||||
|
||||
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
|
||||
if (!username || !password) {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
let decryptedPassword = decrypt(password);
|
||||
@@ -619,17 +654,17 @@ export async function saveDockerRegistryCredentials({ url, username, password, w
|
||||
try {
|
||||
await fs.mkdir(`${workdir}/.docker`);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
// console.log(error);
|
||||
}
|
||||
const payload = JSON.stringify({
|
||||
"auths": {
|
||||
auths: {
|
||||
[url]: {
|
||||
"auth": Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||
auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64')
|
||||
}
|
||||
}
|
||||
})
|
||||
await fs.writeFile(`${location}/config.json`, payload)
|
||||
return location
|
||||
});
|
||||
await fs.writeFile(`${location}/config.json`, payload);
|
||||
return location;
|
||||
}
|
||||
export async function buildImage({
|
||||
applicationId,
|
||||
@@ -640,29 +675,41 @@ export async function buildImage({
|
||||
isCache = false,
|
||||
debug = false,
|
||||
dockerFileLocation = '/Dockerfile',
|
||||
commit
|
||||
commit,
|
||||
forceRebuild = false
|
||||
}) {
|
||||
if (isCache) {
|
||||
await saveBuildLog({ line: `Building cache image...`, buildId, applicationId });
|
||||
} else {
|
||||
await saveBuildLog({ line: `Building production image...`, buildId, applicationId });
|
||||
}
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
|
||||
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`;
|
||||
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`;
|
||||
let location = null;
|
||||
|
||||
let location = null
|
||||
|
||||
const { dockerRegistry } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
|
||||
const { dockerRegistry } = await prisma.application.findUnique({
|
||||
where: { id: applicationId },
|
||||
select: { dockerRegistry: true }
|
||||
});
|
||||
if (dockerRegistry) {
|
||||
const { url, username, password } = dockerRegistry
|
||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
|
||||
const { url, username, password } = dockerRegistry;
|
||||
location = await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||
}
|
||||
|
||||
await executeCommand({ stream: true, debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
|
||||
await executeCommand({
|
||||
stream: true,
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker ${location ? `--config ${location}` : ''} build ${
|
||||
forceRebuild ? '--no-cache' : ''
|
||||
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
|
||||
});
|
||||
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } })
|
||||
const { status } = await prisma.build.findUnique({ where: { id: buildId } });
|
||||
if (status === 'canceled') {
|
||||
throw new Error('Canceled.')
|
||||
throw new Error('Canceled.');
|
||||
}
|
||||
}
|
||||
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
|
||||
@@ -741,21 +788,8 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
@@ -765,7 +799,6 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
if (installCommand) {
|
||||
Dockerfile.push(`RUN ${installCommand}`);
|
||||
}
|
||||
// Dockerfile.push(`ARG CACHEBUST=1`);
|
||||
Dockerfile.push(`RUN ${buildCommand}`);
|
||||
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
|
||||
await buildImage({ ...data, isCache: true });
|
||||
@@ -773,27 +806,13 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
||||
|
||||
export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||
const { workdir, buildId, secrets, pullmergeRequestId } = data;
|
||||
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push(`COPY *.json *.mix.js /app/`);
|
||||
@@ -804,11 +823,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
|
||||
}
|
||||
|
||||
export async function buildCacheImageWithCargo(data, imageForBuild) {
|
||||
const {
|
||||
applicationId,
|
||||
workdir,
|
||||
buildId,
|
||||
} = data;
|
||||
const { applicationId, workdir, buildId } = data;
|
||||
|
||||
const Dockerfile: Array<string> = [];
|
||||
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { defaultComposeConfiguration, executeCommand } from '../common';
|
||||
import { defaultComposeConfiguration, executeCommand, generateSecrets } from '../common';
|
||||
import { saveBuildLog } from './common';
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
@@ -25,30 +25,13 @@ export default async function (data) {
|
||||
if (!dockerComposeYaml.services) {
|
||||
throw 'No Services found in docker-compose file.';
|
||||
}
|
||||
const envs = [];
|
||||
let envs = [];
|
||||
let buildEnvs = [];
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret);
|
||||
if (isSecretFound.length > 0) {
|
||||
envs.push(`${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||
let envFound = false;
|
||||
try {
|
||||
envFound = !!(await fs.stat(`${workdir}/.env`));
|
||||
} catch (error) {
|
||||
//
|
||||
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
|
||||
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
|
||||
}
|
||||
|
||||
const composeVolumes = [];
|
||||
if (volumes.length > 0) {
|
||||
for (const volume of volumes) {
|
||||
@@ -62,7 +45,34 @@ export default async function (data) {
|
||||
let networks = {};
|
||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||
value['container_name'] = `${applicationId}-${key}`;
|
||||
value['env_file'] = envFound ? [`${workdir}/.env`] : [];
|
||||
|
||||
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
|
||||
if (Object.keys(environment).length > 0) {
|
||||
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
|
||||
}
|
||||
value['environment'] = [...environment, ...envs];
|
||||
|
||||
let build = typeof value['build'] === 'undefined' ? [] : value['build'];
|
||||
if (typeof build === 'string') {
|
||||
build = { context: build };
|
||||
}
|
||||
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
|
||||
let finalArgs = [...buildEnvs];
|
||||
if (Object.keys(buildArgs).length > 0) {
|
||||
for (const arg of buildArgs) {
|
||||
const [key, _] = arg.split('=');
|
||||
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
|
||||
finalArgs.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (build.length > 0 || buildArgs.length > 0 ) {
|
||||
value['build'] = {
|
||||
...build,
|
||||
args: finalArgs
|
||||
};
|
||||
}
|
||||
|
||||
value['labels'] = labels;
|
||||
// TODO: If we support separated volume for each service, we need to add it here
|
||||
if (value['volumes']?.length > 0) {
|
||||
@@ -106,13 +116,14 @@ export default async function (data) {
|
||||
dockerComposeYaml['volumes'] = { ...composeVolumes };
|
||||
}
|
||||
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
|
||||
|
||||
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
|
||||
await executeCommand({
|
||||
debug,
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker compose --project-directory ${workdir} pull`
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} pull`
|
||||
});
|
||||
await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId });
|
||||
await executeCommand({
|
||||
@@ -120,7 +131,7 @@ export default async function (data) {
|
||||
buildId,
|
||||
applicationId,
|
||||
dockerId,
|
||||
command: `docker compose --project-directory ${workdir} build --progress plain`
|
||||
command: `docker compose --project-directory ${workdir} -f ${fileYaml} build --progress plain`
|
||||
});
|
||||
await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId });
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (depsFound) {
|
||||
|
||||
@@ -1,47 +1,27 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
export default async function (data) {
|
||||
let {
|
||||
applicationId,
|
||||
debug,
|
||||
tag,
|
||||
workdir,
|
||||
buildId,
|
||||
baseDirectory,
|
||||
secrets,
|
||||
pullmergeRequestId,
|
||||
dockerFileLocation
|
||||
} = data
|
||||
let { workdir, buildId, baseDirectory, secrets, pullmergeRequestId, dockerFileLocation } = data;
|
||||
const file = `${workdir}${baseDirectory}${dockerFileLocation}`;
|
||||
data.workdir = `${workdir}${baseDirectory}`;
|
||||
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8')
|
||||
const Dockerfile: Array<string> = DockerfileRaw
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n');
|
||||
const DockerfileRaw = await fs.readFile(`${file}`, 'utf8');
|
||||
const Dockerfile: Array<string> = DockerfileRaw.toString().trim().split('\n');
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`);
|
||||
}
|
||||
});
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (
|
||||
(pullmergeRequestId && secret.isPRMRSecret) ||
|
||||
(!pullmergeRequestId && !secret.isPRMRSecret)
|
||||
) {
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, `ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
});
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.forEach((line, index) => {
|
||||
if (line.startsWith('FROM')) {
|
||||
Dockerfile.splice(index + 1, 0, env);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await fs.writeFile(`${workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||
await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n'));
|
||||
await buildImage(data);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageForLaravel, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
const { workdir, applicationId, tag, buildId, port } = data;
|
||||
const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data;
|
||||
const Dockerfile: Array<string> = [];
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
||||
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -20,21 +21,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage, checkPnpm } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -24,21 +25,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (isPnpm) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
@@ -13,21 +14,8 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -18,21 +19,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (pythonWSGI?.toLowerCase() === 'gunicorn') {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageWithNode, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@@ -25,21 +26,8 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
}
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
secrets.forEach((secret) => {
|
||||
if (secret.isBuildSecret) {
|
||||
if (pullmergeRequestId) {
|
||||
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
|
||||
if (isSecretFound.length > 0) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
|
||||
} else {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
if (!secret.isPRMRSecret) {
|
||||
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
if (buildCommand) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common
|
||||
import { scheduler } from './scheduler';
|
||||
import type { ExecaChildProcess } from 'execa';
|
||||
|
||||
export const version = '3.12.1';
|
||||
export const version = '3.12.18';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
export const sentryDSN =
|
||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
||||
@@ -714,8 +714,10 @@ export async function startTraefikProxy(id: string): Promise<void> {
|
||||
--network coolify-infra \
|
||||
-p "80:80" \
|
||||
-p "443:443" \
|
||||
${isDev ? '-p "8080:8080"' : ''} \
|
||||
--name coolify-proxy \
|
||||
-d ${defaultTraefikImage} \
|
||||
${isDev ? '--api.insecure=true' : ''} \
|
||||
--entrypoints.web.address=:80 \
|
||||
--entrypoints.web.forwardedHeaders.insecure=true \
|
||||
--entrypoints.websecure.address=:443 \
|
||||
@@ -1712,70 +1714,24 @@ export function convertTolOldVolumeNames(type) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
|
||||
// Cleanup old coolify images
|
||||
export async function cleanupDockerStorage(dockerId) {
|
||||
// Cleanup images that are not used by any container
|
||||
try {
|
||||
let { stdout: images } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`,
|
||||
shell: true
|
||||
});
|
||||
|
||||
images = images.trim();
|
||||
if (images) {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command: `docker rmi -f ${images}" -q | xargs -r`,
|
||||
shell: true
|
||||
});
|
||||
}
|
||||
await executeCommand({ dockerId, command: `docker image prune -af` });
|
||||
} catch (error) {}
|
||||
if (lowDiskSpace || force) {
|
||||
// Cleanup images that are not used
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker image prune -f` });
|
||||
} catch (error) {}
|
||||
|
||||
const { numberOfDockerImagesKeptLocally } = await prisma.setting.findUnique({
|
||||
where: { id: '0' }
|
||||
});
|
||||
const { stdout: images } = await executeCommand({
|
||||
// Prune coolify managed containers
|
||||
try {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command: `docker images|grep -v "<none>"|grep -v REPOSITORY|awk '{print $1, $2}'`,
|
||||
shell: true
|
||||
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||
});
|
||||
const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
|
||||
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
|
||||
let deleteImage = [];
|
||||
for (const image of imagesSet) {
|
||||
let keepImage = [];
|
||||
for (const image2 of imagesArray) {
|
||||
if (image2.startsWith(image)) {
|
||||
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
|
||||
deleteImage.push(image2);
|
||||
} else {
|
||||
keepImage.push(image2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const image of deleteImage) {
|
||||
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
// Prune coolify managed containers
|
||||
try {
|
||||
await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container prune -f --filter "label=coolify.managed=true"`
|
||||
});
|
||||
} catch (error) {}
|
||||
|
||||
// Cleanup build caches
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker builder prune -a -f` });
|
||||
} catch (error) {}
|
||||
}
|
||||
// Cleanup build caches
|
||||
try {
|
||||
await executeCommand({ dockerId, command: `docker builder prune -af` });
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
export function persistentVolumes(id, persistentStorage, config) {
|
||||
@@ -1875,3 +1831,66 @@ export async function pushToRegistry(
|
||||
command: pushCommand
|
||||
});
|
||||
}
|
||||
|
||||
function parseSecret(secret, isBuild) {
|
||||
if (secret.value.includes('$')) {
|
||||
secret.value = secret.value.replaceAll('$', '$$$$');
|
||||
}
|
||||
if (secret.value.includes('\\n')) {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}=${secret.value}`;
|
||||
} else {
|
||||
return `${secret.name}=${secret.value}`;
|
||||
}
|
||||
} else if (secret.value.includes(' ')) {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}='${secret.value}'`;
|
||||
} else {
|
||||
return `${secret.name}='${secret.value}'`;
|
||||
}
|
||||
} else {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}=${secret.value}`;
|
||||
} else {
|
||||
return `${secret.name}=${secret.value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
export function generateSecrets(
|
||||
secrets: Array<any>,
|
||||
pullmergeRequestId: string,
|
||||
isBuild = false,
|
||||
port = null,
|
||||
compose = false
|
||||
): Array<string> {
|
||||
const envs = [];
|
||||
const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret);
|
||||
const normalSecrets = secrets.filter((s) => !s.isPRMRSecret);
|
||||
if (pullmergeRequestId && isPRMRSecret.length > 0) {
|
||||
isPRMRSecret.forEach((secret) => {
|
||||
if (isBuild && !secret.isBuildSecret) {
|
||||
return;
|
||||
}
|
||||
const build = isBuild && secret.isBuildSecret;
|
||||
envs.push(parseSecret(secret, compose ? false : build));
|
||||
});
|
||||
}
|
||||
if (!pullmergeRequestId && normalSecrets.length > 0) {
|
||||
normalSecrets.forEach((secret) => {
|
||||
if (isBuild && !secret.isBuildSecret) {
|
||||
return;
|
||||
}
|
||||
const build = isBuild && secret.isBuildSecret;
|
||||
envs.push(parseSecret(secret, compose ? false : build));
|
||||
});
|
||||
}
|
||||
const portFound = envs.filter((env) => env.startsWith('PORT'));
|
||||
if (portFound.length === 0 && port && !isBuild) {
|
||||
envs.push(`PORT=${port}`);
|
||||
}
|
||||
const nodeEnv = envs.filter((env) => env.startsWith('NODE_ENV'));
|
||||
if (nodeEnv.length === 0 && !isBuild) {
|
||||
envs.push(`NODE_ENV=production`);
|
||||
}
|
||||
return envs;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ export default async function ({
|
||||
buildId,
|
||||
privateSshKey,
|
||||
customPort,
|
||||
forPublic
|
||||
forPublic,
|
||||
customUser,
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@@ -25,6 +26,7 @@ export default async function ({
|
||||
privateSshKey: string;
|
||||
customPort: number;
|
||||
forPublic: boolean;
|
||||
customUser: string;
|
||||
}): Promise<string> {
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||
if (!forPublic) {
|
||||
@@ -53,7 +55,7 @@ export default async function ({
|
||||
} else {
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
`git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,51 +1,47 @@
|
||||
import { isARM, isDev } from "./common";
|
||||
import { isARM, isDev } from './common';
|
||||
import fs from 'fs/promises';
|
||||
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 => d.arch !== 'amd64')
|
||||
}
|
||||
return jsonData;
|
||||
} catch (error) {
|
||||
return []
|
||||
} finally {
|
||||
await open?.close()
|
||||
}
|
||||
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) => d.arch !== 'amd64');
|
||||
}
|
||||
return jsonData;
|
||||
} catch (error) {
|
||||
return [];
|
||||
} finally {
|
||||
await open?.close();
|
||||
}
|
||||
}
|
||||
const compareSemanticVersions = (a: string, b: string) => {
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
const a1 = a.split('.');
|
||||
const b1 = b.split('.');
|
||||
const len = Math.min(a1.length, b1.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const a2 = +a1[i] || 0;
|
||||
const b2 = +b1[i] || 0;
|
||||
if (a2 !== b2) {
|
||||
return a2 > b2 ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return b1.length - a1.length;
|
||||
};
|
||||
export async function getTags(type: string) {
|
||||
|
||||
try {
|
||||
if (type) {
|
||||
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||
const data = await fs.readFile(tagsPath, 'utf8')
|
||||
let tags = JSON.parse(data)
|
||||
if (tags) {
|
||||
tags = tags.find((tag: any) => tag.name.includes(type))
|
||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||
return tags
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return []
|
||||
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (type) {
|
||||
const tagsPath = isDev ? './tags.json' : '/app/tags.json';
|
||||
const data = await fs.readFile(tagsPath, 'utf8');
|
||||
let tags = JSON.parse(data);
|
||||
if (tags) {
|
||||
tags = tags.find((tag: any) => tag.name.includes(type));
|
||||
tags.tags = tags.tags.sort(compareSemanticVersions).reverse();
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Body: {
|
||||
name: string,
|
||||
buildPack: string,
|
||||
fqdn: string,
|
||||
port: number,
|
||||
exposePort: number,
|
||||
installCommand: string,
|
||||
buildCommand: string,
|
||||
startCommand: string,
|
||||
baseDirectory: string,
|
||||
publishDirectory: string,
|
||||
pythonWSGI: string,
|
||||
pythonModule: string,
|
||||
pythonVariable: string,
|
||||
dockerFileLocation: string,
|
||||
denoMainFile: string,
|
||||
denoOptions: string,
|
||||
baseImage: string,
|
||||
gitCommitHash: string,
|
||||
baseBuildImage: string,
|
||||
deploymentType: string,
|
||||
baseDatabaseBranch: string,
|
||||
dockerComposeFile: string,
|
||||
dockerComposeFileLocation: string,
|
||||
dockerComposeConfiguration: string,
|
||||
simpleDockerfile: string,
|
||||
dockerRegistryImageName: string
|
||||
}
|
||||
Body: {
|
||||
name: string;
|
||||
buildPack: string;
|
||||
fqdn: string;
|
||||
port: number;
|
||||
exposePort: number;
|
||||
installCommand: string;
|
||||
buildCommand: string;
|
||||
startCommand: string;
|
||||
baseDirectory: string;
|
||||
publishDirectory: string;
|
||||
pythonWSGI: string;
|
||||
pythonModule: string;
|
||||
pythonVariable: string;
|
||||
dockerFileLocation: string;
|
||||
denoMainFile: string;
|
||||
denoOptions: string;
|
||||
baseImage: string;
|
||||
gitCommitHash: string;
|
||||
baseBuildImage: string;
|
||||
deploymentType: string;
|
||||
baseDatabaseBranch: string;
|
||||
dockerComposeFile: string;
|
||||
dockerComposeFileLocation: string;
|
||||
dockerComposeConfiguration: string;
|
||||
simpleDockerfile: string;
|
||||
dockerRegistryImageName: string;
|
||||
};
|
||||
}
|
||||
export interface SaveApplicationSettings extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean };
|
||||
Querystring: { domain: string };
|
||||
Body: {
|
||||
debug: boolean;
|
||||
previews: boolean;
|
||||
dualCerts: boolean;
|
||||
autodeploy: boolean;
|
||||
branch: string;
|
||||
projectId: number;
|
||||
isBot: boolean;
|
||||
isDBBranching: boolean;
|
||||
isCustomSSL: boolean;
|
||||
isHttp2: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteApplication extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: { force: boolean }
|
||||
Querystring: { domain: string };
|
||||
Body: { force: boolean };
|
||||
}
|
||||
export interface CheckDomain extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Querystring: { domain: string };
|
||||
}
|
||||
export interface CheckDNS extends OnlyId {
|
||||
Querystring: { domain: string; };
|
||||
Body: {
|
||||
exposePort: number,
|
||||
fqdn: string,
|
||||
forceSave: boolean,
|
||||
dualCerts: boolean
|
||||
}
|
||||
Querystring: { domain: string };
|
||||
Body: {
|
||||
exposePort: number;
|
||||
fqdn: string;
|
||||
forceSave: boolean;
|
||||
dualCerts: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeployApplication {
|
||||
Querystring: { domain: string }
|
||||
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean }
|
||||
Querystring: { domain: string };
|
||||
Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
|
||||
}
|
||||
export interface GetImages {
|
||||
Body: { buildPack: string, deploymentType: string }
|
||||
Body: { buildPack: string; deploymentType: string };
|
||||
}
|
||||
export interface SaveApplicationSource extends OnlyId {
|
||||
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string }
|
||||
Body: {
|
||||
gitSourceId?: string | null;
|
||||
forPublic?: boolean;
|
||||
type?: string;
|
||||
simpleDockerfile?: string;
|
||||
};
|
||||
}
|
||||
export interface CheckRepository extends OnlyId {
|
||||
Querystring: { repository: string, branch: string }
|
||||
Querystring: { repository: string; branch: string };
|
||||
}
|
||||
export interface SaveDestination extends OnlyId {
|
||||
Body: { destinationId: string }
|
||||
Body: { destinationId: string };
|
||||
}
|
||||
export interface SaveSecret extends OnlyId {
|
||||
Body: {
|
||||
name: string,
|
||||
value: string,
|
||||
isBuildSecret: boolean,
|
||||
previewSecret: boolean,
|
||||
isNew: boolean
|
||||
}
|
||||
Body: {
|
||||
name: string;
|
||||
value: string;
|
||||
isBuildSecret: boolean;
|
||||
previewSecret: boolean;
|
||||
isNew: boolean;
|
||||
};
|
||||
}
|
||||
export interface DeleteSecret extends OnlyId {
|
||||
Body: { name: string }
|
||||
Body: { name: string };
|
||||
}
|
||||
export interface SaveStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
newStorage: boolean,
|
||||
storageId: string
|
||||
}
|
||||
Body: {
|
||||
path: string;
|
||||
newStorage: boolean;
|
||||
storageId: string;
|
||||
};
|
||||
}
|
||||
export interface DeleteStorage extends OnlyId {
|
||||
Body: {
|
||||
path: string,
|
||||
}
|
||||
Body: {
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
export interface GetApplicationLogs {
|
||||
Params: {
|
||||
id: string,
|
||||
containerId: string
|
||||
}
|
||||
Querystring: {
|
||||
since: number,
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
containerId: string;
|
||||
};
|
||||
Querystring: {
|
||||
since: number;
|
||||
};
|
||||
}
|
||||
export interface GetBuilds extends OnlyId {
|
||||
Querystring: {
|
||||
buildId: string
|
||||
skip: number,
|
||||
}
|
||||
Querystring: {
|
||||
buildId: string;
|
||||
skip: number;
|
||||
};
|
||||
}
|
||||
export interface GetBuildIdLogs {
|
||||
Params: {
|
||||
id: string,
|
||||
buildId: string
|
||||
},
|
||||
Querystring: {
|
||||
sequence: number
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
buildId: string;
|
||||
};
|
||||
Querystring: {
|
||||
sequence: number;
|
||||
};
|
||||
}
|
||||
export interface SaveDeployKey extends OnlyId {
|
||||
Body: {
|
||||
deployKeyId: number
|
||||
}
|
||||
Body: {
|
||||
deployKeyId: number;
|
||||
};
|
||||
}
|
||||
export interface CancelDeployment {
|
||||
Body: {
|
||||
buildId: string,
|
||||
applicationId: string
|
||||
}
|
||||
Body: {
|
||||
buildId: string;
|
||||
applicationId: string;
|
||||
};
|
||||
}
|
||||
export interface DeployApplication extends OnlyId {
|
||||
Body: {
|
||||
pullmergeRequestId: string | null,
|
||||
branch: string,
|
||||
forceRebuild?: boolean
|
||||
}
|
||||
Body: {
|
||||
pullmergeRequestId: string | null;
|
||||
branch: string;
|
||||
forceRebuild?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StopPreviewApplication extends OnlyId {
|
||||
Body: {
|
||||
pullmergeRequestId: string | null,
|
||||
}
|
||||
Body: {
|
||||
pullmergeRequestId: string | null;
|
||||
};
|
||||
}
|
||||
export interface RestartPreviewApplication {
|
||||
Params: {
|
||||
id: string,
|
||||
pullmergeRequestId: string | null,
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
pullmergeRequestId: string | null;
|
||||
};
|
||||
}
|
||||
export interface RestartApplication {
|
||||
Params: {
|
||||
id: string,
|
||||
},
|
||||
Body: {
|
||||
imageId: string | null,
|
||||
}
|
||||
}
|
||||
Params: {
|
||||
id: string;
|
||||
};
|
||||
Body: {
|
||||
imageId: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { errorHandler, listSettings, version } from '../../../../lib/common';
|
||||
import { errorHandler, isARM, listSettings, version } from '../../../../lib/common';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
try {
|
||||
await request.jwtVerify()
|
||||
} catch(error) {
|
||||
return
|
||||
}
|
||||
});
|
||||
fastify.get('/', async (request) => {
|
||||
const teamId = request.user?.teamId;
|
||||
const settings = await listSettings()
|
||||
try {
|
||||
return {
|
||||
ipv4: teamId ? settings.ipv4 : null,
|
||||
ipv6: teamId ? settings.ipv6 : null,
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
});
|
||||
|
||||
fastify.addHook('onRequest', async (request) => {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
fastify.get('/', async (request) => {
|
||||
const teamId = request.user?.teamId;
|
||||
const settings = await listSettings();
|
||||
try {
|
||||
return {
|
||||
ipv4: teamId ? settings.ipv4 : null,
|
||||
ipv6: teamId ? settings.ipv6 : null,
|
||||
version,
|
||||
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
|
||||
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
|
||||
isRegistrationEnabled: settings.isRegistrationEnabled,
|
||||
isARM: isARM(process.arch)
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
|
||||
Body: { type: string }
|
||||
}
|
||||
export interface DeleteDatabase extends OnlyId {
|
||||
Body: { force: string }
|
||||
Body: { }
|
||||
}
|
||||
export interface SaveVersion extends OnlyId {
|
||||
Body: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { compareVersions } from "compare-versions";
|
||||
import cuid from "cuid";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import cuid from 'cuid';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import fs from 'fs/promises';
|
||||
import yaml from 'js-yaml';
|
||||
import {
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
uniqueName,
|
||||
version,
|
||||
sentryDSN,
|
||||
executeCommand,
|
||||
} from "../../../lib/common";
|
||||
import { scheduler } from "../../../lib/scheduler";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type { Login, Update } from ".";
|
||||
import type { GetCurrentUser } from "./types";
|
||||
executeCommand
|
||||
} from '../../../lib/common';
|
||||
import { scheduler } from '../../../lib/scheduler';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type { Login, Update } from '.';
|
||||
import type { GetCurrentUser } from './types';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
const saltRounds = 15;
|
||||
@@ -29,9 +29,9 @@ export async function backup(request: FastifyRequest) {
|
||||
try {
|
||||
const { backupData } = request.params;
|
||||
let std = null;
|
||||
const [id, backupType, type, zipped, storage] = backupData.split(':')
|
||||
console.log(id, backupType, type, zipped, storage)
|
||||
const database = await prisma.database.findUnique({ where: { id } })
|
||||
const [id, backupType, type, zipped, storage] = backupData.split(':');
|
||||
console.log(id, backupType, type, zipped, storage);
|
||||
const database = await prisma.database.findUnique({ where: { id } });
|
||||
if (database) {
|
||||
// await executeDockerCmd({
|
||||
// dockerId: database.destinationDockerId,
|
||||
@@ -40,8 +40,7 @@ export async function backup(request: FastifyRequest) {
|
||||
std = await executeCommand({
|
||||
dockerId: database.destinationDockerId,
|
||||
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
|
||||
})
|
||||
|
||||
});
|
||||
}
|
||||
if (std.stdout) {
|
||||
return std.stdout;
|
||||
@@ -58,9 +57,9 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
try {
|
||||
const { serverId } = request.body;
|
||||
const destination = await prisma.destinationDocker.findUnique({
|
||||
where: { id: serverId },
|
||||
where: { id: serverId }
|
||||
});
|
||||
await cleanupDockerStorage(destination.id, true, true);
|
||||
await cleanupDockerStorage(destination.id);
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -68,17 +67,25 @@ export async function cleanupManually(request: FastifyRequest) {
|
||||
}
|
||||
export async function refreshTags() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const tags = await fs.readFile('./devTags.json', 'utf8')
|
||||
await fs.writeFile('./tags.json', tags)
|
||||
let tags = await fs.readFile('./devTags.json', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTags.json')) {
|
||||
const testTags = await fs.readFile('./testTags.json', 'utf8');
|
||||
if (testTags.length > 0) {
|
||||
tags = JSON.parse(tags).concat(JSON.parse(testTags));
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
await fs.writeFile('./tags.json', tags);
|
||||
} else {
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text()
|
||||
await fs.writeFile('/app/tags.json', tags)
|
||||
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
|
||||
await fs.writeFile('/app/tags.json', tags);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -88,17 +95,25 @@ export async function refreshTags() {
|
||||
}
|
||||
export async function refreshTemplates() {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
try {
|
||||
if (isDev) {
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8')
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)))
|
||||
let templates = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
try {
|
||||
if (await fs.stat('./testTemplate.yaml')) {
|
||||
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
|
||||
}
|
||||
} catch (error) {}
|
||||
const response = await fs.readFile('./devTemplates.yaml', 'utf8');
|
||||
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
|
||||
} else {
|
||||
const response = await got.get('https://get.coollabs.io/coolify/service-templates.yaml').text()
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)))
|
||||
const response = await got
|
||||
.get('https://get.coollabs.io/coolify/service-templates.yaml')
|
||||
.text();
|
||||
await fs.writeFile('/app/templates.json', JSON.stringify(yaml.load(response)));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
@@ -107,28 +122,29 @@ export async function refreshTemplates() {
|
||||
}
|
||||
export async function checkUpdate(request: FastifyRequest) {
|
||||
try {
|
||||
const { default: got } = await import('got')
|
||||
const { default: got } = await import('got');
|
||||
const isStaging =
|
||||
request.hostname === "staging.coolify.io" ||
|
||||
request.hostname === "arm.coolify.io";
|
||||
request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io';
|
||||
const currentVersion = version;
|
||||
const { coolify } = await got.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
}).json()
|
||||
const { coolify } = await got
|
||||
.get('https://get.coollabs.io/versions.json', {
|
||||
searchParams: {
|
||||
appId: process.env['COOLIFY_APP_ID'] || undefined,
|
||||
version: currentVersion
|
||||
}
|
||||
})
|
||||
.json();
|
||||
const latestVersion = coolify.main.version;
|
||||
const isUpdateAvailable = compareVersions(latestVersion, currentVersion);
|
||||
if (isStaging) {
|
||||
return {
|
||||
isUpdateAvailable: true,
|
||||
latestVersion: "next",
|
||||
latestVersion: 'next'
|
||||
};
|
||||
}
|
||||
return {
|
||||
isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
|
||||
latestVersion,
|
||||
latestVersion
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -142,8 +158,13 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
|
||||
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
|
||||
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` });
|
||||
await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
|
||||
await executeCommand({ shell: true, command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` });
|
||||
await executeCommand({
|
||||
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
|
||||
});
|
||||
await executeCommand({
|
||||
shell: true,
|
||||
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
|
||||
});
|
||||
return {};
|
||||
} else {
|
||||
await asyncSleep(2000);
|
||||
@@ -156,12 +177,12 @@ export async function update(request: FastifyRequest<Update>) {
|
||||
export async function resetQueue(request: FastifyRequest<any>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
if (teamId === "0") {
|
||||
if (teamId === '0') {
|
||||
await prisma.build.updateMany({
|
||||
where: { status: { in: ["queued", "running"] } },
|
||||
data: { status: "canceled" },
|
||||
where: { status: { in: ['queued', 'running'] } },
|
||||
data: { status: 'canceled' }
|
||||
});
|
||||
scheduler.workers.get("deployApplication").postMessage("cancel");
|
||||
scheduler.workers.get('deployApplication').postMessage('cancel');
|
||||
}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -170,7 +191,7 @@ export async function resetQueue(request: FastifyRequest<any>) {
|
||||
export async function restartCoolify(request: FastifyRequest<any>) {
|
||||
try {
|
||||
const teamId = request.user.teamId;
|
||||
if (teamId === "0") {
|
||||
if (teamId === '0') {
|
||||
if (!isDev) {
|
||||
await executeCommand({ command: `docker restart coolify` });
|
||||
return {};
|
||||
@@ -180,7 +201,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
||||
}
|
||||
throw {
|
||||
status: 500,
|
||||
message: "You are not authorized to restart Coolify.",
|
||||
message: 'You are not authorized to restart Coolify.'
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@@ -192,43 +213,52 @@ export async function showDashboard(request: FastifyRequest) {
|
||||
const userId = request.user.userId;
|
||||
const teamId = request.user.teamId;
|
||||
let applications = await prisma.application.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
const databases = await prisma.database.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { settings: true, destinationDocker: true, teams: true }
|
||||
});
|
||||
const services = await prisma.service.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { destinationDocker: true, teams: true }
|
||||
});
|
||||
const gitSources = await prisma.gitSource.findMany({
|
||||
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] },
|
||||
include: { teams: true },
|
||||
where: {
|
||||
OR: [
|
||||
{ teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
{ isSystemWide: true }
|
||||
]
|
||||
},
|
||||
include: { teams: true }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({
|
||||
where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } },
|
||||
include: { teams: true },
|
||||
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
|
||||
include: { teams: true }
|
||||
});
|
||||
const settings = await listSettings();
|
||||
|
||||
let foundUnconfiguredApplication = false;
|
||||
for (const application of applications) {
|
||||
if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
|
||||
foundUnconfiguredApplication = true
|
||||
if (
|
||||
((!application.buildPack || !application.branch) && !application.simpleDockerfile) ||
|
||||
!application.destinationDockerId ||
|
||||
(!application.settings?.isBot && !application?.fqdn && application.buildPack !== 'compose')
|
||||
) {
|
||||
foundUnconfiguredApplication = true;
|
||||
}
|
||||
}
|
||||
let foundUnconfiguredService = false;
|
||||
for (const service of services) {
|
||||
if (!service.fqdn) {
|
||||
foundUnconfiguredService = true
|
||||
foundUnconfiguredService = true;
|
||||
}
|
||||
}
|
||||
let foundUnconfiguredDatabase = false;
|
||||
for (const database of databases) {
|
||||
if (!database.version) {
|
||||
foundUnconfiguredDatabase = true
|
||||
foundUnconfiguredDatabase = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -240,101 +270,94 @@ export async function showDashboard(request: FastifyRequest) {
|
||||
services,
|
||||
gitSources,
|
||||
destinations,
|
||||
settings,
|
||||
settings
|
||||
};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(
|
||||
request: FastifyRequest<Login>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
export async function login(request: FastifyRequest<Login>, reply: FastifyReply) {
|
||||
if (request.user) {
|
||||
return reply.redirect("/dashboard");
|
||||
return reply.redirect('/dashboard');
|
||||
} else {
|
||||
const { email, password, isLogin } = request.body || {};
|
||||
if (!email || !password) {
|
||||
throw { status: 500, message: "Email and password are required." };
|
||||
throw { status: 500, message: 'Email and password are required.' };
|
||||
}
|
||||
const users = await prisma.user.count();
|
||||
const userFound = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: { teams: true, permission: true },
|
||||
rejectOnNotFound: false,
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
if (!userFound && isLogin) {
|
||||
throw { status: 500, message: "User not found." };
|
||||
throw { status: 500, message: 'User not found.' };
|
||||
}
|
||||
const { isRegistrationEnabled, id } = await prisma.setting.findFirst();
|
||||
let uid = cuid();
|
||||
let permission = "read";
|
||||
let permission = 'read';
|
||||
let isAdmin = false;
|
||||
|
||||
if (users === 0) {
|
||||
await prisma.setting.update({
|
||||
where: { id },
|
||||
data: { isRegistrationEnabled: false },
|
||||
data: { isRegistrationEnabled: false }
|
||||
});
|
||||
uid = "0";
|
||||
uid = '0';
|
||||
}
|
||||
if (userFound) {
|
||||
if (userFound.type === "email") {
|
||||
if (userFound.password === "RESETME") {
|
||||
if (userFound.type === 'email') {
|
||||
if (userFound.password === 'RESETME') {
|
||||
const hashedPassword = await hashPassword(password);
|
||||
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {
|
||||
if (userFound.id === "0") {
|
||||
if (userFound.id === '0') {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: "RESETME" },
|
||||
data: { password: 'RESETME' }
|
||||
});
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: "RESETTIMEOUT" },
|
||||
data: { password: 'RESETTIMEOUT' }
|
||||
});
|
||||
}
|
||||
|
||||
throw {
|
||||
status: 500,
|
||||
message:
|
||||
"Password reset link has expired. Please request a new one.",
|
||||
message: 'Password reset link has expired. Please request a new one.'
|
||||
};
|
||||
} else {
|
||||
await prisma.user.update({
|
||||
where: { email: userFound.email },
|
||||
data: { password: hashedPassword },
|
||||
data: { password: hashedPassword }
|
||||
});
|
||||
return {
|
||||
userId: userFound.id,
|
||||
teamId: userFound.id,
|
||||
permission: userFound.permission,
|
||||
isAdmin: true,
|
||||
isAdmin: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(
|
||||
password,
|
||||
userFound.password
|
||||
);
|
||||
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||
if (!passwordMatch) {
|
||||
throw {
|
||||
status: 500,
|
||||
message: "Wrong password or email address.",
|
||||
message: 'Wrong password or email address.'
|
||||
};
|
||||
}
|
||||
uid = userFound.id;
|
||||
isAdmin = true;
|
||||
}
|
||||
} else {
|
||||
permission = "owner";
|
||||
permission = 'owner';
|
||||
isAdmin = true;
|
||||
if (!isRegistrationEnabled) {
|
||||
throw {
|
||||
status: 404,
|
||||
message: "Registration disabled by administrator.",
|
||||
message: 'Registration disabled by administrator.'
|
||||
};
|
||||
}
|
||||
const hashedPassword = await hashPassword(password);
|
||||
@@ -344,17 +367,17 @@ export async function login(
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: "email",
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName(),
|
||||
destinationDocker: { connect: { network: "coolify" } },
|
||||
},
|
||||
destinationDocker: { connect: { network: 'coolify' } }
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: "owner" } },
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true },
|
||||
include: { teams: true }
|
||||
});
|
||||
} else {
|
||||
await prisma.user.create({
|
||||
@@ -362,16 +385,16 @@ export async function login(
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: "email",
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName(),
|
||||
},
|
||||
name: uniqueName()
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: "owner" } },
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true },
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -379,23 +402,20 @@ export async function login(
|
||||
userId: uid,
|
||||
teamId: uid,
|
||||
permission,
|
||||
isAdmin,
|
||||
isAdmin
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUser(
|
||||
request: FastifyRequest<GetCurrentUser>,
|
||||
fastify
|
||||
) {
|
||||
export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
|
||||
let token = null;
|
||||
const { teamId } = request.query;
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: request.user.userId },
|
||||
where: { id: request.user.userId }
|
||||
});
|
||||
if (!user) {
|
||||
throw "User not found";
|
||||
throw 'User not found';
|
||||
}
|
||||
} catch (error) {
|
||||
throw { status: 401, message: error };
|
||||
@@ -404,17 +424,15 @@ export async function getCurrentUser(
|
||||
try {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { id: request.user.userId, teams: { some: { id: teamId } } },
|
||||
include: { teams: true, permission: true },
|
||||
include: { teams: true, permission: true }
|
||||
});
|
||||
if (user) {
|
||||
const permission = user.permission.find(
|
||||
(p) => p.teamId === teamId
|
||||
).permission;
|
||||
const permission = user.permission.find((p) => p.teamId === teamId).permission;
|
||||
const payload = {
|
||||
...request.user,
|
||||
teamId,
|
||||
permission: permission || null,
|
||||
isAdmin: permission === "owner" || permission === "admin",
|
||||
isAdmin: permission === 'owner' || permission === 'admin'
|
||||
};
|
||||
token = fastify.jwt.sign(payload);
|
||||
}
|
||||
@@ -422,12 +440,14 @@ export async function getCurrentUser(
|
||||
// No new token -> not switching teams
|
||||
}
|
||||
}
|
||||
const pendingInvitations = await prisma.teamInvitation.findMany({ where: { uid: request.user.userId } })
|
||||
const pendingInvitations = await prisma.teamInvitation.findMany({
|
||||
where: { uid: request.user.userId }
|
||||
});
|
||||
return {
|
||||
settings: await prisma.setting.findUnique({ where: { id: "0" } }),
|
||||
settings: await prisma.setting.findUnique({ where: { id: '0' } }),
|
||||
sentryDSN,
|
||||
pendingInvitations,
|
||||
token,
|
||||
...request.user,
|
||||
...request.user
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,11 @@ export async function listSources(request: FastifyRequest) {
|
||||
export async function saveSource(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
|
||||
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
|
||||
if (customPort) customPort = Number(customPort)
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
|
||||
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
@@ -48,6 +48,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
customUser: 'git',
|
||||
},
|
||||
settings
|
||||
}
|
||||
@@ -133,7 +134,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
|
||||
request.body
|
||||
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
@@ -142,7 +143,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
@@ -158,7 +159,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
id: newId
|
||||
}
|
||||
} else {
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
|
||||
appSecret: string,
|
||||
groupName: string,
|
||||
customPort: number,
|
||||
customUser: string,
|
||||
}
|
||||
}
|
||||
export interface CheckGitLabOAuthId extends OnlyId {
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
import { FastifyRequest } from "fastify";
|
||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common";
|
||||
import { getTemplates } from "../../../lib/services";
|
||||
import { OnlyId } from "../../../types";
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
|
||||
import { getTemplates } from '../../../lib/services';
|
||||
import { OnlyId } from '../../../types';
|
||||
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
|
||||
|
||||
function generateServices(serviceId, containerId, port) {
|
||||
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
|
||||
if (isHttp2) {
|
||||
return {
|
||||
[serviceId]: {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
[`${serviceId}-http2`]: {
|
||||
loadbalancer: {
|
||||
servers: [
|
||||
{
|
||||
url: `h2c://${containerId}:${port}`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
[serviceId]: {
|
||||
loadbalancer: {
|
||||
@@ -14,43 +37,57 @@ function generateServices(serviceId, containerId, port) {
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) {
|
||||
function generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
isDualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2 = false
|
||||
) {
|
||||
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
|
||||
let ruleWWW = `Host(\`www.${nakedDomain}\`)${
|
||||
pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
|
||||
}`;
|
||||
let http: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
}
|
||||
};
|
||||
let https: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
rule,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
}
|
||||
};
|
||||
let httpWWW: any = {
|
||||
entrypoints: ['web'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
middlewares: []
|
||||
}
|
||||
};
|
||||
let httpsWWW: any = {
|
||||
entrypoints: ['websecure'],
|
||||
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`,
|
||||
rule: ruleWWW,
|
||||
service: `${serviceId}`,
|
||||
priority: 2,
|
||||
tls: {
|
||||
certresolver: 'letsencrypt'
|
||||
},
|
||||
middlewares: []
|
||||
}
|
||||
};
|
||||
// 2. http + non-www only
|
||||
if (!isHttps && !isWWW) {
|
||||
https.middlewares.push('redirect-to-http');
|
||||
@@ -58,19 +95,19 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
||||
|
||||
httpWWW.middlewares.push('redirect-to-non-www');
|
||||
httpsWWW.middlewares.push('redirect-to-non-www');
|
||||
delete https.tls
|
||||
delete httpsWWW.tls
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
}
|
||||
|
||||
// 3. http + www only
|
||||
// 3. http + www only
|
||||
if (!isHttps && isWWW) {
|
||||
https.middlewares.push('redirect-to-http');
|
||||
httpsWWW.middlewares.push('redirect-to-http');
|
||||
|
||||
http.middlewares.push('redirect-to-www');
|
||||
https.middlewares.push('redirect-to-www');
|
||||
delete https.tls
|
||||
delete httpsWWW.tls
|
||||
delete https.tls;
|
||||
delete httpsWWW.tls;
|
||||
}
|
||||
// 5. https + non-www only
|
||||
if (isHttps && !isWWW) {
|
||||
@@ -86,17 +123,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
||||
httpsWWW.tls = true;
|
||||
} else {
|
||||
https.tls = true;
|
||||
delete httpsWWW.tls.certresolver
|
||||
delete httpsWWW.tls.certresolver;
|
||||
httpsWWW.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (!isDualCerts) {
|
||||
delete httpsWWW.tls.certresolver
|
||||
delete httpsWWW.tls.certresolver;
|
||||
httpsWWW.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,26 +151,59 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
|
||||
httpsWWW.tls = true;
|
||||
} else {
|
||||
httpsWWW.tls = true;
|
||||
delete https.tls.certresolver
|
||||
delete https.tls.certresolver;
|
||||
https.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (!isDualCerts) {
|
||||
delete https.tls.certresolver
|
||||
delete https.tls.certresolver;
|
||||
https.tls.domains = {
|
||||
main: domain
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isHttp2) {
|
||||
let http2 = {
|
||||
...http,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let http2WWW = {
|
||||
...httpWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
let https2 = {
|
||||
...https,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
|
||||
let https2WWW = {
|
||||
...httpsWWW,
|
||||
service: `${serviceId}-http2`,
|
||||
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
|
||||
};
|
||||
return {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-http2`]: { ...http2 },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-secure-http2`]: { ...https2 },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-www-http2`]: { ...http2WWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
|
||||
};
|
||||
}
|
||||
return {
|
||||
[`${serviceId}-${pathPrefix}`]: { ...http },
|
||||
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
|
||||
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
|
||||
}
|
||||
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
|
||||
};
|
||||
}
|
||||
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
|
||||
const traefik = {
|
||||
@@ -174,26 +244,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const coolifySettings = await prisma.setting.findFirst();
|
||||
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
|
||||
traefik.http.routers['catchall-http'] = {
|
||||
entrypoints: ["web"],
|
||||
rule: "HostRegexp(`{catchall:.*}`)",
|
||||
service: "noop",
|
||||
entrypoints: ['web'],
|
||||
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||
service: 'noop',
|
||||
priority: 1,
|
||||
middlewares: ["redirect-regexp"]
|
||||
}
|
||||
middlewares: ['redirect-regexp']
|
||||
};
|
||||
traefik.http.routers['catchall-https'] = {
|
||||
entrypoints: ["websecure"],
|
||||
rule: "HostRegexp(`{catchall:.*}`)",
|
||||
service: "noop",
|
||||
entrypoints: ['websecure'],
|
||||
rule: 'HostRegexp(`{catchall:.*}`)',
|
||||
service: 'noop',
|
||||
priority: 1,
|
||||
middlewares: ["redirect-regexp"]
|
||||
}
|
||||
middlewares: ['redirect-regexp']
|
||||
};
|
||||
traefik.http.middlewares['redirect-regexp'] = {
|
||||
redirectregex: {
|
||||
regex: '(.*)',
|
||||
replacement: coolifySettings.proxyDefaultRedirect,
|
||||
permanent: false
|
||||
}
|
||||
}
|
||||
};
|
||||
traefik.http.services['noop'] = {
|
||||
loadBalancer: {
|
||||
servers: [
|
||||
@@ -202,25 +272,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
const sslpath = '/etc/traefik/acme/custom';
|
||||
|
||||
let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
|
||||
let certificates = await prisma.certificate.findMany({
|
||||
where: {
|
||||
team: {
|
||||
applications: { some: { settings: { isCustomSSL: true } } },
|
||||
destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (remote) {
|
||||
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
|
||||
certificates = await prisma.certificate.findMany({
|
||||
where: {
|
||||
team: {
|
||||
applications: { some: { settings: { isCustomSSL: true } } },
|
||||
destinationDocker: {
|
||||
some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let parsedCertificates = []
|
||||
let parsedCertificates = [];
|
||||
for (const certificate of certificates) {
|
||||
parsedCertificates.push({
|
||||
certFile: `${sslpath}/${certificate.id}-cert.pem`,
|
||||
keyFile: `${sslpath}/${certificate.id}-key.pem`
|
||||
})
|
||||
});
|
||||
}
|
||||
if (parsedCertificates.length > 0) {
|
||||
traefik.tls.certificates = parsedCertificates
|
||||
traefik.tls.certificates = parsedCertificates;
|
||||
}
|
||||
|
||||
let applications = [];
|
||||
@@ -236,7 +322,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
serviceSetting: true,
|
||||
serviceSetting: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
@@ -251,23 +337,25 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
destinationDocker: true,
|
||||
persistentStorage: true,
|
||||
serviceSecret: true,
|
||||
serviceSetting: true,
|
||||
serviceSetting: true
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (applications.length > 0) {
|
||||
const dockerIds = new Set()
|
||||
const runningContainers = {}
|
||||
const dockerIds = new Set();
|
||||
const runningContainers = {};
|
||||
applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||
for (const dockerId of dockerIds) {
|
||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
||||
const { stdout: container } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||
});
|
||||
if (container) {
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray
|
||||
runningContainers[dockerId] = containersArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,38 +377,54 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
if (
|
||||
!runningContainers[destinationDockerId] ||
|
||||
runningContainers[destinationDockerId].length === 0 ||
|
||||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id)).length === 0
|
||||
runningContainers[destinationDockerId].filter((container) => container.startsWith(id))
|
||||
.length === 0
|
||||
) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
if (buildPack === 'compose') {
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
|
||||
const services = Object.entries(JSON.parse(dockerComposeConfiguration));
|
||||
if (services.length > 0) {
|
||||
for (const service of services) {
|
||||
const [key, value] = service
|
||||
const [key, value] = service;
|
||||
if (key && value) {
|
||||
if (!value.fqdn || !value.port) {
|
||||
continue;
|
||||
}
|
||||
const { fqdn, port } = value
|
||||
const containerId = `${id}-${key}`
|
||||
const { fqdn, port } = value;
|
||||
const containerId = `${id}-${key}`;
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const dualCerts = false;
|
||||
const serviceId = `${id}-${port || 'default'}`
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) }
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, containerId, port)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const { previews, dualCerts, isCustomSSL } = settings;
|
||||
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
|
||||
const { network, id: dockerId } = destinationDocker;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
@@ -329,12 +433,31 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
const serviceId = `${id}-${port || 'default'}`
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL,
|
||||
isHttp2
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port, isHttp2, isHttps)
|
||||
};
|
||||
if (previews) {
|
||||
const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
|
||||
});
|
||||
if (stdout) {
|
||||
const containers = stdout
|
||||
.trim()
|
||||
@@ -343,44 +466,57 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
.map((c) => c.replace(/"/g, ''));
|
||||
if (containers.length > 0) {
|
||||
for (const container of containers) {
|
||||
const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator}${domain}`;
|
||||
const previewDomain = `${container.split('-')[1]}${
|
||||
coolifySettings.previewSeparator
|
||||
}${domain}`;
|
||||
const nakedDomain = previewDomain.replace(/^www\./, '');
|
||||
const pathPrefix = '/'
|
||||
const serviceId = `${container}-${port || 'default'}`
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
||||
const pathPrefix = '/';
|
||||
const serviceId = `${container}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
previewDomain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port, isHttp2)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (services.length > 0) {
|
||||
const dockerIds = new Set()
|
||||
const runningContainers = {}
|
||||
const dockerIds = new Set();
|
||||
const runningContainers = {};
|
||||
services.forEach((app) => dockerIds.add(app.destinationDocker.id));
|
||||
for (const dockerId of dockerIds) {
|
||||
const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
|
||||
const { stdout: container } = await executeCommand({
|
||||
dockerId,
|
||||
command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'`
|
||||
});
|
||||
if (container) {
|
||||
const containersArray = container.trim().split('\n');
|
||||
if (containersArray.length > 0) {
|
||||
runningContainers[dockerId] = containersArray
|
||||
runningContainers[dockerId] = containersArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const service of services) {
|
||||
try {
|
||||
let {
|
||||
fqdn,
|
||||
id,
|
||||
type,
|
||||
destinationDockerId,
|
||||
dualCerts,
|
||||
serviceSetting
|
||||
} = service;
|
||||
let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
|
||||
if (!fqdn) {
|
||||
continue;
|
||||
}
|
||||
@@ -392,7 +528,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
runningContainers[destinationDockerId].length === 0 ||
|
||||
!runningContainers[destinationDockerId].includes(id)
|
||||
) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
const templates = await getTemplates();
|
||||
let found = templates.find((a) => a.type === type);
|
||||
@@ -401,88 +537,144 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
}
|
||||
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
|
||||
for (const oneService of Object.keys(found.services)) {
|
||||
const isDomainConfiguration = found?.services[oneService]?.proxy?.filter(p => p.domain) ?? [];
|
||||
if (isDomainConfiguration.length > 0) {
|
||||
const { proxy } = found.services[oneService];
|
||||
const isDomainAndProxyConfiguration =
|
||||
found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
|
||||
if (isDomainAndProxyConfiguration.length > 0) {
|
||||
const template: any = await parseAndFindServiceTemplates(service, null, true);
|
||||
const { proxy } = template.services[oneService] || found.services[oneService];
|
||||
for (let configuration of proxy) {
|
||||
if (configuration.domain) {
|
||||
const setting = serviceSetting.find((a) => a.variableName === configuration.domain);
|
||||
const setting = serviceSetting.find(
|
||||
(a) => a.variableName === configuration.domain
|
||||
);
|
||||
if (setting) {
|
||||
configuration.domain = configuration.domain.replace(configuration.domain, setting.value);
|
||||
configuration.domain = configuration.domain.replace(
|
||||
configuration.domain,
|
||||
setting.value
|
||||
);
|
||||
}
|
||||
}
|
||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
||||
const foundPortVariable = serviceSetting.find(
|
||||
(a) => a.name.toLowerCase() === 'port'
|
||||
);
|
||||
if (foundPortVariable) {
|
||||
configuration.port = foundPortVariable.value
|
||||
configuration.port = foundPortVariable.value;
|
||||
}
|
||||
let port, pathPrefix, customDomain;
|
||||
if (configuration) {
|
||||
port = configuration?.port;
|
||||
pathPrefix = configuration?.pathPrefix || '/';
|
||||
customDomain = configuration?.domain
|
||||
customDomain = configuration?.domain;
|
||||
}
|
||||
if (customDomain) {
|
||||
fqdn = customDomain
|
||||
fqdn = customDomain;
|
||||
} else {
|
||||
fqdn = service.fqdn
|
||||
fqdn = service.fqdn;
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const isCustomSSL = false;
|
||||
const serviceId = `${oneService}-${port || 'default'}`
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) }
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, oneService, port)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
|
||||
for (let [index, port] of found.services[oneService].ports.entries()) {
|
||||
if (port == 22) continue;
|
||||
if (index === 0) {
|
||||
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port')
|
||||
const foundPortVariable = serviceSetting.find(
|
||||
(a) => a.name.toLowerCase() === 'port'
|
||||
);
|
||||
if (foundPortVariable) {
|
||||
port = foundPortVariable.value
|
||||
port = foundPortVariable.value;
|
||||
}
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const pathPrefix = '/'
|
||||
const isCustomSSL = false
|
||||
const serviceId = `${oneService}-${port || 'default'}`
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const serviceId = `${oneService}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, id, port)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!remote) {
|
||||
const { fqdn, dualCerts } = await prisma.setting.findFirst();
|
||||
if (!fqdn) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const domain = getDomain(fqdn);
|
||||
const nakedDomain = domain.replace(/^www\./, '');
|
||||
const isHttps = fqdn.startsWith('https://');
|
||||
const isWWW = fqdn.includes('www.');
|
||||
const id = isDev ? 'host.docker.internal' : 'coolify'
|
||||
const container = isDev ? 'host.docker.internal' : 'coolify'
|
||||
const port = 3000
|
||||
const pathPrefix = '/'
|
||||
const isCustomSSL = false
|
||||
const serviceId = `${id}-${port || 'default'}`
|
||||
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
|
||||
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) }
|
||||
const id = isDev ? 'host.docker.internal' : 'coolify';
|
||||
const container = isDev ? 'host.docker.internal' : 'coolify';
|
||||
const port = 3000;
|
||||
const pathPrefix = '/';
|
||||
const isCustomSSL = false;
|
||||
const serviceId = `${id}-${port || 'default'}`;
|
||||
traefik.http.routers = {
|
||||
...traefik.http.routers,
|
||||
...generateRouters(
|
||||
serviceId,
|
||||
domain,
|
||||
nakedDomain,
|
||||
pathPrefix,
|
||||
isHttps,
|
||||
isWWW,
|
||||
dualCerts,
|
||||
isCustomSSL
|
||||
)
|
||||
};
|
||||
traefik.http.services = {
|
||||
...traefik.http.services,
|
||||
...generateServices(serviceId, container, port)
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log(error);
|
||||
} finally {
|
||||
if (Object.keys(traefik.http.routers).length === 0) {
|
||||
traefik.http.routers = null;
|
||||
@@ -496,9 +688,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
|
||||
|
||||
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
|
||||
try {
|
||||
const { id } = request.query
|
||||
const { id } = request.query;
|
||||
if (id) {
|
||||
const { privatePort, publicPort, type, address = id } = request.query
|
||||
const { privatePort, publicPort, type, address = id } = request.query;
|
||||
let traefik = {};
|
||||
if (publicPort && type && privatePort) {
|
||||
if (type === 'tcp') {
|
||||
@@ -559,18 +751,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
}
|
||||
return {
|
||||
...traefik
|
||||
};
|
||||
}
|
||||
throw { status: 500 }
|
||||
throw { status: 500 };
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
|
||||
import { OtherProxyConfiguration } from './types';
|
||||
|
||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request));
|
||||
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false));
|
||||
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
|
||||
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) =>
|
||||
otherProxyConfiguration(request)
|
||||
);
|
||||
};
|
||||
|
||||
export default root;
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
13
apps/trpc-experimental/client/.eslintignore
Normal file
13
apps/trpc-experimental/client/.eslintignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
20
apps/trpc-experimental/client/.eslintrc.cjs
Normal file
20
apps/trpc-experimental/client/.eslintrc.cjs
Normal file
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
plugins: ['svelte3', '@typescript-eslint'],
|
||||
ignorePatterns: ['*.cjs'],
|
||||
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
||||
settings: {
|
||||
'svelte3/typescript': () => require('typescript')
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
}
|
||||
};
|
||||
10
apps/trpc-experimental/client/.gitignore
vendored
Normal file
10
apps/trpc-experimental/client/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
1
apps/trpc-experimental/client/.npmrc
Normal file
1
apps/trpc-experimental/client/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
engine-strict=true
|
||||
13
apps/trpc-experimental/client/.prettierignore
Normal file
13
apps/trpc-experimental/client/.prettierignore
Normal file
@@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
9
apps/trpc-experimental/client/.prettierrc
Normal file
9
apps/trpc-experimental/client/.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
1
apps/trpc-experimental/client/README.md
Normal file
1
apps/trpc-experimental/client/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# SvelteKit Static site
|
||||
54
apps/trpc-experimental/client/package.json
Normal file
54
apps/trpc-experimental/client/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "client",
|
||||
"description": "Coolify's SvelteKit UI",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build && cp -Pr build/ ../../build/public",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.28.1",
|
||||
"@sveltejs/adapter-static": "1.0.0-next.48",
|
||||
"@sveltejs/kit": "1.0.0-next.572",
|
||||
"@types/js-cookie": "3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.44.0",
|
||||
"@typescript-eslint/parser": "5.44.0",
|
||||
"autoprefixer": "10.4.13",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-svelte3": "4.0.0",
|
||||
"postcss": "8.4.19",
|
||||
"postcss-load-config": "4.0.1",
|
||||
"prettier": "2.8.0",
|
||||
"prettier-plugin-svelte": "2.8.1",
|
||||
"svelte": "3.53.1",
|
||||
"svelte-check": "2.9.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "3.2.4",
|
||||
"tslib": "2.4.1",
|
||||
"typescript": "4.9.3",
|
||||
"vite": "3.2.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@trpc/client": "10.1.0",
|
||||
"@trpc/server": "10.1.0",
|
||||
"cuid": "2.1.8",
|
||||
"daisyui": "2.41.0",
|
||||
"dayjs": "1.11.6",
|
||||
"flowbite-svelte": "0.28.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"p-limit": "4.0.0",
|
||||
"server": "workspace:*",
|
||||
"superjson": "1.11.0",
|
||||
"svelte-select": "4.4.7"
|
||||
}
|
||||
}
|
||||
10
apps/trpc-experimental/client/playwright.config.ts
Normal file
10
apps/trpc-experimental/client/playwright.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
1793
apps/trpc-experimental/client/pnpm-lock.yaml
generated
Normal file
1793
apps/trpc-experimental/client/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
apps/trpc-experimental/client/postcss.config.cjs
Normal file
13
apps/trpc-experimental/client/postcss.config.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const tailwindcss = require('tailwindcss');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss(),
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
12
apps/trpc-experimental/client/src/app.d.ts
vendored
Normal file
12
apps/trpc-experimental/client/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
|
||||
declare const GITPOD_WORKSPACE_URL: string;
|
||||
declare const CODESANDBOX_HOST: string;
|
||||
12
apps/trpc-experimental/client/src/app.html
Normal file
12
apps/trpc-experimental/client/src/app.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-screen">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
284
apps/trpc-experimental/client/src/app.postcss
Normal file
284
apps/trpc-experimental/client/src/app.postcss
Normal file
@@ -0,0 +1,284 @@
|
||||
/* Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-regular.woff2') format('woff2'),
|
||||
url('/poppins-v19-latin-ext_latin_devanagari-regular.woff') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-500.woff2') format('woff2'),
|
||||
url('/poppins-v19-latin-ext_latin_devanagari-500.woff') format('woff');
|
||||
}
|
||||
|
||||
button {
|
||||
@apply text-sm !important;
|
||||
}
|
||||
html {
|
||||
@apply h-full min-h-full overflow-y-scroll;
|
||||
}
|
||||
body {
|
||||
@apply min-h-screen overflow-x-hidden bg-coolblack text-sm text-white scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
|
||||
}
|
||||
|
||||
input,
|
||||
.input {
|
||||
@apply h-12 w-96 rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent disabled:bg-coolblack md:text-sm;
|
||||
}
|
||||
textarea {
|
||||
@apply min-w-[14rem] rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent md:text-sm;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer.disabled input {
|
||||
@apply placeholder:text-stone-600;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer input {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
#svelte .custom-select-wrapper .selectContainer {
|
||||
@apply h-12 rounded bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
|
||||
}
|
||||
|
||||
#svelte .listContainer {
|
||||
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
|
||||
}
|
||||
#svelte .selectedItem {
|
||||
@apply pl-2;
|
||||
}
|
||||
|
||||
#svelte .item.hover {
|
||||
@apply bg-coollabs text-white !important;
|
||||
}
|
||||
#svelte .item.active {
|
||||
@apply bg-coolgray-100 text-white;
|
||||
}
|
||||
|
||||
select {
|
||||
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
|
||||
}
|
||||
.custom-select-wrapper {
|
||||
--background: rgb(32 32 32);
|
||||
--inputColor: white;
|
||||
--multiItemPadding: 0;
|
||||
--multiSelectPadding: 0 0.5rem 0 0.5rem;
|
||||
--border: none;
|
||||
--placeholderColor: rgb(87 83 78);
|
||||
--listBackground: rgb(32 32 32);
|
||||
--itemColor: white;
|
||||
--itemHoverBG: rgb(107 22 237);
|
||||
--multiItemBG: rgb(32 32 32);
|
||||
--multiClearHoverBG: transparent;
|
||||
--multiClearHoverFill: rgb(239 68 68);
|
||||
--multiItemActiveBG: transparent;
|
||||
--multiClearBG: transparent;
|
||||
--clearSelectFocusColor: white;
|
||||
--clearSelectHoverColor: rgb(239 68 68);
|
||||
--multiItemBorderRadius: 0.25rem;
|
||||
--listShadow: none;
|
||||
}
|
||||
|
||||
label {
|
||||
@apply inline-block;
|
||||
}
|
||||
.btn {
|
||||
@apply text-white text-base min-w-fit no-animation;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply underline hover:text-white;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply p-2 px-4;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-lg lg:text-2xl font-bold;
|
||||
}
|
||||
.subtitle {
|
||||
@apply text-lg lg:text-xl font-bold text-indigo-300;
|
||||
}
|
||||
.label {
|
||||
@apply text-sm leading-6 font-semibold text-sky-500 dark:text-sky-400;
|
||||
}
|
||||
.card {
|
||||
@apply border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2 sticky top-4 mb-2 items-center;
|
||||
}
|
||||
.icon-holder {
|
||||
overflow: hidden;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
margin-right: 8px;
|
||||
background: linear-gradient(0deg, #999, #ddd);
|
||||
}
|
||||
.instance-status-running {
|
||||
box-shadow: 1px 4px 5px #3df721;
|
||||
}
|
||||
.instance-status-stopped {
|
||||
box-shadow: 1px 4px 5px rgb(110, 191, 225);
|
||||
}
|
||||
.instance-status-error {
|
||||
box-shadow: 1px 4px 5px #fb00ff;
|
||||
}
|
||||
.instance-status-degraded {
|
||||
box-shadow: 1px 4px 5px #f7b121;
|
||||
}
|
||||
.badge-status-healthy,
|
||||
.badge-status-running {
|
||||
@apply text-green-500;
|
||||
}
|
||||
.badge-status-degraded {
|
||||
@apply text-green-500;
|
||||
}
|
||||
.badge-status-stopped {
|
||||
@apply text-sky-500;
|
||||
}
|
||||
.delete-button {
|
||||
@apply bg-red-600;
|
||||
}
|
||||
.delete-button:hover {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
/* Interchange menu position */
|
||||
.menu-left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.menu-left .menu-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.menu-left .menu-bar > * {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.menu-top {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.menu-top .menu-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.menu-top .menu-bar > * {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.nav-main {
|
||||
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-hidden border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
|
||||
}
|
||||
|
||||
.nav-side {
|
||||
@apply absolute right-0 top-0 z-50 m-5 flex flex-wrap items-center justify-end space-x-2 bg-coolblack/40 text-white;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
@apply rounded p-1 transition duration-200;
|
||||
}
|
||||
|
||||
.icons {
|
||||
@apply rounded p-2 transition duration-200 hover:bg-coolgray-500 disabled:bg-coolblack disabled:text-coolgray-500 !important;
|
||||
}
|
||||
|
||||
.arrow-right-applications {
|
||||
@apply -ml-6 px-2 font-bold text-green-500;
|
||||
}
|
||||
|
||||
.border-gradient {
|
||||
border-bottom: 2px solid transparent;
|
||||
-o-border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
.border-gradient-full {
|
||||
border: 4px solid transparent;
|
||||
-o-border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image: linear-gradient(
|
||||
0.25turn,
|
||||
rgba(255, 249, 34),
|
||||
rgba(255, 0, 128),
|
||||
rgba(56, 2, 155, 0)
|
||||
);
|
||||
border-image-slice: 1;
|
||||
}
|
||||
|
||||
.box-selection {
|
||||
@apply min-w-[16rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
|
||||
}
|
||||
|
||||
.lds-heart {
|
||||
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
}
|
||||
@keyframes lds-heart {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
5% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
39% {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
45% {
|
||||
transform: scale(1);
|
||||
}
|
||||
60% {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
@apply w-48 text-base font-bold hover:bg-coolgray-500 rounded p-2 hover:text-white text-stone-200 cursor-pointer;
|
||||
}
|
||||
|
||||
.sub-menu-active {
|
||||
@apply bg-coolgray-500 text-white;
|
||||
}
|
||||
|
||||
.table tbody td,
|
||||
.table tbody th,
|
||||
.table thead th {
|
||||
background-color: transparent;
|
||||
}
|
||||
.table * {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply flex flex-row z-10 w-full py-5 px-5;
|
||||
}
|
||||
.burger {
|
||||
@apply block m-[2px] h-[3px] w-5 rounded;
|
||||
}
|
||||
|
||||
.bg-coollabs-gradient {
|
||||
@apply bg-gradient-to-r from-purple-500 via-pink-500 to-red-500;
|
||||
}
|
||||
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>
|
||||
@@ -0,0 +1,64 @@
|
||||
<script>
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
export let type = 'info';
|
||||
function success() {
|
||||
if (type === 'success') {
|
||||
return 'bg-dark lg:bg-primary';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click={() => dispatch('click')}
|
||||
on:mouseover={() => dispatch('pause')}
|
||||
on:focus={() => dispatch('pause')}
|
||||
on:mouseout={() => dispatch('resume')}
|
||||
on:blur={() => dispatch('resume')}
|
||||
class={` flex flex-row justify-center alert shadow-lg text-white hover:scale-105 transition-all duration-100 cursor-pointer rounded ${success()}`}
|
||||
class:alert-error={type === 'error'}
|
||||
class:alert-info={type === 'info'}
|
||||
>
|
||||
{#if type === 'success'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{:else if type === 'error'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current flex-shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{:else if type === 'info'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
class="stroke-current flex-shrink-0 w-6 h-6"
|
||||
><path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/></svg
|
||||
>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Toast from './Toast.svelte';
|
||||
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
|
||||
</script>
|
||||
|
||||
{#if $toasts.length > 0}
|
||||
<section>
|
||||
<article class="toast toast-top toast-center rounded-none w-2/3 lg:w-[20rem]" role="alert">
|
||||
{#each $toasts as toast (toast.id)}
|
||||
<Toast
|
||||
type={toast.type}
|
||||
on:resume={() => resumeToast(toast.id)}
|
||||
on:pause={() => pauseToast(toast.id)}
|
||||
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
|
||||
>
|
||||
{/each}
|
||||
</article>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
section {
|
||||
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Tooltip } from 'flowbite-svelte';
|
||||
export let placement = 'bottom';
|
||||
export let color = 'bg-coollabs';
|
||||
export let triggeredBy = '#tooltip-default';
|
||||
</script>
|
||||
|
||||
<Tooltip {triggeredBy} {placement} arrow={false} defaultClass={color + ' font-thin text-xs text-left border-none p-2'} style="custom"
|
||||
><slot /></Tooltip
|
||||
>
|
||||
@@ -0,0 +1,206 @@
|
||||
<script lang="ts">
|
||||
import { dev } from '$app/environment';
|
||||
import {
|
||||
addToast,
|
||||
appSession,
|
||||
features,
|
||||
updateLoading,
|
||||
isUpdateAvailable,
|
||||
latestVersion
|
||||
} from '$lib/store';
|
||||
import { asyncSleep, errorNotification } from '$lib/common';
|
||||
import { onMount } from 'svelte';
|
||||
import Tooltip from './Tooltip.svelte';
|
||||
|
||||
let updateStatus: any = {
|
||||
found: false,
|
||||
loading: false,
|
||||
success: null
|
||||
};
|
||||
async function update() {
|
||||
updateStatus.loading = true;
|
||||
try {
|
||||
if (dev) {
|
||||
localStorage.setItem('lastVersion', $appSession.version);
|
||||
await asyncSleep(1000);
|
||||
updateStatus.loading = false;
|
||||
return window.location.reload();
|
||||
} else {
|
||||
localStorage.setItem('lastVersion', $appSession.version);
|
||||
// await post(`/update`, { type: 'update', latestVersion: $latestVersion });
|
||||
addToast({
|
||||
message: 'Update completed.<br><br>Waiting for the new version to start...',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
let reachable = false;
|
||||
let tries = 0;
|
||||
do {
|
||||
await asyncSleep(4000);
|
||||
try {
|
||||
// await get(`/undead`);
|
||||
reachable = true;
|
||||
} catch (error) {
|
||||
reachable = false;
|
||||
}
|
||||
if (reachable) break;
|
||||
tries++;
|
||||
} while (!reachable || tries < 120);
|
||||
addToast({
|
||||
message: 'New version reachable. Reloading...',
|
||||
type: 'success'
|
||||
});
|
||||
updateStatus.loading = false;
|
||||
updateStatus.success = true;
|
||||
await asyncSleep(3000);
|
||||
return window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
updateStatus.success = false;
|
||||
updateStatus.loading = false;
|
||||
return errorNotification(error);
|
||||
}
|
||||
}
|
||||
onMount(async () => {
|
||||
if ($appSession.userId) {
|
||||
const overrideVersion = $features.latestVersion;
|
||||
if ($appSession.teamId === '0') {
|
||||
if ($updateLoading === true) return;
|
||||
try {
|
||||
$updateLoading = true;
|
||||
// const data = await get(`/update`);
|
||||
if (overrideVersion || data?.isUpdateAvailable) {
|
||||
$latestVersion = overrideVersion || data.latestVersion;
|
||||
if (overrideVersion) {
|
||||
$isUpdateAvailable = true;
|
||||
} else {
|
||||
$isUpdateAvailable = data.isUpdateAvailable;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$updateLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="py-0 lg:py-2">
|
||||
{#if $appSession.teamId === '0'}
|
||||
{#if $isUpdateAvailable}
|
||||
<button
|
||||
id="update"
|
||||
disabled={updateStatus.success === false}
|
||||
on:click={update}
|
||||
class="icons bg-coollabs-gradient text-white duration-75 hover:scale-105 w-full"
|
||||
>
|
||||
{#if updateStatus.loading}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="lds-heart h-8 w-8 mx-auto"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"
|
||||
/>
|
||||
</svg>
|
||||
{:else if updateStatus.success === null}
|
||||
<div class="flex items-center justify-center space-x-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="12" y1="8" x2="8" y2="12" />
|
||||
<line x1="12" y1="8" x2="12" y2="16" />
|
||||
<line x1="16" y1="12" x2="12" y2="8" />
|
||||
</svg>
|
||||
<span class="flex lg:hidden">Update available</span>
|
||||
</div>
|
||||
{:else if updateStatus.success}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-8 w-8"
|
||||
><path
|
||||
fill="#DD2E44"
|
||||
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
|
||||
/><path
|
||||
fill="#EA596E"
|
||||
d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"
|
||||
/><path
|
||||
fill="#A0041E"
|
||||
d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"
|
||||
/><path
|
||||
fill="#AA8DD8"
|
||||
d="M18.59 13.609c-.199.161-.459.245-.734.215-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532-.549-.06-.947-.552-.886-1.102.059-.549.55-.946 1.101-.886 2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532.548.06.947.553.886 1.102-.028.274-.167.511-.366.671z"
|
||||
/><path
|
||||
fill="#77B255"
|
||||
d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095.53-.149 1.084.16 1.233.691.148.532-.161 1.084-.693 1.234-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096-.532.15-1.083-.16-1.233-.691-.149-.53.161-1.082.693-1.232z"
|
||||
/><path
|
||||
fill="#AA8DD8"
|
||||
d="M23.001 20.16c-.294 0-.584-.129-.782-.375-.345-.432-.274-1.061.156-1.406.218-.175 5.418-4.259 12.767-3.208.547.078.927.584.849 1.131-.078.546-.58.93-1.132.848-6.493-.922-11.187 2.754-11.233 2.791-.186.148-.406.219-.625.219z"
|
||||
/><path
|
||||
fill="#77B255"
|
||||
d="M5.754 16c-.095 0-.192-.014-.288-.042-.529-.159-.829-.716-.67-1.245 1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071.042.551-.372 1.031-.922 1.072-.559.034-1.031-.372-1.072-.923-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192-.13.433-.528.712-.958.712z"
|
||||
/><circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5" /><circle
|
||||
fill="#9266CC"
|
||||
cx="2"
|
||||
cy="18"
|
||||
r="2"
|
||||
/><circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5" /><circle
|
||||
fill="#5C913B"
|
||||
cx="23.5"
|
||||
cy="31.5"
|
||||
r="1.5"
|
||||
/><circle fill="#FFCC4D" cx="28" cy="4" r="2" /><circle
|
||||
fill="#FFCC4D"
|
||||
cx="32.5"
|
||||
cy="8.5"
|
||||
r="1.5"
|
||||
/><circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5" /><circle
|
||||
fill="#FFCC4D"
|
||||
cx="7.5"
|
||||
cy="23.5"
|
||||
r="1.5"
|
||||
/></svg
|
||||
>
|
||||
{:else}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
|
||||
><path
|
||||
fill="#FFCC4D"
|
||||
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
|
||||
/><path
|
||||
fill="#664500"
|
||||
d="M22 27c0 2.763-1.791 3-4 3-2.21 0-4-.237-4-3 0-2.761 1.79-6 4-6 2.209 0 4 3.239 4 6zm8-12c-.124 0-.25-.023-.371-.072-5.229-2.091-7.372-5.241-7.461-5.374-.307-.46-.183-1.081.277-1.387.459-.306 1.077-.184 1.385.274.019.027 1.93 2.785 6.541 4.629.513.206.763.787.558 1.3-.157.392-.533.63-.929.63zM6 15c-.397 0-.772-.238-.929-.629-.205-.513.044-1.095.557-1.3 4.612-1.844 6.523-4.602 6.542-4.629.308-.456.929-.577 1.387-.27.457.308.581.925.275 1.383-.089.133-2.232 3.283-7.46 5.374C6.25 14.977 6.124 15 6 15z"
|
||||
/><path fill="#5DADEC" d="M24 16h4v19l-4-.046V16zM8 35l4-.046V16H8v19z" /><path
|
||||
fill="#664500"
|
||||
d="M14.999 18c-.15 0-.303-.034-.446-.105-3.512-1.756-7.07-.018-7.105 0-.495.249-1.095.046-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.498-2.197 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552zm14 0c-.15 0-.303-.034-.446-.105-3.513-1.756-7.07-.018-7.105 0-.494.248-1.094.047-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.501-2.196 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552z"
|
||||
/><ellipse fill="#5DADEC" cx="18" cy="34" rx="18" ry="2" /><ellipse
|
||||
fill="#E75A70"
|
||||
cx="18"
|
||||
cy="27"
|
||||
rx="3"
|
||||
ry="2"
|
||||
/></svg
|
||||
>
|
||||
{/if}
|
||||
</button>
|
||||
<Tooltip triggeredBy="#update" placement="right" color="bg-coolgray-200 text-white"
|
||||
>New Version Available!</Tooltip
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<line x1="4" y1="7" x2="20" y2="7" />
|
||||
<line x1="10" y1="11" x2="10" y2="17" />
|
||||
<line x1="14" y1="11" x2="14" y2="17" />
|
||||
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
|
||||
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -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: 262 B |
@@ -0,0 +1,47 @@
|
||||
<script lang="ts">
|
||||
import * as Icons from '$lib/components/icons/applications';
|
||||
export let application: any;
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
{#if application.buildPack?.toLowerCase() === 'rust'}
|
||||
<Icons.Rust {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'node'}
|
||||
<Icons.Nodejs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'react'}
|
||||
<Icons.React {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'svelte'}
|
||||
<Icons.Svelte {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'vuejs'}
|
||||
<Icons.Vuejs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'php'}
|
||||
<Icons.Php {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'python'}
|
||||
<Icons.Python {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'static'}
|
||||
<Icons.Static {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nestjs'}
|
||||
<Icons.Nestjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nuxtjs'}
|
||||
<Icons.Nuxtjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'nextjs'}
|
||||
<Icons.Nextjs {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'gatsby'}
|
||||
<Icons.Gatsby {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'docker'}
|
||||
<Icons.Docker {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'astro'}
|
||||
<Icons.Astro {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'eleventy'}
|
||||
<Icons.Eleventy {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'deno'}
|
||||
<Icons.Deno {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'laravel'}
|
||||
<Icons.Laravel {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'heroku'}
|
||||
<Icons.Heroku {isAbsolute} />
|
||||
{:else if application.buildPack?.toLowerCase() === 'compose'}
|
||||
<Icons.Compose {isAbsolute} />
|
||||
{:else if application.simpleDockerfile}
|
||||
<Icons.Docker {isAbsolute} />
|
||||
{/if}
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 256 256"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
id="a"
|
||||
fill="#302649"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
|
||||
/>
|
||||
<path
|
||||
id="flame"
|
||||
fill="#EF661E"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = false;
|
||||
</script>
|
||||
|
||||
<img
|
||||
alt="docker compose logo"
|
||||
class={isAbsolute ? 'w-16 h-16 absolute top-0 left-0 -m-8' : 'w-8 h-8 mx-auto'}
|
||||
src="/icons/compose.png"
|
||||
/>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
|
||||
<svg viewBox="0 0 128 128" class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12' : 'mx-auto w-10 h-10'}>
|
||||
<path d="M124.8 52.1c-4.3-2.5-10-2.8-14.8-1.4-.6-5.2-4-9.7-8-12.9l-1.6-1.3-1.4 1.6c-2.7 3.1-3.5 8.3-3.1 12.3.3 2.9 1.2 5.9 3 8.3-1.4.8-2.9 1.9-4.3 2.4-2.8 1-5.9 2-8.9 2H79V49H66V24H51v12H26v13H13v14H1.8l-.2 1.5c-.5 6.4.3 12.6 3 18.5l1.1 2.2.1.2c7.9 13.4 21.7 19 36.8 19 29.2 0 53.3-13.1 64.3-40.6 7.4.4 15-1.8 18.6-8.9l.9-1.8-1.6-1zM28 39h10v11H28V39zm13.1 44.2c0 1.7-1.4 3.1-3.1 3.1-1.7 0-3.1-1.4-3.1-3.1 0-1.7 1.4-3.1 3.1-3.1 1.7.1 3.1 1.4 3.1 3.1zM28 52h10v11H28V52zm-13 0h11v11H15V52zm27.7 50.2c-15.8-.1-24.3-5.4-31.3-12.4 2.1.1 4.1.2 5.9.2 1.6 0 3.2 0 4.7-.1 3.9-.2 7.3-.7 10.1-1.5 2.3 5.3 6.5 10.2 14 13.8h-3.4zM51 63H40V52h11v11zm0-13H40V39h11v11zm13 13H53V52h11v11zm0-13H53V39h11v11zm0-13H53V26h11v11zm13 26H66V52h11v11zM38.8 81.2c-.2-.1-.5-.2-.8-.2-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2s2.2-1 2.2-2.2c0-.3-.1-.6-.2-.8-.2.3-.4.5-.8.5-.5 0-.9-.4-.9-.9.1-.4.3-.7.5-.8z" fill="#019BC6"></path>
|
||||
</svg>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 128 128"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
|
||||
>
|
||||
<path fill="transparent" d="M18 0h92v128H18z" /><path
|
||||
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
fill="#64328B"
|
||||
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
|
||||
viewBox="0 0 72 80"
|
||||
>
|
||||
<path
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#430098"
|
||||
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 50 52"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><title>Logomark</title><path
|
||||
d="M49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1-.402.694l-9.209 5.302V39.25c0 .286-.152.55-.4.694L20.42 51.01c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1-.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054L.402 39.944A.801.801 0 0 1 0 39.25V6.334c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216l17.62-10.144zM1.602 7.719v31.068L19.22 48.93v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-.002-21.481L4.965 9.654 1.602 7.72zm8.81-5.994L2.405 6.334l8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764l4.645-2.674V7.719l-3.363 1.936-4.646 2.675v20.096l3.364-1.937zM39.243 7.164l-8.006 4.609 8.006 4.609 8.005-4.61-8.005-4.608zm-.801 10.605l-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937v-9.124zM20.02 38.33l11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833 7.993 4.524z"
|
||||
fill="#FF2D20"
|
||||
fill-rule="evenodd"
|
||||
/></svg
|
||||
>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
|
||||
: 'mx-auto w-8 h-8 fill-current text-blue-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
data-prefix="fab"
|
||||
data-icon="node-js"
|
||||
role="img"
|
||||
viewBox="0 0 448 512"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M224 508c-6.7 0-13.5-1.8-19.4-5.2l-61.7-36.5c-9.2-5.2-4.7-7-1.7-8 12.3-4.3 14.8-5.2 27.9-12.7 1.4-.8 3.2-.5 4.6.4l47.4 28.1c1.7 1 4.1 1 5.7 0l184.7-106.6c1.7-1 2.8-3 2.8-5V149.3c0-2.1-1.1-4-2.9-5.1L226.8 37.7c-1.7-1-4-1-5.7 0L36.6 144.3c-1.8 1-2.9 3-2.9 5.1v213.1c0 2 1.1 4 2.9 4.9l50.6 29.2c27.5 13.7 44.3-2.4 44.3-18.7V167.5c0-3 2.4-5.3 5.4-5.3h23.4c2.9 0 5.4 2.3 5.4 5.3V378c0 36.6-20 57.6-54.7 57.6-10.7 0-19.1 0-42.5-11.6l-48.4-27.9C8.1 389.2.7 376.3.7 362.4V149.3c0-13.8 7.4-26.8 19.4-33.7L204.6 9c11.7-6.6 27.2-6.6 38.8 0l184.7 106.7c12 6.9 19.4 19.8 19.4 33.7v213.1c0 13.8-7.4 26.7-19.4 33.7L243.4 502.8c-5.9 3.4-12.6 5.2-19.4 5.2zm149.1-210.1c0-39.9-27-50.5-83.7-58-57.4-7.6-63.2-11.5-63.2-24.9 0-11.1 4.9-25.9 47.4-25.9 37.9 0 51.9 8.2 57.7 33.8.5 2.4 2.7 4.2 5.2 4.2h24c1.5 0 2.9-.6 3.9-1.7s1.5-2.6 1.4-4.1c-3.7-44.1-33-64.6-92.2-64.6-52.7 0-84.1 22.2-84.1 59.5 0 40.4 31.3 51.6 81.8 56.6 60.5 5.9 65.2 14.8 65.2 26.7 0 20.6-16.6 29.4-55.5 29.4-48.9 0-59.6-12.3-63.2-36.6-.4-2.6-2.6-4.5-5.3-4.5h-23.9c-3 0-5.3 2.4-5.3 5.3 0 31.1 16.9 68.2 97.8 68.2 58.4-.1 92-23.2 92-63.4z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 400"
|
||||
>
|
||||
<g fill-rule="nonzero" transform="translate(0 50)" fill="none">
|
||||
<path
|
||||
d="M227.92099 83.45116l-13.6889 24.10141-46.8148-82.44693L23.7037 278.17052h97.3037c0 13.31084 10.61252 24.10142 23.70371 24.10142H23.70371c-8.46771 0-16.29145-4.59601-20.5246-12.05272-4.23315-7.4567-4.23272-16.64312.00114-24.0994L146.89383 13.05492c4.23415-7.45738 12.0596-12.05138 20.5284-12.05138 8.46878 0 16.29423 4.594 20.52839 12.05138l39.97037 70.39623z"
|
||||
fill="#00C58E"
|
||||
/>
|
||||
<path
|
||||
d="M331.6642 266.11981l-90.05432-158.56724-13.6889-24.10141-13.68888 24.10141-90.04445 158.56724c-4.23385 7.45629-4.23428 16.64271-.00113 24.09941 4.23314 7.4567 12.05689 12.05272 20.5246 12.05272h166.4c8.46946 0 16.29644-4.591 20.532-12.04837 4.23555-7.45736 4.23606-16.64592.00132-24.10376h.01976zM144.7111 278.17052L227.921 131.65399l83.19012 146.51653h-166.4z"
|
||||
fill="#FFF"
|
||||
/>
|
||||
<path
|
||||
d="M396.04938 290.22123c-4.23344 7.45557-12.05656 12.0507-20.52345 12.0507H311.1111c13.0912 0 23.7037-10.79057 23.7037-24.10141h40.66173L260.09877 74.98553l-18.4889 32.56704L227.921 83.45116l11.65432-20.51634c4.23416-7.45738 12.0596-12.05138 20.5284-12.05138 8.46879 0 16.29423 4.594 20.52839 12.05138l115.41728 203.185c4.23426 7.457 4.23426 16.6444 0 24.1014z"
|
||||
fill="#108775"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 128 128"
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-6 h-14 w-14 text-white'
|
||||
: 'mx-auto w-8 h-8 text-white'}
|
||||
>
|
||||
<path
|
||||
fill="#6181B6"
|
||||
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<linearGradient
|
||||
id="a"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="70.252"
|
||||
y1="1237.476"
|
||||
x2="170.659"
|
||||
y2="1151.089"
|
||||
gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"
|
||||
><stop offset="0" stop-color="#5A9FD4" /><stop
|
||||
offset="1"
|
||||
stop-color="#306998"
|
||||
/></linearGradient
|
||||
><path
|
||||
fill="url(#a)"
|
||||
d="M63.391 1.988c-4.222.02-8.252.379-11.8 1.007-10.45 1.846-12.346 5.71-12.346 12.837v9.411h24.693v3.137h-33.961c-7.176 0-13.46 4.313-15.426 12.521-2.268 9.405-2.368 15.275 0 25.096 1.755 7.311 5.947 12.519 13.124 12.519h8.491v-11.282c0-8.151 7.051-15.34 15.426-15.34h24.665c6.866 0 12.346-5.654 12.346-12.548v-23.513c0-6.693-5.646-11.72-12.346-12.837-4.244-.706-8.645-1.027-12.866-1.008zm-13.354 7.569c2.55 0 4.634 2.117 4.634 4.721 0 2.593-2.083 4.69-4.634 4.69-2.56 0-4.633-2.097-4.633-4.69-.001-2.604 2.073-4.721 4.633-4.721z"
|
||||
/><linearGradient
|
||||
id="b"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="209.474"
|
||||
y1="1098.811"
|
||||
x2="173.62"
|
||||
y2="1149.537"
|
||||
gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"
|
||||
><stop offset="0" stop-color="#FFD43B" /><stop
|
||||
offset="1"
|
||||
stop-color="#FFE873"
|
||||
/></linearGradient
|
||||
><path
|
||||
fill="url(#b)"
|
||||
d="M91.682 28.38v10.966c0 8.5-7.208 15.655-15.426 15.655h-24.665c-6.756 0-12.346 5.783-12.346 12.549v23.515c0 6.691 5.818 10.628 12.346 12.547 7.816 2.297 15.312 2.713 24.665 0 6.216-1.801 12.346-5.423 12.346-12.547v-9.412h-24.664v-3.138h37.012c7.176 0 9.852-5.005 12.348-12.519 2.578-7.735 2.467-15.174 0-25.096-1.774-7.145-5.161-12.521-12.348-12.521h-9.268zm-13.873 59.547c2.561 0 4.634 2.097 4.634 4.692 0 2.602-2.074 4.719-4.634 4.719-2.55 0-4.633-2.117-4.633-4.719 0-2.595 2.083-4.692 4.633-4.692z"
|
||||
/><radialGradient
|
||||
id="c"
|
||||
cx="1825.678"
|
||||
cy="444.45"
|
||||
r="26.743"
|
||||
gradientTransform="matrix(0 -.24 -1.055 0 532.979 557.576)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
><stop offset="0" stop-color="#B8B8B8" stop-opacity=".498" /><stop
|
||||
offset="1"
|
||||
stop-color="#7F7F7F"
|
||||
stop-opacity="0"
|
||||
/></radialGradient
|
||||
><path
|
||||
opacity=".444"
|
||||
fill="url(#c)"
|
||||
enable-background="new"
|
||||
d="M97.309 119.597c0 3.543-14.816 6.416-33.091 6.416-18.276 0-33.092-2.873-33.092-6.416 0-3.544 14.815-6.417 33.092-6.417 18.275 0 33.091 2.872 33.091 6.417z"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-blue-500'
|
||||
: 'mx-auto w-8 h-8 text-blue-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<g fill="#61DAFB"
|
||||
><circle cx="64" cy="64" r="11.4" /><path
|
||||
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-white'
|
||||
: 'mx-auto w-8 h-8 text-white'}
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
><g clip-path="url(#HTML5_Clip0_4)"
|
||||
><path
|
||||
d="M30.216 0L27.6454 28.7967L16.0907 32L4.56783 28.8012L2 0H30.216Z"
|
||||
fill="#E44D26"
|
||||
/><path
|
||||
d="M16.108 29.5515L25.4447 26.963L27.6415 2.35497H16.108V29.5515Z"
|
||||
fill="#F16529"
|
||||
/><path
|
||||
d="M11.1109 9.4197H16.108V5.88731H7.25053L7.33509 6.83499L8.20327 16.5692H16.108V13.0369H11.4338L11.1109 9.4197Z"
|
||||
fill="#EBEBEB"
|
||||
/><path
|
||||
d="M11.907 18.3354H8.36111L8.856 23.8818L16.0917 25.8904L16.108 25.8859V22.2108L16.0925 22.2149L12.1585 21.1527L11.907 18.3354Z"
|
||||
fill="#EBEBEB"
|
||||
/><path
|
||||
d="M16.0958 16.5692H20.4455L20.0354 21.1504L16.0958 22.2138V25.8887L23.3373 23.8817L23.3904 23.285L24.2205 13.9855L24.3067 13.0369H16.0958V16.5692Z"
|
||||
fill="white"
|
||||
/><path
|
||||
d="M16.0958 9.41105V9.41969H24.6281L24.6989 8.62572L24.8599 6.83499L24.9444 5.88731H16.0958V9.41105Z"
|
||||
fill="white"
|
||||
/></g
|
||||
><defs><clipPath id="HTML5_Clip0_4"><rect width="32" height="32" fill="white" /></clipPath></defs
|
||||
></svg
|
||||
>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 98.1 118"
|
||||
style="enable-background:new 0 0 98.1 118;"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<path
|
||||
fill="#FF3E00"
|
||||
d="M91.8,15.6C80.9-0.1,59.2-4.7,43.6,5.2L16.1,22.8C8.6,27.5,3.4,35.2,1.9,43.9c-1.3,7.3-0.2,14.8,3.3,21.3 c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1 c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.1,22.9,91.8,15.6"
|
||||
/>
|
||||
<path
|
||||
fill="#FFFFFF"
|
||||
d="M40.9,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1 c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7L65.5,72 c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7 c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.5-17.5 c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1 c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.4,46.1 c-1.4,0.9-2.3,2.2-2.6,3.8s0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7c1.7-1.1,3.6-1.9,5.6-2.5 c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.5,17.5C44.8,102.5,42.9,103.3,40.9,103.9"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
export let isAbsolute = true;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={isAbsolute
|
||||
? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500'
|
||||
: 'mx-auto w-8 h-8 text-green-500'}
|
||||
viewBox="0 0 128 128"
|
||||
>
|
||||
<path
|
||||
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
|
||||
fill="none"
|
||||
/><path
|
||||
d="m25.997 9.3929 23.002 0.0087l15.036 24.958 14.983-24.956 22.982-0.0057-37.85 65.655z"
|
||||
fill="#35495e"
|
||||
/><path
|
||||
d="m0.91068 9.5686 25.066-0.1711 38.151 65.658 37.852-65.654 25.11 0.0263-62.966 108.06z"
|
||||
fill="#41b883"
|
||||
/>
|
||||
</svg>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user