Compare commits

...

223 Commits
v3.12.13 ... v3

Author SHA1 Message Date
Andras Bacsai
b80a519b80 updates 2023-09-05 11:31:55 +02:00
Andras Bacsai
2fa7ffc931 updates 2023-09-05 11:25:16 +02:00
Andras Bacsai
4abec14a21 updates 2023-09-05 11:24:42 +02:00
Andras Bacsai
18d0623011 force volume prune 2023-08-16 15:27:10 +02:00
Andras Bacsai
aa634c78d1 fix seed js 2023-08-16 15:26:36 +02:00
Andras Bacsai
a2d4373104 cleanup volumes as well 2023-08-16 15:26:33 +02:00
Andras Bacsai
702e16d643 rename rollback to upgrade 2023-08-14 09:23:33 +02:00
Andras Bacsai
3b25c8f96b fix: docker compose env file 2023-08-12 00:10:14 +02:00
Andras Bacsai
1c8c567791 fix: env variables in compose deplyoments 2023-07-27 12:40:58 +02:00
Andras Bacsai
807a3c9d66 fix: n8n double mount
version++
2023-07-26 10:38:36 +02:00
Andras Bacsai
2abd7bd7bb copy to persisten storage 2023-07-25 12:38:36 +02:00
Andras Bacsai
343957ab8b update backups 2023-07-25 12:33:05 +02:00
Andras Bacsai
49261308f7 update webhook 2023-07-25 12:00:31 +02:00
Andras Bacsai
d037409237 updates 2023-07-25 11:40:27 +02:00
Andras Bacsai
338cbf62a1 fix: encrypt decrypt 2023-07-25 10:48:31 +02:00
Andras Bacsai
4c51bffc7b save db backup on seed 2023-07-20 21:41:47 +02:00
Andras Bacsai
fd98ba8812 auto update every hour 2023-07-20 16:37:24 +02:00
Andras Bacsai
930251e9c8 autoupdate fixed 2023-07-20 16:19:54 +02:00
Andras Bacsai
7cd441266a remove console log 2023-07-20 14:52:32 +02:00
Andras Bacsai
990fb8ec15 fix 2023-07-20 14:52:16 +02:00
Andras Bacsai
3fe982b2f4 Merge pull request #1052 from f-kawamura/bugfix-http-git-source
[Bug] Added support for HTTP source URLs in Git source
2023-07-20 13:48:34 +02:00
Andras Bacsai
9dd874e959 Merge pull request #1147 from martijnmichel/v3
Update serviceFields.ts
2023-07-20 13:46:52 +02:00
Andras Bacsai
b91368223b update plausible docs 2023-07-20 13:39:29 +02:00
Andras Bacsai
139670372b updates for templates 2023-07-20 13:29:03 +02:00
Andras Bacsai
1c0769ad75 update tags + only download on service view 2023-07-20 13:12:54 +02:00
Andras Bacsai
e6cbcf98cb fix: cleanup plausible 2023-07-20 13:06:19 +02:00
martijnmichel
64b0481055 Update serviceFields.ts 2023-07-20 08:19:51 +02:00
Andras Bacsai
ce15161926 Merge pull request #1144 from coollabsio/feature/implement-basic-auth-handling
fix: traefik config + ui + api
2023-07-18 15:37:57 +02:00
Andras Bacsai
4003d4d894 Merge pull request #1071 from pascal-klesse/feature/implement-basic-auth-handling
feat: Implement basic auth for applications
2023-07-18 15:37:35 +02:00
Andras Bacsai
6e011025a7 fix: traefik config + ui + api 2023-07-18 15:34:05 +02:00
Andras Bacsai
6c0544adb2 Merge branch 'v2' into feature/implement-basic-auth-handling 2023-07-18 14:48:02 +02:00
Andras Bacsai
8e4f7c9065 remove console.log 2023-07-18 14:44:46 +02:00
Andras Bacsai
e71f890b54 Merge pull request #1084 from Geczy/main
add trycatch
2023-07-18 14:44:32 +02:00
Andras Bacsai
4dc35dea97 Merge pull request #1119 from jenishngl/patch-1
Fixing the help link - source.svelte line 263
2023-07-18 14:40:33 +02:00
Andras Bacsai
b63dfb4bcd feat: backup databases 2023-07-18 14:36:54 +02:00
Andras Bacsai
b2ffd9183b fix: set connection string on publicity change 2023-07-18 13:01:10 +02:00
Andras Bacsai
5cb0bcfd9b fix: increase query time for new services etc 2023-07-18 09:50:05 +02:00
Jenish J
1fbcfcaf74 Merge branch 'v3' into patch-1 2023-07-17 20:15:27 +05:30
Andras Bacsai
3ba44a1e23 backup db file 2023-07-17 15:17:18 +02:00
Andras Bacsai
de4efbb555 update GH actions 2023-07-17 14:36:33 +02:00
Andras Bacsai
6f443680f3 typo 2023-07-17 14:02:51 +02:00
Andras Bacsai
23b22e5ca8 wip: reencrypt everything 2023-07-17 13:50:26 +02:00
Andras Bacsai
1bba747ce5 update 2023-07-15 09:50:29 +02:00
Andras Bacsai
9ebfc6646e wip 2023-07-14 23:15:43 +02:00
Andras Bacsai
055ff6dbbd updates 2023-07-14 22:30:40 +02:00
Andras Bacsai
6430e7b288 fix seed 2023-07-14 22:19:52 +02:00
Andras Bacsai
87b0050161 update seed 2023-07-14 22:11:59 +02:00
Andras Bacsai
369d8b408d version ++ 2023-07-14 21:36:28 +02:00
Andras Bacsai
505abc592c fix: gh actions 2023-07-14 21:33:30 +02:00
Andras Bacsai
c9df812258 testing seeder 2023-07-14 21:30:08 +02:00
Andras Bacsai
0bfcf6b66f fix: gh actions 2023-07-14 21:06:26 +02:00
Andras Bacsai
67853acabd docs link update 2023-07-14 20:58:54 +02:00
Jenish J
79c98657b1 Update source.svelte line 263 - Correcting the help link
Updated to the correct help link, as the old link was not pointing to the correct section in the docs.coollabs.io page
2023-06-28 20:45:03 +05:30
Geczy
d1be7e44af add trycatch 2023-05-25 13:39:57 -04:00
Andras Bacsai
33b853b981 Merge pull request #1081 from coollabsio/next
v3.12.32
2023-05-25 09:14:17 +02:00
Andras Bacsai
e6063fb93a fix: force delete stucked destinations 2023-05-24 21:55:24 +02:00
Andras Bacsai
f30f23af59 fix: restart storage volumes 2023-05-24 20:51:33 +02:00
Andras Bacsai
d4798a3b22 fix: more aggressive cleanup 2023-05-24 20:34:40 +02:00
Pascal Klesse
eefc2a3d0e remove TODO 2023-05-15 09:33:03 +02:00
Pascal Klesse
d14ca724e9 feat: Implement basic auth for applications 2023-05-15 09:27:49 +02:00
f-kawamura
7b05aaffc3 fix: Added support for HTTP source URLs in Git source. Currently only support HTTPS 2023-04-24 16:12:47 +09:00
Andras Bacsai
f3beb5d8db Merge pull request #1045 from coollabsio/next
v3.12.31
2023-04-19 08:53:20 +02:00
Andras Bacsai
e86b916415 fix: application logs duplicate 2023-04-19 08:50:55 +02:00
Andras Bacsai
e14cc6f2f0 remove assignment for issues 2023-04-18 14:52:26 +02:00
Andras Bacsai
8c1eb94401 fix: remove git is not necessary for docker bp 2023-04-18 14:51:31 +02:00
Andras Bacsai
29fa421945 feat: add custom version/tag 2023-04-18 14:46:40 +02:00
Andras Bacsai
7cfe98d988 fix: fail build if no application found. 2023-04-18 14:32:29 +02:00
Andras Bacsai
e2314c350b update lock file 2023-04-18 14:27:54 +02:00
Andras Bacsai
3713b33578 Update templates
fix: non string inputs in templates
2023-04-18 14:22:46 +02:00
Andras Bacsai
e007a773fd Merge pull request #1030 from coollabsio/next
v3.12.30
2023-04-03 10:40:27 +02:00
Andras Bacsai
e2821118eb fix. removing git 2023-04-03 10:21:08 +02:00
Andras Bacsai
4c8e73ac86 fix: harder to remove destinations and sources 2023-04-03 09:55:13 +02:00
Andras Bacsai
cb980fb814 fix: docker compose generator 2023-04-03 08:59:48 +02:00
Andras Bacsai
41c84e3642 Merge pull request #1001 from coollabsio/next
v3.12.29
2023-03-20 13:57:01 +01:00
Andras Bacsai
2bad98424f switch back to aarch-runners 2023-03-20 13:49:41 +01:00
Andras Bacsai
bc6b1e2dea fix: remove .git dir from final image 2023-03-20 13:05:53 +01:00
Andras Bacsai
911c15d1be update versions 2023-03-20 12:44:45 +01:00
Andras Bacsai
f79d570870 fix: gitea 2023-03-20 12:28:23 +01:00
Andras Bacsai
7fffa9fba5 Merge branch 'main' into next 2023-03-20 12:05:21 +01:00
Andras Bacsai
cbd634fb99 Update README.md 2023-03-17 15:31:00 +01:00
Andras Bacsai
7ae7436d4f Update staging-release.yml 2023-03-17 15:27:16 +01:00
Andras Bacsai
641bada100 ignore dockerhub releases 2023-03-16 13:54:58 +01:00
Andras Bacsai
3416d8d88e only arm 2023-03-16 13:42:19 +01:00
Andras Bacsai
0bb503368b concurrency 2023-03-16 13:37:49 +01:00
Andras Bacsai
ac3a77c3c7 no qemu 2023-03-16 13:35:04 +01:00
Andras Bacsai
79b4178d76 vcpu increase 2023-03-16 13:32:48 +01:00
Andras Bacsai
42a61296d7 test buildjet 2023-03-16 13:29:13 +01:00
Andras Bacsai
e8088e2a70 Merge pull request #993 from coollabsio/next
fix: revert from dockerhub if ghcr.io does not exists
2023-03-16 13:10:58 +01:00
Andras Bacsai
c4d39aced2 fix: revert from dockerhub if ghcr.io does not exists 2023-03-16 13:10:34 +01:00
Andras Bacsai
b40a5adeb0 update GH actions 2023-03-16 12:28:40 +01:00
Andras Bacsai
558a900620 Merge pull request #992 from coollabsio/next
Move to ghcr.io
2023-03-16 12:18:57 +01:00
Andras Bacsai
6b5e5a504d updates 2023-03-16 12:09:48 +01:00
Andras Bacsai
e44dca2464 updates 2023-03-16 12:01:57 +01:00
Andras Bacsai
e1f84b277a updates 2023-03-16 11:57:04 +01:00
Andras Bacsai
2518f46b08 remove fluentbit + pocketbase builds 2023-03-16 11:56:19 +01:00
Andras Bacsai
01e18a9496 Merge pull request #991 from coollabsio/ghcr
Move to ghcr from dockerhub
2023-03-16 10:55:22 +01:00
Andras Bacsai
564ca709d3 updates 2023-03-16 10:53:54 +01:00
Andras Bacsai
a54a36ae18 updates 2023-03-16 10:50:26 +01:00
Andras Bacsai
43603b0961 update 2023-03-16 10:26:20 +01:00
Andras Bacsai
96cd99f904 fixes 2023-03-16 10:23:14 +01:00
Andras Bacsai
3438d10e25 test 2023-03-16 10:13:44 +01:00
Andras Bacsai
022ccb42a1 test 2023-03-16 10:04:53 +01:00
Andras Bacsai
e6d72e9f87 test 2023-03-16 09:56:39 +01:00
Andras Bacsai
06e8a6af23 test 2023-03-16 09:38:20 +01:00
Andras Bacsai
ac188d137a test 2023-03-16 09:32:20 +01:00
Andras Bacsai
cae466745a test 2023-03-16 09:10:11 +01:00
Andras Bacsai
d61f16dab0 test 2023-03-16 08:48:37 +01:00
Andras Bacsai
02ba277a86 fix: show ip address as host in public dbs 2023-03-07 13:25:08 +01:00
Andras Bacsai
470ff49a02 Merge pull request #981 from coollabsio/next
v3.12.26
2023-03-07 12:19:36 +01:00
Andras Bacsai
04d741581d Merge pull request #980 from hyenabyte/main
Fixing multiple remotes breaking the server overview
2023-03-07 12:12:59 +01:00
Andras Bacsai
038f210148 Merge branch 'main' into next 2023-03-07 11:47:29 +01:00
Andras Bacsai
2adad3a7bd fix: handle log format volumes 2023-03-07 11:46:23 +01:00
Andras Bacsai
05fb26a49b remove console logs 2023-03-07 11:15:43 +01:00
Andras Bacsai
1c237affb4 feat: add host path to any container 2023-03-07 11:15:05 +01:00
Andras Bacsai
3e81d7e9cb fix: replace . & .. & $PWD with ~ 2023-03-07 10:44:53 +01:00
David Koch Gregersen
edb66620c1 Adding a check when reading ssh config file
Also adds comments to the createRemoteEngineConfiguration function
2023-03-07 10:43:34 +01:00
Andras Bacsai
04f7e8e777 fix: host volumes 2023-03-07 10:31:10 +01:00
David Koch Gregersen
eee201013c Fixing multiple remotes breaking the server overview 2023-03-06 22:31:01 +01:00
Andras Bacsai
1190cb4ea1 Update deployApplication.ts 2023-03-04 18:49:34 +01:00
Andras Bacsai
507100ea0b Update package.json 2023-03-04 18:48:38 +01:00
Andras Bacsai
9b13912b6d Update common.ts 2023-03-04 18:48:28 +01:00
Andras Bacsai
ee65deebfd fix: nestjs buildpack 2023-03-04 18:05:01 +01:00
Andras Bacsai
ba9fa442d1 Merge pull request #973 from coollabsio/next
v3.12.23
2023-03-04 15:11:42 +01:00
Andras Bacsai
87da27f9bf fix: publishDirectory 2023-03-04 15:06:35 +01:00
Andras Bacsai
b5bc5fe2c6 Merge pull request #965 from coollabsio/next
v3.12.22
2023-03-03 09:13:56 +01:00
Andras Bacsai
d2329360d0 Merge pull request #950 from hemangjoshi37a/main
added `star-history`
2023-03-02 17:33:22 +01:00
Andras Bacsai
7ece0ae10a Merge pull request #955 from eltociear/patch-1
fix typo in _GitlabRepositories.svelte
2023-03-02 17:32:26 +01:00
Andras Bacsai
f931b47eb8 Merge pull request #957 from addianto/fix/pack
Fix PACK_VERSION build argument in Dockerfile
2023-03-02 17:31:55 +01:00
Andras Bacsai
7f7eb12ded fix: empty port in docker compose 2023-03-02 17:22:49 +01:00
Andras Bacsai
c0940f7a19 fix: cannot delete resource when you are not on root team 2023-03-02 17:12:29 +01:00
Andras Bacsai
9dfde11e35 possible fix: vaultwarden 2023-03-02 16:56:44 +01:00
Andras Bacsai
6f15cc2dbc fix: base directory not found 2023-03-02 16:52:55 +01:00
Daya Adianto
120308638f fix: set PACK_VERSION to 0.27.0
This commit removes the `v` prefix in the version identifier assigned to
PACK_VERSION build argument. The `pack` script actually available on
Coolify's CDN, but named without `v` prefix in the script's version identifier.

Related issue: #689, which reported that the `pack` script in the
container image is a HTML 404 file instead of the actual `pack`
executable.
2023-02-25 17:27:41 +07:00
Ikko Eltociear Ashimine
1d04ef99bb fix typo in _GitlabRepositories.svelte
occured -> occurred
2023-02-24 11:24:55 +09:00
Hemang Joshi
9b00d177ef added star-history
added `star-history`
2023-02-22 16:21:32 +05:30
Andras Bacsai
884524c448 Merge pull request #945 from coollabsio/next
v3.12.21
2023-02-21 13:55:29 +01:00
Andras Bacsai
3ae1e7e87d remove debug 2023-02-21 13:47:28 +01:00
Andras Bacsai
81f885311d debug 2023-02-21 13:24:23 +01:00
Andras Bacsai
d9362f09d8 debug 2023-02-21 13:23:34 +01:00
Andras Bacsai
906d181d1b debug 2023-02-21 13:15:17 +01:00
Andras Bacsai
44b8812a7b debug 2023-02-21 13:08:14 +01:00
Andras Bacsai
3308c45e88 Merge pull request #943 from coollabsio/next
v3.12.21
2023-02-21 13:02:39 +01:00
Andras Bacsai
e530ecf9f9 fix 2023-02-21 12:59:21 +01:00
Andras Bacsai
51b5edb04f hmm fix 2023-02-21 12:48:06 +01:00
Andras Bacsai
f0d89f850e fix 2023-02-21 12:45:22 +01:00
Andras Bacsai
b777e08542 fix: arm servics 2023-02-21 12:35:20 +01:00
Andras Bacsai
2e485df530 Merge pull request #935 from coollabsio/next
v3.12.20
2023-02-20 12:03:57 +01:00
Andras Bacsai
3c37d22a6e typo 2023-02-20 11:55:08 +01:00
Andras Bacsai
08ab7a504a fix: applications cannot be deleted 2023-02-20 11:54:43 +01:00
Andras Bacsai
06563ef921 add latest tag with prod release 2023-02-20 10:23:31 +01:00
Andras Bacsai
34f6210bc0 Merge pull request #931 from coollabsio/next
v3.12.19
2023-02-20 10:04:37 +01:00
Andras Bacsai
0bbde0c605 add ccareer logo 2023-02-20 09:40:54 +01:00
Andras Bacsai
a8f24fd1b7 update icon 2023-02-20 09:11:05 +01:00
Andras Bacsai
c3e0237696 Merge pull request #909 from scshiv29-dev/main
changed copypassword fields in databases
2023-02-20 09:06:35 +01:00
Andras Bacsai
bb6925920f fix: escape new line chars in wp custom configs 2023-02-17 15:00:29 +01:00
Andras Bacsai
63ec2a33ae fix: network in compose files 2023-02-17 14:45:13 +01:00
Andras Bacsai
c89a959fe8 remove debug 2023-02-17 14:35:42 +01:00
Andras Bacsai
150b50e0ba isarm simplification 2023-02-17 14:20:17 +01:00
Andras Bacsai
4ef824f665 typo fix 2023-02-17 14:16:12 +01:00
Andras Bacsai
5a56cca0aa debug a thing 2023-02-17 14:09:15 +01:00
Andras Bacsai
b9189d7647 readd compose icon 2023-02-17 13:34:21 +01:00
Andras Bacsai
20226c914b update templates 2023-02-17 13:29:14 +01:00
Andras Bacsai
434e7f8a09 fix versions 2023-02-17 13:11:50 +01:00
Andras Bacsai
a29d733a02 Merge pull request #924 from jloewe/main
fix: gitlab personal repo listing
2023-02-17 13:06:46 +01:00
Andras Bacsai
9abe4b967b package updates + remove local icons 2023-02-17 13:05:18 +01:00
Andras Bacsai
3b6a4ece0f reset production release 2023-02-17 13:00:31 +01:00
Andras Bacsai
28d2471b4d dump pocketbase version 2023-02-17 13:00:31 +01:00
Andras Bacsai
d122af9fed Merge pull request #891 from TetrisIQ/moveIconsInComunityRepo
Move icons in comunity repo
2023-02-17 13:00:02 +01:00
Andras Bacsai
77271f3856 Merge pull request #919 from rawalplawit/docfix
fix: typos in docs
2023-02-17 12:47:17 +01:00
Andras Bacsai
ededfb68a6 Merge pull request #926 from simonorzel26/main
Spelling correction README.md
2023-02-17 12:45:10 +01:00
Andras Bacsai
4a3affdd24 Merge pull request #930 from usr3/patch-1
fix: link 404 in contribution.md
2023-02-17 12:44:46 +01:00
Andras Bacsai
8f8ea120d3 test prod release 2023-02-17 12:15:17 +01:00
Andras Bacsai
0fa88009f8 remove rc gh action 2023-02-17 11:56:10 +01:00
Andras Bacsai
4375a807df remove trpc 2023-02-17 11:55:21 +01:00
usr3
b2d97c5908 Update link 2023-02-17 13:36:14 +05:30
usr3
ec89dd606d Fix link 404
Fix 404 for `Setup Docker Compose Plugin`
2023-02-17 13:09:16 +05:30
Simon O
198508a7c3 Update README.md 2023-02-15 17:58:14 +01:00
Jan Loewe
4845e986bb fix gitlab personal repo listing 2023-02-14 17:33:50 +00:00
Shivam Deepak Chaudhary
1da8a307fc fixded copyvolumefield css 2023-02-11 09:32:04 +00:00
Shivam Deepak Chaudhary
b4886e604e added copyvolumefield 2023-02-11 09:24:08 +00:00
rawalplawit
e84544136e fix: typos in docs 2023-02-10 14:54:16 +05:45
Shivam Deepak Chaudhary
ce70252a69 changed copypassword fields in databases 2023-02-09 07:51:33 +00:00
Andras Bacsai
5c56962ea1 Merge pull request #888 from Rados51/883
Fixes coollabsio/coolify#883
2023-02-08 15:00:57 +01:00
Andras Bacsai
d2ed53b946 Merge pull request #889 from Rados51/884
Fixes coollabsio/coolify#884
2023-02-08 14:59:24 +01:00
Andras Bacsai
a4da80b498 Merge pull request #894 from m0ddixx/main
feat: Add environment variables for proxy ports
2023-02-08 14:57:22 +01:00
Andras Bacsai
9bd01492b1 Merge pull request #896 from inluxc/PocketBase-v0.12.0
Update PocketBase to v0.12.2
2023-02-08 14:54:22 +01:00
Fulvio Carvalhido
da032941b4 Update version 0.12.2 2023-02-07 12:31:44 +00:00
Fulvio Carvalhido
c138fcc2e2 Update PocketBase to v0.12.0
Release:
https://github.com/pocketbase/pocketbase/releases/tag/v0.12.0
2023-01-30 13:18:03 +00:00
Nico Kranz
cbb69b0350 add env support for traefik ports 2023-01-30 11:04:22 +00:00
Alex
a8aed3354d fix: url 2023-01-28 21:02:16 +00:00
Alex
e8790a4d4c feat: remove svg support 2023-01-28 21:00:57 +00:00
Radoš
df6ef3aaa0 Fixes coollabsio/coolify#884 2023-01-27 18:52:23 +01:00
Radoš
2820d99f7b Fixes coollabsio/coolify#883 2023-01-27 18:35:34 +01:00
Alex
077aa4445a feat: github raw icon url 2023-01-24 20:04:43 +00:00
Andras Bacsai
23bfc119d9 Fix GH 2023-01-24 15:43:43 +01:00
Andras Bacsai
ab712ac637 version++ 2023-01-24 15:32:32 +01:00
Andras Bacsai
b056826e94 Fixing prod release 2023-01-24 15:31:26 +01:00
Andras Bacsai
6311627899 Merge pull request #882 from coollabsio/next
v3.12.18
2023-01-24 15:09:51 +01:00
Andras Bacsai
37cea5fb61 update mattermost + traefik 2023-01-24 14:48:03 +01:00
Andras Bacsai
655a8cd60d feat: able to use $$ in traefik config gen
fix: repman icon
2023-01-24 13:52:39 +01:00
Andras Bacsai
4c8babc96a version++ 2023-01-23 10:45:19 +01:00
Andras Bacsai
612bacebed fix: cleanupStuckedContainers 2023-01-23 10:37:28 +01:00
Andras Bacsai
ade7c8566d fix: cleanupStuckedContainers 2023-01-23 10:37:14 +01:00
Andras Bacsai
19553ce5c8 Merge pull request #874 from coollabsio/next
v3.12.17
2023-01-20 21:14:06 +01:00
Andras Bacsai
18ed2527e8 fix 2023-01-20 20:51:37 +01:00
Andras Bacsai
b0652bc884 Merge pull request #872 from coollabsio/next
Next
2023-01-20 14:07:46 +01:00
Andras Bacsai
15c9ad23fe fix: stucked containers 2023-01-20 14:06:55 +01:00
Andras Bacsai
578bb12562 test new release gh action 2023-01-20 14:05:07 +01:00
Andras Bacsai
f82cfda07f version++ 2023-01-20 13:55:05 +01:00
Andras Bacsai
9e52b2788d Pocketbase GH release updated 2023-01-20 13:49:39 +01:00
Andras Bacsai
2e56a113d9 Test GH release thing 2023-01-20 13:48:57 +01:00
Andras Bacsai
4722d777e6 Merge pull request #871 from coollabsio/next
v3.12.15
2023-01-20 13:22:11 +01:00
Andras Bacsai
2141d54ae0 fix 2023-01-20 13:15:52 +01:00
Andras Bacsai
e346225136 fix 2023-01-20 13:10:40 +01:00
Andras Bacsai
012d4dae56 testing 2023-01-20 11:15:38 +01:00
Andras Bacsai
b4d9fe70af fix 2023-01-20 10:43:21 +01:00
Andras Bacsai
85e83b5441 Test new gh actions 2023-01-20 10:42:13 +01:00
Andras Bacsai
6b2a453b8f fix: deletion + cleanupStuckedContainers 2023-01-20 10:10:36 +01:00
Andras Bacsai
27021538d8 fix: cleanup stucked containers 2023-01-20 09:40:29 +01:00
Andras Bacsai
8b57a2b055 fix: cleanup function 2023-01-20 09:26:48 +01:00
Andras Bacsai
75dd894685 Merge pull request #867 from coollabsio/next
v3.12.14
2023-01-19 14:36:05 +01:00
Andras Bacsai
9101ef8774 version++ 2023-01-19 14:33:33 +01:00
Andras Bacsai
5932540630 fix: www redirect 2023-01-19 14:33:20 +01:00
499 changed files with 8969 additions and 44495 deletions

View File

@@ -2,9 +2,6 @@ name: 🐞 Bug report
description: Create a bug report to help us improve coolify description: Create a bug report to help us improve coolify
title: "[Bug]: " title: "[Bug]: "
labels: [Bug] labels: [Bug]
assignees:
- andrasbacsai
- vasani-arpit
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -2,9 +2,6 @@ name: 🛠️ Feature request
description: Suggest an idea to improve coolify description: Suggest an idea to improve coolify
title: '[Feature]: ' title: '[Feature]: '
labels: [Enhancement] labels: [Enhancement]
assignees:
- andrasbacsai
- vasani-arpit
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -1,93 +0,0 @@
name: fluent-bit-release
on:
push:
paths:
- "others/fluentbit"
- ".github/workflows/fluent-bit-release.yml"
branches:
- next
jobs:
arm64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/fluentbit/
platforms: linux/arm64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-arm64
amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: others/fluentbit/
platforms: linux/amd64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-amd64
aarch64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/fluentbit/
platforms: linux/aarch64
push: true
tags: coollabsio/coolify-fluent-bit:1.0.0-aarch64
merge-manifest:
runs-on: ubuntu-latest
needs: [amd64, arm64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker manifest create coollabsio/coolify-fluent-bit:1.0.0 --amend coollabsio/coolify-fluent-bit:1.0.0-amd64 --amend coollabsio/coolify-fluent-bit:1.0.0-arm64 --amend coollabsio/coolify-fluent-bit:1.0.0-aarch64
docker manifest push coollabsio/coolify-fluent-bit:1.0.0

View File

@@ -1,91 +0,0 @@
name: pocketbase-release
on:
push:
paths:
- "others/pocketbase/*"
- ".github/workflows/pocketbase-release.yml"
jobs:
arm64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/pocketbase/
platforms: linux/arm64
push: true
tags: coollabsio/pocketbase:0.11.0-arm64
amd64:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: others/pocketbase/
platforms: linux/amd64
push: true
tags: coollabsio/pocketbase:0.11.0-amd64
aarch64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: others/pocketbase/
platforms: linux/aarch64
push: true
tags: coollabsio/pocketbase:0.11.0-aarch64
merge-manifest:
runs-on: ubuntu-latest
needs: [amd64, arm64, aarch64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker manifest create coollabsio/pocketbase:0.11.0 --amend coollabsio/pocketbase:0.11.0-amd64 --amend coollabsio/pocketbase:0.11.0-arm64 --amend coollabsio/pocketbase:0.11.0-aarch64
docker manifest push coollabsio/pocketbase:0.11.0

View File

@@ -1,91 +1,81 @@
name: production-release name: Production Release to ghcr.io
on: on:
release: release:
types: [released] types: [released]
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs: jobs:
arm64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get current package version
uses: martinbeentjes/npm-get-version-action@v1.2.3
id: package-version
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm64
push: true
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64
cache-from: type=registry,ref=coollabsio/coolify:buildcache-arm64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-arm64,mode=max
amd64: amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: "v3"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} registry: ${{ env.REGISTRY }}
password: ${{ secrets.DOCKERHUB_TOKEN }} username: ${{ github.actor }}
- name: Get current package version password: ${{ secrets.GITHUB_TOKEN }}
uses: martinbeentjes/npm-get-version-action@v1.2.3 - name: Extract metadata
id: package-version id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=coollabsio/coolify:buildcache-amd64 labels: ${{ steps.meta.outputs.labels }}
cache-to: type=registry,ref=coollabsio/coolify:buildcache-amd64,mode=max
aarch64: aarch64:
runs-on: [self-hosted, arm64] runs-on: [self-hosted, arm64]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: "v3"
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- name: Login to DockerHub - name: Login to ghcr.io
uses: docker/login-action@v1 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} registry: ${{ env.REGISTRY }}
password: ${{ secrets.DOCKERHUB_TOKEN }} username: ${{ github.actor }}
- name: Get current package version password: ${{ secrets.GITHUB_TOKEN }}
uses: martinbeentjes/npm-get-version-action@v1.2.3 - name: Extract metadata
id: package-version id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}-aarch64
- name: Build and push - name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v3
with: with:
context: . context: .
platforms: linux/aarch64 platforms: linux/aarch64
push: true push: true
tags: coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 tags: ${{ steps.meta.outputs.tags }}-aarch64
cache-from: type=registry,ref=coollabsio/coolify:buildcache-aarch64 labels: ${{ steps.meta.outputs.labels }}
cache-to: type=registry,ref=coollabsio/coolify:buildcache-aarch64,mode=max
merge-manifest: merge-manifest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [amd64, arm64, aarch64] needs: [amd64, aarch64]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -93,20 +83,22 @@ jobs:
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} registry: ${{ env.REGISTRY }}
password: ${{ secrets.DOCKERHUB_TOKEN }} username: ${{ github.actor }}
- name: Get current package version password: ${{ secrets.GITHUB_TOKEN }}
uses: martinbeentjes/npm-get-version-action@v1.2.3 - name: Extract metadata
id: package-version id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
- name: Create & publish manifest - name: Create & publish manifest
run: | run: |
docker manifest create coollabsio/coolify:${{steps.package-version.outputs.current-version}} --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64 docker buildx imagetools create --append ${{ fromJSON(steps.meta.outputs.json).tags[0] }}-aarch64 --tag ${{ fromJSON(steps.meta.outputs.json).tags[0] }}
docker manifest create coollabsio/coolify:latest --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-amd64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-arm64 --amend coollabsio/coolify:${{steps.package-version.outputs.current-version}}-aarch64
docker manifest push coollabsio/coolify:${{steps.package-version.outputs.current-version}}
docker manifest push coollabsio/coolify:latest
- uses: sarisia/actions-status-discord@v1 - uses: sarisia/actions-status-discord@v1
if: always() if: always()
with: with:

View File

@@ -1,90 +0,0 @@
name: release-candidate
on:
release:
types: [prereleased]
jobs:
arm64-making-something-cool:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "next"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get current package version
uses: martinbeentjes/npm-get-version-action@v1.2.3
id: package-version
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm64
push: true
tags: coollabsio/coolify:${{github.event.release.name}}-arm64
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-arm64,mode=max
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
amd64-making-something-cool:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "next"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get current package version
uses: martinbeentjes/npm-get-version-action@v1.2.3
id: package-version
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64
push: true
tags: coollabsio/coolify:${{github.event.release.name}}-amd64
cache-from: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-rc-amd64,mode=max
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
merge-manifest-to-be-cool:
runs-on: ubuntu-latest
needs: [arm64-making-something-cool, amd64-making-something-cool]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create & publish manifest
run: |
docker manifest create coollabsio/coolify:${{github.event.release.name}} --amend coollabsio/coolify:${{github.event.release.name}}-amd64 --amend coollabsio/coolify:${{github.event.release.name}}-arm64
docker manifest push coollabsio/coolify:${{github.event.release.name}}

View File

@@ -1,76 +1,76 @@
name: staging-release name: Staging Release to ghcr.io
concurrency:
group: staging_environment
cancel-in-progress: true
on: on:
push: push:
paths:
- "**"
- "!others/fluentbit"
- "!others/pocketbase"
- "!.github/workflows/fluent-bit-release.yml"
- "!.github/workflows/pocketbase-release.yml"
branches: branches:
- next - "v3"
env:
REGISTRY: ghcr.io
IMAGE_NAME: "coollabsio/coolify"
jobs: jobs:
arm64:
runs-on: [self-hosted, arm64]
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "next"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get current package version
uses: martinbeentjes/npm-get-version-action@v1.2.3
id: package-version
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm64
push: true
tags: coollabsio/coolify:next-arm64
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-arm64
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-arm64,mode=max
amd64: amd64:
runs-on: ubuntu-latest runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
ref: "next" ref: "v3"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} registry: ${{ env.REGISTRY }}
password: ${{ secrets.DOCKERHUB_TOKEN }} username: ${{ github.actor }}
- name: Get current package version password: ${{ secrets.GITHUB_TOKEN }}
uses: martinbeentjes/npm-get-version-action@v1.2.3 - name: Extract metadata (tags, labels)
id: package-version id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
platforms: linux/amd64 platforms: linux/amd64
push: true push: true
tags: coollabsio/coolify:next-amd64 tags: ${{ steps.meta.outputs.tags }}
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next-amd64 labels: ${{ steps.meta.outputs.labels }}
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next-amd64,mode=max aarch64:
runs-on:
group: aarch-runners
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "v3"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to ghcr.io
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/aarch64
push: true
tags: ${{ steps.meta.outputs.tags }}-aarch64
labels: ${{ steps.meta.outputs.labels }}
merge-manifest: merge-manifest:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [arm64, amd64] needs: [amd64, aarch64]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -78,15 +78,20 @@ jobs:
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} registry: ${{ env.REGISTRY }}
password: ${{ secrets.DOCKERHUB_TOKEN }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Create & publish manifest - name: Create & publish manifest
run: | run: |
docker manifest create coollabsio/coolify:next --amend coollabsio/coolify:next-amd64 --amend coollabsio/coolify:next-arm64 docker buildx imagetools create --append ${{ steps.meta.outputs.tags }}-aarch64 --tag ${{ steps.meta.outputs.tags }}
docker manifest push coollabsio/coolify:next
- uses: sarisia/actions-status-discord@v1 - uses: sarisia/actions-status-discord@v1
if: always() if: always()
with: with:

11
.vscode/settings.json vendored
View File

@@ -18,5 +18,14 @@
"ts", "ts",
"json" "json"
], ],
"i18n-ally.extract.autoDetect": true "i18n-ally.extract.autoDetect": true,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true
},
"hide-files.files": []
} }

View File

@@ -10,7 +10,7 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
## 1) Setup your development environment ## 1) Setup your development environment
- 🌟 [Container based](docs/dev_setup/Container.md) ← *Recomended* - 🌟 [Container based](docs/dev_setup/Container.md) ← *Recommended*
- 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP - 📦 [DockerContainer](docs/dev_setup/DockerContiner.md) *WIP
- 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md) - 🐙 [Github Codespaces](docs/dev_setup/GithubCodespaces.md)
- ☁️ [GitPod](docs/dev_setup/GitPod.md) - ☁️ [GitPod](docs/dev_setup/GitPod.md)
@@ -20,7 +20,7 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
- [Install Pnpm](https://pnpm.io/installation) - [Install Pnpm](https://pnpm.io/installation)
- [Install Docker Engine](https://docs.docker.com/engine/install/) - [Install Docker Engine](https://docs.docker.com/engine/install/)
- [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/compose-plugin/) - [Setup Docker Compose Plugin](https://docs.docker.com/compose/install/)
- [Setup GIT LFS Support](https://git-lfs.github.com/) - [Setup GIT LFS Support](https://git-lfs.github.com/)
## 3) Setup Coolify ## 3) Setup Coolify
@@ -28,12 +28,12 @@ You'll need a set of skills to [get started](docs/contribution/GettingStarted.md
- Copy `apps/api/.env.example` to `apps/api/.env` - Copy `apps/api/.env.example` to `apps/api/.env`
- Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool. - Edit `apps/api/.env`, set the `COOLIFY_APP_ID` environment variable to something cool.
- Run `pnpm install` to install dependencies. - Run `pnpm install` to install dependencies.
- Run `pnpm db:push` to o create a local SQlite database. This will apply all migrations at `db/dev.db`. - Run `pnpm db:push` to create a local SQlite database. This will apply all migrations at `db/dev.db`.
- Run `pnpm db:seed` seed the database. - Run `pnpm db:seed` seed the database.
- Run `pnpm dev` start coding. - Run `pnpm dev` start coding.
```sh ```sh
# Or... Copy and paste commands bellow: # Or... Copy and paste commands below:
cp apps/api/.env.example apps/api/.env cp apps/api/.env.example apps/api/.env
pnpm install pnpm install
pnpm db:push pnpm db:push
@@ -45,4 +45,4 @@ pnpm dev
You should be able to access `http://localhost:3000`. You should be able to access `http://localhost:3000`.
1. Click `Register` and setup your first user. 1. Click `Register` and setup your first user.

View File

@@ -22,9 +22,9 @@ ARG DOCKER_VERSION=20.10.18
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug. # Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
ARG DOCKER_COMPOSE_VERSION=2.6.1 ARG DOCKER_COMPOSE_VERSION=2.6.1
# https://github.com/buildpacks/pack/releases # https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=v0.27.0 ARG PACK_VERSION=0.27.0
RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 RUN apt update && apt -y install --no-install-recommends ca-certificates git git-lfs openssh-client curl jq cmake sqlite3 openssl psmisc python3 vim
RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN apt-get clean autoclean && apt-get autoremove --yes && rm -rf /var/lib/{apt,dpkg,cache,log}/
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION} RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
RUN npm install -g npm@${PNPM_VERSION} RUN npm install -g npm@${PNPM_VERSION}
@@ -38,7 +38,7 @@ RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
COPY --from=build /app/apps/api/build/ . COPY --from=build /app/apps/api/build/ .
COPY --from=build /app/others/fluentbit/ ./fluentbit # COPY --from=build /app/others/fluentbit/ ./fluentbit
COPY --from=build /app/apps/ui/build/ ./public COPY --from=build /app/apps/ui/build/ ./public
COPY --from=build /app/apps/api/prisma/ ./prisma COPY --from=build /app/apps/api/prisma/ ./prisma
COPY --from=build /app/apps/api/package.json . COPY --from=build /app/apps/api/package.json .
@@ -50,4 +50,4 @@ RUN pnpm install -p
EXPOSE 3000 EXPOSE 3000
ENV CHECKPOINT_DISABLE=1 ENV CHECKPOINT_DISABLE=1
CMD pnpm start CMD pnpm start

View File

@@ -9,7 +9,7 @@ ARG DOCKER_VERSION=20.10.18
# Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug. # Reverted to 2.6.1 because of this https://github.com/docker/compose/issues/9704. 2.9.0 still has a bug.
ARG DOCKER_COMPOSE_VERSION=2.6.1 ARG DOCKER_COMPOSE_VERSION=2.6.1
# https://github.com/buildpacks/pack/releases # https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=v0.27.0 ARG PACK_VERSION=0.27.0
WORKDIR /app WORKDIR /app
RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION} RUN npm --no-update-notifier --no-fund --global install pnpm@${PNPM_VERSION}
@@ -28,4 +28,4 @@ RUN curl -SL https://cdn.coollabs.io/bin/$TARGETPLATFORM/pack-$PACK_VERSION -o /
RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack RUN chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack
EXPOSE 3000 EXPOSE 3000
ENV CHECKPOINT_DISABLE=1 ENV CHECKPOINT_DISABLE=1

View File

@@ -16,7 +16,7 @@ If you have a new service / build pack you would like to add, raise an idea [her
## How to install ## How to install
For more details goto the [docs](https://docs.coollabs.io/coolify/installation). For more details goto the [docs](https://docs.coollabs.io/coolify-v3/installation).
Installation is automated with the following command: Installation is automated with the following command:
@@ -79,9 +79,9 @@ Deploy your resource to:
### Services ### Services
- [Appwrite](https://appwrite.io) - [Appwrite](https://appwrite.io)
- [WordPress](https://docs.coollabs.io/coolify/services/wordpress) - [WordPress](https://docs.coollabs.io/coolify-v3/services/wordpress)
- [Ghost](https://ghost.org) - [Ghost](https://ghost.org)
- [Plausible Analytics](https://docs.coollabs.io/coolify/services/plausible-analytics) - [Plausible Analytics](https://docs.coollabs.io/coolify-v3/services/plausible-analytics)
- [NocoDB](https://nocodb.com) - [NocoDB](https://nocodb.com)
- [VSCode Server](https://github.com/cdr/code-server) - [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io) - [MinIO](https://min.io)
@@ -100,7 +100,7 @@ Deploy your resource to:
- Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai) - Mastodon: [@andrasbacsai@fosstodon.org](https://fosstodon.org/@andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai) - Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Twitter: [@andrasbacsai](https://twitter.com/heyandras)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io) - Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://coollabs.io/discord) - Discord: [Invitation](https://coollabs.io/discord)
@@ -119,7 +119,7 @@ Learn how to contribute to Coolify as as ...
<!-- <!--
&rarr; 🧑🏽‍🎨 Designer &rarr; 🧑🏽‍🎨 Designer
&rarr; 🙋‍♀️ Community Managemer &rarr; 🙋‍♀️ Community Manager
&rarr; 🧙🏻‍♂️ Text Content Creator &rarr; 🧙🏻‍♂️ Text Content Creator
&rarr; 👨🏼‍🎤 Video Content Creator &rarr; 👨🏼‍🎤 Video Content Creator
--> -->
@@ -130,12 +130,12 @@ Learn how to contribute to Coolify as as ...
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)] Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
### Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
### Organizations ### Organizations
Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
![CCCareers](./others/logo/ccc-logo.webp)
Support this project with your organization. Your logo will show up here with a link to your website. Support this project with your organization. Your logo will show up here with a link to your website.
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
@@ -148,3 +148,11 @@ Support this project with your organization. Your logo will show up here with a
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
### Individuals
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=coollabsio/coolify&type=Date)](https://star-history.com/#coollabsio/coolify&Date)

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,13 +21,11 @@
"@fastify/cors": "8.2.0", "@fastify/cors": "8.2.0",
"@fastify/env": "4.2.0", "@fastify/env": "4.2.0",
"@fastify/jwt": "6.5.0", "@fastify/jwt": "6.5.0",
"@fastify/multipart": "7.3.0", "@fastify/multipart": "7.4.1",
"@fastify/static": "6.6.0", "@fastify/static": "6.6.0",
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@ladjs/graceful": "3.2.1", "@ladjs/graceful": "3.2.1",
"@prisma/client": "4.8.1", "@prisma/client": "4.8.1",
"@sentry/node": "7.30.0",
"@sentry/tracing": "7.30.0",
"axe": "11.2.1", "axe": "11.2.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bree": "9.1.3", "bree": "9.1.3",

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT;

View File

@@ -1,3 +1,7 @@
-- AlterTable
ALTER TABLE "Application" ADD COLUMN "basicAuthPw" TEXT;
ALTER TABLE "Application" ADD COLUMN "basicAuthUser" TEXT;
-- RedefineTables -- RedefineTables
PRAGMA foreign_keys=OFF; PRAGMA foreign_keys=OFF;
CREATE TABLE "new_ApplicationSettings" ( CREATE TABLE "new_ApplicationSettings" (
@@ -12,11 +16,12 @@ CREATE TABLE "new_ApplicationSettings" (
"isDBBranching" BOOLEAN NOT NULL DEFAULT false, "isDBBranching" BOOLEAN NOT NULL DEFAULT false,
"isCustomSSL" BOOLEAN NOT NULL DEFAULT false, "isCustomSSL" BOOLEAN NOT NULL DEFAULT false,
"isHttp2" BOOLEAN NOT NULL DEFAULT false, "isHttp2" BOOLEAN NOT NULL DEFAULT false,
"basicAuth" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL, "updatedAt" DATETIME NOT NULL,
CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
); );
INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings"; INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isHttp2", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings";
DROP TABLE "ApplicationSettings"; DROP TABLE "ApplicationSettings";
ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings";
CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId");

View File

@@ -135,6 +135,8 @@ model Application {
dockerRegistryId String? dockerRegistryId String?
dockerRegistryImageName String? dockerRegistryImageName String?
simpleDockerfile String? simpleDockerfile String?
basicAuthUser String?
basicAuthPw String?
persistentStorage ApplicationPersistentStorage[] persistentStorage ApplicationPersistentStorage[]
secrets Secret[] secrets Secret[]
@@ -187,6 +189,7 @@ model ApplicationSettings {
isDBBranching Boolean @default(false) isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false) isCustomSSL Boolean @default(false)
isHttp2 Boolean @default(false) isHttp2 Boolean @default(false)
basicAuth Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])
@@ -195,6 +198,7 @@ model ApplicationSettings {
model ApplicationPersistentStorage { model ApplicationPersistentStorage {
id String @id @default(cuid()) id String @id @default(cuid())
applicationId String applicationId String
hostPath String?
path String path String
oldPath Boolean @default(false) oldPath Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())

View File

@@ -12,7 +12,7 @@ async function main() {
await prisma.setting.create({ await prisma.setting.create({
data: { data: {
id: '0', id: '0',
arch: process.arch, arch: process.arch
} }
}); });
} else { } else {
@@ -81,16 +81,295 @@ async function main() {
}); });
} }
// Set new preview secrets // Set new preview secrets
const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } }) const secrets = await prisma.secret.findMany({ where: { isPRMRSecret: false } });
if (secrets.length > 0) { if (secrets.length > 0) {
for (const secret of secrets) { for (const secret of secrets) {
const previewSecrets = await prisma.secret.findMany({ where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true } }) const previewSecrets = await prisma.secret.findMany({
where: { applicationId: secret.applicationId, name: secret.name, isPRMRSecret: true }
});
if (previewSecrets.length === 0) { if (previewSecrets.length === 0) {
await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } }) await prisma.secret.create({ data: { ...secret, id: undefined, isPRMRSecret: true } });
} }
} }
} }
} }
async function reEncryptSecrets() {
const { execaCommand } = await import('execa');
const image = await execaCommand("docker inspect coolify --format '{{ .Config.Image }}'", {
shell: true
});
const version = image.stdout.split(':')[1] ?? null;
const date = new Date().getTime();
let backupfile = `/app/db/prod.db_${date}`;
if (version) {
backupfile = `/app/db/prod.db_${version}_${date}`;
}
await execaCommand('env | grep "^COOLIFY" | sort > .env', {
shell: true
});
const secretOld = process.env['COOLIFY_SECRET_KEY'];
let secretNew = process.env['COOLIFY_SECRET_KEY_BETTER'];
if (!secretNew) {
console.log('No COOLIFY_SECRET_KEY_BETTER found... Generating new one...');
const { stdout: newKey } = await execaCommand(
'openssl rand -base64 1024 | sha256sum | base64 | head -c 32',
{ shell: true }
);
secretNew = newKey;
}
if (secretOld !== secretNew) {
console.log(`Backup database to ${backupfile}.`);
await execaCommand(`cp /app/db/prod.db ${backupfile}`, { shell: true });
console.log(
'Secrets (COOLIFY_SECRET_KEY & COOLIFY_SECRET_KEY_BETTER) are different, so re-encrypting everything...'
);
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY=/d' .env`, { shell: true });
await execaCommand(`sed -i '/COOLIFY_SECRET_KEY_BETTER=/d' .env`, { shell: true });
await execaCommand(`echo "COOLIFY_SECRET_KEY=${secretNew}" >> .env`, { shell: true });
await execaCommand('echo "COOLIFY_SECRET_KEY_BETTER=' + secretNew + '" >> .env ', {
shell: true
});
await execaCommand(`echo "COOLIFY_SECRET_KEY_OLD_${date}=${secretOld}" >> .env`, {
shell: true
});
const transactions = [];
const secrets = await prisma.secret.findMany();
if (secrets.length > 0) {
for (const secret of secrets) {
try {
const value = decrypt(secret.value, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.secret.update({
where: { id: secret.id },
data: { value: newValue }
})
);
} catch (e) {
console.log(e);
}
}
}
const serviceSecrets = await prisma.serviceSecret.findMany();
if (serviceSecrets.length > 0) {
for (const secret of serviceSecrets) {
try {
const value = decrypt(secret.value, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.serviceSecret.update({
where: { id: secret.id },
data: { value: newValue }
})
);
} catch (e) {
console.log(e);
}
}
}
const gitlabApps = await prisma.gitlabApp.findMany();
if (gitlabApps.length > 0) {
for (const gitlabApp of gitlabApps) {
try {
const value = decrypt(gitlabApp.privateSshKey, secretOld);
const newValue = encrypt(value, secretNew);
const appSecret = decrypt(gitlabApp.appSecret, secretOld);
const newAppSecret = encrypt(appSecret, secretNew);
transactions.push(
prisma.gitlabApp.update({
where: { id: gitlabApp.id },
data: { privateSshKey: newValue, appSecret: newAppSecret }
})
);
} catch (e) {
console.log(e);
}
}
}
const githubApps = await prisma.githubApp.findMany();
if (githubApps.length > 0) {
for (const githubApp of githubApps) {
try {
const clientSecret = decrypt(githubApp.clientSecret, secretOld);
const newClientSecret = encrypt(clientSecret, secretNew);
const webhookSecret = decrypt(githubApp.webhookSecret, secretOld);
const newWebhookSecret = encrypt(webhookSecret, secretNew);
const privateKey = decrypt(githubApp.privateKey, secretOld);
const newPrivateKey = encrypt(privateKey, secretNew);
transactions.push(
prisma.githubApp.update({
where: { id: githubApp.id },
data: {
clientSecret: newClientSecret,
webhookSecret: newWebhookSecret,
privateKey: newPrivateKey
}
})
);
} catch (e) {
console.log(e);
}
}
}
const databases = await prisma.database.findMany();
if (databases.length > 0) {
for (const database of databases) {
try {
const dbUserPassword = decrypt(database.dbUserPassword, secretOld);
const newDbUserPassword = encrypt(dbUserPassword, secretNew);
const rootUserPassword = decrypt(database.rootUserPassword, secretOld);
const newRootUserPassword = encrypt(rootUserPassword, secretNew);
transactions.push(
prisma.database.update({
where: { id: database.id },
data: {
dbUserPassword: newDbUserPassword,
rootUserPassword: newRootUserPassword
}
})
);
} catch (e) {
console.log(e);
}
}
}
const databaseSecrets = await prisma.databaseSecret.findMany();
if (databaseSecrets.length > 0) {
for (const databaseSecret of databaseSecrets) {
try {
const value = decrypt(databaseSecret.value, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.databaseSecret.update({
where: { id: databaseSecret.id },
data: { value: newValue }
})
);
} catch (e) {
console.log(e);
}
}
}
const wordpresses = await prisma.wordpress.findMany();
if (wordpresses.length > 0) {
for (const wordpress of wordpresses) {
try {
const value = decrypt(wordpress.ftpHostKey, secretOld);
const newValue = encrypt(value, secretNew);
const ftpHostKeyPrivate = decrypt(wordpress.ftpHostKeyPrivate, secretOld);
const newFtpHostKeyPrivate = encrypt(ftpHostKeyPrivate, secretNew);
let newFtpPassword = undefined;
if (wordpress.ftpPassword != null) {
const ftpPassword = decrypt(wordpress.ftpPassword, secretOld);
newFtpPassword = encrypt(ftpPassword, secretNew);
}
transactions.push(
prisma.wordpress.update({
where: { id: wordpress.id },
data: {
ftpHostKey: newValue,
ftpHostKeyPrivate: newFtpHostKeyPrivate,
ftpPassword: newFtpPassword
}
})
);
} catch (e) {
console.log(e);
}
}
}
const sshKeys = await prisma.sshKey.findMany();
if (sshKeys.length > 0) {
for (const key of sshKeys) {
try {
const value = decrypt(key.privateKey, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.sshKey.update({
where: { id: key.id },
data: {
privateKey: newValue
}
})
);
} catch (e) {
console.log(e);
}
}
}
const dockerRegistries = await prisma.dockerRegistry.findMany();
if (dockerRegistries.length > 0) {
for (const registry of dockerRegistries) {
try {
const value = decrypt(registry.password, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.dockerRegistry.update({
where: { id: registry.id },
data: {
password: newValue
}
})
);
} catch (e) {
console.log(e);
}
}
}
const certificates = await prisma.certificate.findMany();
if (certificates.length > 0) {
for (const certificate of certificates) {
try {
const value = decrypt(certificate.key, secretOld);
const newValue = encrypt(value, secretNew);
transactions.push(
prisma.certificate.update({
where: { id: certificate.id },
data: {
key: newValue
}
})
);
} catch (e) {
console.log(e);
}
}
}
await prisma.$transaction(transactions);
} else {
console.log('secrets are the same, so no need to re-encrypt');
}
}
const encrypt = (text, secret) => {
if (text && secret) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, secret, iv);
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
return JSON.stringify({
iv: iv.toString('hex'),
content: encrypted.toString('hex')
});
}
};
const decrypt = (hashString, secret) => {
if (hashString && secret) {
const hash = JSON.parse(hashString);
const decipher = crypto.createDecipheriv(algorithm, secret, Buffer.from(hash.iv, 'hex'));
const decrpyted = Buffer.concat([
decipher.update(Buffer.from(hash.content, 'hex')),
decipher.final()
]);
if (/<2F>/.test(decrpyted.toString())) {
throw new Error('Invalid secret. Skipping...');
}
return decrpyted.toString();
}
};
main() main()
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
@@ -99,15 +378,11 @@ main()
.finally(async () => { .finally(async () => {
await prisma.$disconnect(); await prisma.$disconnect();
}); });
reEncryptSecrets()
const encrypt = (text) => { .catch((e) => {
if (text) { console.error(e);
const iv = crypto.randomBytes(16); process.exit(1);
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv); })
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]); .finally(async () => {
return JSON.stringify({ await prisma.$disconnect();
iv: iv.toString('hex'), });
content: encrypted.toString('hex')
});
}
};

View File

@@ -1,14 +1,19 @@
import Fastify from 'fastify';
import cors from '@fastify/cors';
import serve from '@fastify/static';
import env from '@fastify/env';
import cookie from '@fastify/cookie';
import multipart from '@fastify/multipart';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload'; import autoLoad from '@fastify/autoload';
import cookie from '@fastify/cookie';
import cors from '@fastify/cors';
import env from '@fastify/env';
import multipart from '@fastify/multipart';
import serve from '@fastify/static';
import Fastify from 'fastify';
import socketIO from 'fastify-socket.io'; import socketIO from 'fastify-socket.io';
import path, { join } from 'path';
import socketIOServer from './realtime'; import socketIOServer from './realtime';
import Graceful from '@ladjs/graceful';
import { compareVersions } from 'compare-versions';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib';
import { import {
cleanupDockerStorage, cleanupDockerStorage,
createRemoteEngineConfiguration, createRemoteEngineConfiguration,
@@ -18,26 +23,20 @@ import {
isDev, isDev,
listSettings, listSettings,
prisma, prisma,
sentryDSN,
startTraefikProxy, startTraefikProxy,
startTraefikTCPProxy, startTraefikTCPProxy,
version version
} from './lib/common'; } from './lib/common';
import { scheduler } from './lib/scheduler';
import { compareVersions } from 'compare-versions';
import Graceful from '@ladjs/graceful';
import yaml from 'js-yaml';
import fs from 'fs/promises';
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
import { checkContainer } from './lib/docker'; import { checkContainer } from './lib/docker';
import { migrateApplicationPersistentStorage, migrateServicesToNewTemplate } from './lib'; import { scheduler } from './lib/scheduler';
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
import { refreshTags, refreshTemplates } from './routes/api/v1/handlers'; import { refreshTags, refreshTemplates } from './routes/api/v1/handlers';
import * as Sentry from '@sentry/node';
declare module 'fastify' { declare module 'fastify' {
interface FastifyInstance { interface FastifyInstance {
config: { config: {
COOLIFY_APP_ID: string; COOLIFY_APP_ID: string;
COOLIFY_SECRET_KEY: string; COOLIFY_SECRET_KEY: string;
COOLIFY_SECRET_KEY_BETTER: string | null;
COOLIFY_DATABASE_URL: string; COOLIFY_DATABASE_URL: string;
COOLIFY_IS_ON: string; COOLIFY_IS_ON: string;
COOLIFY_WHITE_LABELED: string; COOLIFY_WHITE_LABELED: string;
@@ -67,6 +66,10 @@ const host = '0.0.0.0';
COOLIFY_SECRET_KEY: { COOLIFY_SECRET_KEY: {
type: 'string' type: 'string'
}, },
COOLIFY_SECRET_KEY_BETTER: {
type: 'string',
default: null
},
COOLIFY_DATABASE_URL: { COOLIFY_DATABASE_URL: {
type: 'string', type: 'string',
default: 'file:../db/dev.db' default: 'file:../db/dev.db'
@@ -164,13 +167,18 @@ const host = '0.0.0.0';
// autoUpdater // autoUpdater
setInterval(async () => { setInterval(async () => {
await autoUpdater(); await autoUpdater();
}, 60000 * 15); }, 60000 * 60);
// cleanupStorage // cleanupStorage
setInterval(async () => { setInterval(async () => {
await cleanupStorage(); await cleanupStorage();
}, 60000 * 15); }, 60000 * 15);
// Cleanup stucked containers (not defined in Coolify, but still running and managed by Coolify)
setInterval(async () => {
await cleanupStuckedContainers();
}, 60000);
// checkProxies, checkFluentBit & refresh templates // checkProxies, checkFluentBit & refresh templates
setInterval(async () => { setInterval(async () => {
await checkProxies(); await checkProxies();
@@ -180,24 +188,32 @@ const host = '0.0.0.0';
// Refresh and check templates // Refresh and check templates
setInterval(async () => { setInterval(async () => {
await refreshTemplates(); await refreshTemplates();
}, 60000); }, 60000 * 10);
setInterval(async () => { setInterval(async () => {
await refreshTags(); await refreshTags();
}, 60000); }, 60000 * 10);
setInterval( setInterval(
async () => { async () => {
await migrateServicesToNewTemplate(); await migrateServicesToNewTemplate();
}, },
isDev ? 10000 : 60000 isDev ? 10000 : 60000 * 10
); );
setInterval(async () => { setInterval(async () => {
await copySSLCertificates(); await copySSLCertificates();
}, 10000); }, 10000);
await Promise.all([getTagsTemplates(), getArch(), getIPAddress(), configureRemoteDockers()]); await Promise.all([
getTagsTemplates(),
getArch(),
getIPAddress(),
configureRemoteDockers(),
refreshTemplates(),
refreshTags()
// cleanupStuckedContainers()
]);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
process.exit(1); process.exit(1);
@@ -219,7 +235,7 @@ async function getIPAddress() {
console.log(`Getting public IPv6 address...`); console.log(`Getting public IPv6 address...`);
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } }); await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } });
} }
} catch (error) {} } catch (error) { }
} }
async function getTagsTemplates() { async function getTagsTemplates() {
const { default: got } = await import('got'); const { default: got } = await import('got');
@@ -231,7 +247,7 @@ async function getTagsTemplates() {
if (await fs.stat('./testTemplate.yaml')) { if (await fs.stat('./testTemplate.yaml')) {
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8')); templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
} }
} catch (error) {} } catch (error) { }
try { try {
if (await fs.stat('./testTags.json')) { if (await fs.stat('./testTags.json')) {
const testTags = await fs.readFile('./testTags.json', 'utf8'); const testTags = await fs.readFile('./testTags.json', 'utf8');
@@ -239,7 +255,7 @@ async function getTagsTemplates() {
tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags))); tags = JSON.stringify(JSON.parse(tags).concat(JSON.parse(testTags)));
} }
} }
} catch (error) {} } catch (error) { }
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates))); await fs.writeFile('./templates.json', JSON.stringify(yaml.load(templates)));
await fs.writeFile('./tags.json', tags); await fs.writeFile('./tags.json', tags);
@@ -265,9 +281,6 @@ async function initServer() {
if (settings.doNotTrack === true) { if (settings.doNotTrack === true) {
console.log('[000] Telemetry disabled...'); console.log('[000] Telemetry disabled...');
} else { } else {
if (settings.sentryDSN !== sentryDSN) {
await prisma.setting.update({ where: { id: '0' }, data: { sentryDSN } });
}
// Initialize Sentry // Initialize Sentry
// Sentry.init({ // Sentry.init({
// dsn: sentryDSN, // dsn: sentryDSN,
@@ -282,7 +295,7 @@ async function initServer() {
try { try {
console.log(`[001] Initializing server...`); console.log(`[001] Initializing server...`);
await executeCommand({ command: `docker network create --attachable coolify` }); await executeCommand({ command: `docker network create --attachable coolify` });
} catch (error) {} } catch (error) { }
try { try {
console.log(`[002] Cleanup stucked builds...`); console.log(`[002] Cleanup stucked builds...`);
const isOlder = compareVersions('3.8.1', version); const isOlder = compareVersions('3.8.1', version);
@@ -292,10 +305,10 @@ async function initServer() {
data: { status: 'failed' } data: { status: 'failed' }
}); });
} }
} catch (error) {} } catch (error) { }
try { try {
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...'); console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
await fs.rm('/tmp/build-sources', { recursive: true, force: true }); if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@@ -308,9 +321,52 @@ async function getArch() {
console.log(`Getting architecture...`); console.log(`Getting architecture...`);
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } }); await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } });
} }
} catch (error) {} } catch (error) { }
} }
async function cleanupStuckedContainers() {
try {
const destinationDockers = await prisma.destinationDocker.findMany();
let enginesDone = new Set();
for (const destination of destinationDockers) {
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress))
return;
if (destination.engine) {
enginesDone.add(destination.engine);
}
if (destination.remoteIpAddress) {
if (!destination.remoteVerified) continue;
enginesDone.add(destination.remoteIpAddress);
}
const { stdout: containers } = await executeCommand({
dockerId: destination.id,
command: `docker container ps -a --filter "label=coolify.managed=true" --format '{{ .Names}}'`
});
if (containers) {
const containersArray = containers.trim().split('\n');
if (containersArray.length > 0) {
for (const container of containersArray) {
const containerId = container.split('-')[0];
const application = await prisma.application.findFirst({
where: { id: { startsWith: containerId } }
});
const service = await prisma.service.findFirst({
where: { id: { startsWith: containerId } }
});
const database = await prisma.database.findFirst({
where: { id: { startsWith: containerId } }
});
if (!application && !service && !database) {
await executeCommand({ command: `docker container rm -f ${container}` });
}
}
}
}
}
} catch (error) {
console.log(error);
}
}
async function configureRemoteDockers() { async function configureRemoteDockers() {
try { try {
const remoteDocker = await prisma.destinationDocker.findMany({ const remoteDocker = await prisma.destinationDocker.findMany({
@@ -348,14 +404,21 @@ async function autoUpdater() {
if (!isDev) { if (!isDev) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
if (isAutoUpdateEnabled) { if (isAutoUpdateEnabled) {
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` }); let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
await executeCommand({ shell: true, command: `env | grep '^COOLIFY' > .env` }); try {
await executeCommand({ command: `docker pull ${image}` });
} catch (error) {
image = `coollabsio/coolify:${latestVersion}`;
await executeCommand({ command: `docker pull ${image}` });
}
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
await executeCommand({ await executeCommand({
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
}); });
await executeCommand({ await executeCommand({
shell: true, shell: true,
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
}); });
} }
} else { } else {
@@ -421,7 +484,7 @@ async function checkProxies() {
} }
try { try {
await createRemoteEngineConfiguration(docker.id); await createRemoteEngineConfiguration(docker.id);
} catch (error) {} } catch (error) { }
} }
} }
// TCP Proxies // TCP Proxies
@@ -460,7 +523,7 @@ async function checkProxies() {
// await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000); // await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
// } // }
// } // }
} catch (error) {} } catch (error) { }
} }
async function copySSLCertificates() { async function copySSLCertificates() {
@@ -492,7 +555,11 @@ async function copySSLCertificates() {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` }); try {
await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` });
} catch (e) {
console.log(e);
}
} }
} }
@@ -543,54 +610,61 @@ async function cleanupStorage() {
let enginesDone = new Set(); let enginesDone = new Set();
for (const destination of destinationDockers) { for (const destination of destinationDockers) {
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return; if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return;
if (destination.engine) enginesDone.add(destination.engine); if (destination.engine) {
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress); enginesDone.add(destination.engine);
let force = false; }
let lowDiskSpace = false; if (destination.remoteIpAddress) {
try { if (!destination.remoteVerified) continue;
let stdout = null; enginesDone.add(destination.remoteIpAddress);
if (!isDev) { }
const output = await executeCommand({ await cleanupDockerStorage(destination.id);
dockerId: destination.id, // let lowDiskSpace = false;
command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`, // try {
shell: true // let stdout = null;
}); // if (!isDev) {
stdout = output.stdout; // const output = await executeCommand({
} else { // dockerId: destination.id,
const output = await executeCommand({ // command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`,
command: `df -kPT /` // shell: true
}); // });
stdout = output.stdout; // stdout = output.stdout;
} // } else {
let lines = stdout.trim().split('\n'); // const output = await executeCommand({
let header = lines[0]; // command: `df -kPT /`
let regex = // });
/^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g; // stdout = output.stdout;
const boundaries = []; // }
let match; // let lines = stdout.trim().split('\n');
// let header = lines[0];
// let regex =
// /^Filesystem\s+|Type\s+|1024-blocks|\s+Used|\s+Available|\s+Capacity|\s+Mounted on\s*$/g;
// const boundaries = [];
// let match;
while ((match = regex.exec(header))) { // while ((match = regex.exec(header))) {
boundaries.push(match[0].length); // boundaries.push(match[0].length);
} // }
boundaries[boundaries.length - 1] = -1; // boundaries[boundaries.length - 1] = -1;
const data = lines.slice(1).map((line) => { // const data = lines.slice(1).map((line) => {
const cl = boundaries.map((boundary) => { // const cl = boundaries.map((boundary) => {
const column = boundary > 0 ? line.slice(0, boundary) : line; // const column = boundary > 0 ? line.slice(0, boundary) : line;
line = line.slice(boundary); // line = line.slice(boundary);
return column.trim(); // return column.trim();
}); // });
return { // return {
capacity: Number.parseInt(cl[5], 10) / 100 // capacity: Number.parseInt(cl[5], 10) / 100
}; // };
}); // });
if (data.length > 0) { // if (data.length > 0) {
const { capacity } = data[0]; // const { capacity } = data[0];
if (capacity > 0.8) { // if (capacity > 0.8) {
lowDiskSpace = true; // lowDiskSpace = true;
} // }
} // }
} catch (error) {} // } catch (error) {}
await cleanupDockerStorage(destination.id, lowDiskSpace, force); // if (lowDiskSpace) {
// await cleanupDockerStorage(destination.id);
// }
} }
} }

View File

@@ -69,7 +69,15 @@ import * as buildpacks from '../lib/buildPacks';
teams: true teams: true
} }
}); });
if (!application) {
await prisma.build.update({
where: { id: queueBuild.id },
data: {
status: 'failed'
}
});
throw new Error('Application not found');
}
let { let {
id: buildId, id: buildId,
type, type,
@@ -110,6 +118,9 @@ import * as buildpacks from '../lib/buildPacks';
.replace(/\//gi, '-') .replace(/\//gi, '-')
.replace('-app', '')}:${storage.path}`; .replace('-app', '')}:${storage.path}`;
} }
if (storage.hostPath) {
return `${storage.hostPath}:${storage.path}`;
}
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`; return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}) || []; }) || [];
@@ -160,13 +171,24 @@ import * as buildpacks from '../lib/buildPacks';
port: exposePort ? `${exposePort}:${port}` : port port: exposePort ? `${exposePort}:${port}` : port
}); });
try { try {
const composeVolumes = volumes.map((volume) => { const composeVolumes = volumes
return { .filter((v) => {
[`${volume.split(':')[0]}`]: { if (
name: volume.split(':')[0] !v.startsWith('.') &&
!v.startsWith('..') &&
!v.startsWith('/') &&
!v.startsWith('~')
) {
return v;
} }
}; })
}); .map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
@@ -233,7 +255,7 @@ import * as buildpacks from '../lib/buildPacks';
applicationId: application.id applicationId: application.id
}); });
} }
await fs.rm(workdir, { recursive: true, force: true }); if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
return; return;
} }
try { try {
@@ -256,7 +278,7 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: error.stderr, buildId, applicationId }); await saveBuildLog({ line: error.stderr, buildId, applicationId });
} }
} finally { } finally {
await fs.rm(workdir, { recursive: true, force: true }); if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
await prisma.build.update({ await prisma.build.update({
where: { id: buildId }, where: { id: buildId },
data: { status: 'success' } data: { status: 'success' }
@@ -381,6 +403,9 @@ import * as buildpacks from '../lib/buildPacks';
.replace(/\//gi, '-') .replace(/\//gi, '-')
.replace('-app', '')}:${storage.path}`; .replace('-app', '')}:${storage.path}`;
} }
if (storage.hostPath) {
return `${storage.hostPath}:${storage.path}`;
}
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`; return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}) || []; }) || [];
@@ -406,7 +431,7 @@ import * as buildpacks from '../lib/buildPacks';
installCommand = configuration.installCommand; installCommand = configuration.installCommand;
startCommand = configuration.startCommand; startCommand = configuration.startCommand;
buildCommand = configuration.buildCommand; buildCommand = configuration.buildCommand;
publishDirectory = configuration.publishDirectory; publishDirectory = configuration.publishDirectory || '';
baseDirectory = configuration.baseDirectory || ''; baseDirectory = configuration.baseDirectory || '';
dockerFileLocation = configuration.dockerFileLocation; dockerFileLocation = configuration.dockerFileLocation;
dockerComposeFileLocation = configuration.dockerComposeFileLocation; dockerComposeFileLocation = configuration.dockerComposeFileLocation;
@@ -693,13 +718,24 @@ import * as buildpacks from '../lib/buildPacks';
await saveDockerRegistryCredentials({ url, username, password, workdir }); await saveDockerRegistryCredentials({ url, username, password, workdir });
} }
try { try {
const composeVolumes = volumes.map((volume) => { const composeVolumes = volumes
return { .filter((v) => {
[`${volume.split(':')[0]}`]: { if (
name: volume.split(':')[0] !v.startsWith('.') &&
!v.startsWith('..') &&
!v.startsWith('/') &&
!v.startsWith('~')
) {
return v;
} }
}; })
}); .map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = { const composeFile = {
version: '3.8', version: '3.8',
services: { services: {
@@ -770,7 +806,7 @@ import * as buildpacks from '../lib/buildPacks';
applicationId: application.id applicationId: application.id
}); });
} }
await fs.rm(workdir, { recursive: true, force: true }); if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
return; return;
} }
try { try {
@@ -791,7 +827,7 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: error.stderr, buildId, applicationId }); await saveBuildLog({ line: error.stderr, buildId, applicationId });
} }
} finally { } finally {
await fs.rm(workdir, { recursive: true, force: true }); if (!isDev) await fs.rm(workdir, { recursive: true, force: true });
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
}); });

View File

@@ -86,19 +86,20 @@ export async function migrateServicesToNewTemplate() {
if (template.variables) { if (template.variables) {
if (template.variables.length > 0) { if (template.variables.length > 0) {
for (const variable of template.variables) { for (const variable of template.variables) {
const { defaultValue } = variable; let { defaultValue } = variable;
defaultValue = defaultValue.toString();
const regex = /^\$\$.*\((\d+)\)$/g; const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined const length = Number(regex.exec(defaultValue)?.[1]) || undefined
if (variable.defaultValue.startsWith('$$generate_password')) { if (defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length }); variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) { } else if (defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true }); variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) { } else if (defaultValue.startsWith('$$generate_username')) {
variable.value = cuid(); variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) { } else if (defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken() variable.value = generateToken()
} else { } else {
variable.value = variable.defaultValue || ''; variable.value = defaultValue || '';
} }
} }
} }

View File

@@ -341,8 +341,8 @@ export function setDefaultBaseImage(
}; };
if (nodeBased.includes(buildPack)) { if (nodeBased.includes(buildPack)) {
if (deploymentType === 'static') { if (deploymentType === 'static') {
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine'; payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
payload.baseImages = isARM(process.arch) payload.baseImages = isARM()
? staticVersions.filter((version) => !version.value.includes('webdevops')) ? staticVersions.filter((version) => !version.value.includes('webdevops'))
: staticVersions; : staticVersions;
payload.baseBuildImage = 'node:lts'; payload.baseBuildImage = 'node:lts';
@@ -355,8 +355,8 @@ export function setDefaultBaseImage(
} }
} }
if (staticApps.includes(buildPack)) { if (staticApps.includes(buildPack)) {
payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine'; payload.baseImage = isARM() ? 'nginx:alpine' : 'webdevops/nginx:alpine';
payload.baseImages = isARM(process.arch) payload.baseImages = isARM()
? staticVersions.filter((version) => !version.value.includes('webdevops')) ? staticVersions.filter((version) => !version.value.includes('webdevops'))
: staticVersions; : staticVersions;
payload.baseBuildImage = 'node:lts'; payload.baseBuildImage = 'node:lts';
@@ -376,18 +376,18 @@ export function setDefaultBaseImage(
payload.baseImage = 'denoland/deno:latest'; payload.baseImage = 'denoland/deno:latest';
} }
if (buildPack === 'php') { if (buildPack === 'php') {
payload.baseImage = isARM(process.arch) payload.baseImage = isARM()
? 'php:8.1-fpm-alpine' ? 'php:8.1-fpm-alpine'
: 'webdevops/php-apache:8.2-alpine'; : 'webdevops/php-apache:8.2-alpine';
payload.baseImages = isARM(process.arch) payload.baseImages = isARM()
? phpVersions.filter((version) => !version.value.includes('webdevops')) ? phpVersions.filter((version) => !version.value.includes('webdevops'))
: phpVersions; : phpVersions;
} }
if (buildPack === 'laravel') { if (buildPack === 'laravel') {
payload.baseImage = isARM(process.arch) payload.baseImage = isARM()
? 'php:8.1-fpm-alpine' ? 'php:8.1-fpm-alpine'
: 'webdevops/php-apache:8.2-alpine'; : 'webdevops/php-apache:8.2-alpine';
payload.baseImages = isARM(process.arch) payload.baseImages = isARM()
? phpVersions.filter((version) => !version.value.includes('webdevops')) ? phpVersions.filter((version) => !version.value.includes('webdevops'))
: phpVersions; : phpVersions;
payload.baseBuildImage = 'node:18'; payload.baseBuildImage = 'node:18';
@@ -429,7 +429,12 @@ export const setDefaultConfiguration = async (data: any) => {
startCommand = template?.startCommand || 'yarn start'; startCommand = template?.startCommand || 'yarn start';
if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel') if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel')
buildCommand = template?.buildCommand || null; buildCommand = template?.buildCommand || null;
if (!publishDirectory) publishDirectory = template?.publishDirectory || null; if (!publishDirectory) {
publishDirectory = template?.publishDirectory || null;
} else {
if (!publishDirectory.startsWith('/')) publishDirectory = `/${publishDirectory}`;
if (publishDirectory.endsWith('/')) publishDirectory = publishDirectory.slice(0, -1);
}
if (baseDirectory) { if (baseDirectory) {
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`; if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
if (baseDirectory.endsWith('/') && baseDirectory !== '/') if (baseDirectory.endsWith('/') && baseDirectory !== '/')
@@ -702,9 +707,8 @@ export async function buildImage({
buildId, buildId,
applicationId, applicationId,
dockerId, dockerId,
command: `docker ${location ? `--config ${location}` : ''} build ${ command: `docker ${location ? `--config ${location}` : ''} build ${forceRebuild ? '--no-cache' : ''
forceRebuild ? '--no-cache' : '' } --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
} --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}`
}); });
const { status } = await prisma.build.findUnique({ where: { id: buildId } }); const { status } = await prisma.build.findUnique({ where: { id: buildId } });
@@ -800,6 +804,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);
} }
Dockerfile.push(`RUN ${buildCommand}`); Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true }); await buildImage({ ...data, isCache: true });
} }
@@ -817,6 +822,7 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
} }
Dockerfile.push(`COPY *.json *.mix.js /app/`); Dockerfile.push(`COPY *.json *.mix.js /app/`);
Dockerfile.push(`COPY resources /app/resources`); Dockerfile.push(`COPY resources /app/resources`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`RUN yarn install && yarn production`); Dockerfile.push(`RUN yarn install && yarn production`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true }); await buildImage({ ...data, isCache: true });
@@ -838,6 +844,7 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
Dockerfile.push('RUN cargo install cargo-chef'); Dockerfile.push('RUN cargo install cargo-chef');
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true }); await buildImage({ ...data, isCache: true });
} }

View File

@@ -19,7 +19,9 @@ export default async function (data) {
dockerComposeConfiguration, dockerComposeConfiguration,
dockerComposeFileLocation dockerComposeFileLocation
} = data; } = data;
const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`; const baseDir = `${workdir}${baseDirectory}`;
const envFile = `${baseDir}/.env`;
const fileYaml = `${baseDir}${dockerComposeFileLocation}`;
const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8'); const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8');
const dockerComposeYaml = yaml.load(dockerComposeRaw); const dockerComposeYaml = yaml.load(dockerComposeRaw);
if (!dockerComposeYaml.services) { if (!dockerComposeYaml.services) {
@@ -31,61 +33,114 @@ export default async function (data) {
envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)]; envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)];
buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)]; buildEnvs = [...buildEnvs, ...generateSecrets(secrets, pullmergeRequestId, true, null, true)];
} }
await fs.writeFile(envFile, envs.join('\n'));
const composeVolumes = []; const composeVolumes = [];
if (volumes.length > 0) { if (volumes.length > 0) {
for (const volume of volumes) { for (const volume of volumes) {
let [v, path] = volume.split(':'); let [v, path] = volume.split(':');
composeVolumes[v] = { if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
name: v composeVolumes[v] = {
}; name: v
};
}
} }
} }
let networks = {}; let networks = {};
for (let [key, value] of Object.entries(dockerComposeYaml.services)) { for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
value['container_name'] = `${applicationId}-${key}`; value['container_name'] = `${applicationId}-${key}`;
let environment = typeof value['environment'] === 'undefined' ? [] : value['environment']; if (value['env_file']) {
if (Object.keys(environment).length > 0) { delete value['env_file'];
environment = Object.entries(environment).map(([key, value]) => `${key}=${value}`);
} }
value['environment'] = [...environment, ...envs]; value['env_file'] = [envFile];
// let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'];
// let finalEnvs = [...envs];
// if (Object.keys(environment).length > 0) {
// for (const arg of Object.keys(environment)) {
// const [key, _] = arg.split('=');
// if (finalEnvs.filter((env) => env.startsWith(key)).length === 0) {
// finalEnvs.push(arg);
// }
// }
// }
// value['environment'] = [...finalEnvs];
let build = typeof value['build'] === 'undefined' ? [] : value['build']; let build = typeof value['build'] === 'undefined' ? [] : value['build'];
if (typeof build === 'string') { if (typeof build === 'string') {
build = { context: build }; build = { context: build };
} }
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args']; const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
let finalArgs = [...buildEnvs]; let finalBuildArgs = [...buildEnvs];
if (Object.keys(buildArgs).length > 0) { if (Object.keys(buildArgs).length > 0) {
for (const arg of buildArgs) { for (const arg of Object.keys(buildArgs)) {
const [key, _] = arg.split('='); const [key, _] = arg.split('=');
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) { if (finalBuildArgs.filter((env) => env.startsWith(key)).length === 0) {
finalArgs.push(arg); finalBuildArgs.push(arg);
} }
} }
} }
value['build'] = { if (build.length > 0 || buildArgs.length > 0) {
...build, value['build'] = {
args: finalArgs ...build,
}; args: finalBuildArgs
};
}
value['labels'] = labels; value['labels'] = labels;
// TODO: If we support separated volume for each service, we need to add it here // TODO: If we support separated volume for each service, we need to add it here
if (value['volumes']?.length > 0) { if (value['volumes']?.length > 0) {
value['volumes'] = value['volumes'].map((volume) => { value['volumes'] = value['volumes'].map((volume) => {
let [v, path, permission] = volume.split(':'); if (typeof volume === 'string') {
if (!path) { let [v, path, permission] = volume.split(':');
path = v; if (
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`; v.startsWith('.') ||
} else { v.startsWith('..') ||
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`; v.startsWith('/') ||
v.startsWith('~') ||
v.startsWith('$PWD')
) {
v = v
.replace(/^\./, `~`)
.replace(/^\.\./, '~')
.replace(/^\$PWD/, '~');
} else {
if (!path) {
path = v;
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
} else {
v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`;
}
composeVolumes[v] = {
name: v
};
}
return `${v}:${path}${permission ? ':' + permission : ''}`;
}
if (typeof volume === 'object') {
let { source, target, mode } = volume;
if (
source.startsWith('.') ||
source.startsWith('..') ||
source.startsWith('/') ||
source.startsWith('~') ||
source.startsWith('$PWD')
) {
source = source
.replace(/^\./, `~`)
.replace(/^\.\./, '~')
.replace(/^\$PWD/, '~');
} else {
if (!target) {
target = source;
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
} else {
source = `${applicationId}${source.replace(/\//gi, '-').replace(/\./gi, '')}`;
}
}
return `${source}:${target}${mode ? ':' + mode : ''}`;
} }
composeVolumes[v] = {
name: v
};
return `${v}:${path}${permission ? ':' + permission : ''}`;
}); });
} }
if (volumes.length > 0) { if (volumes.length > 0) {
@@ -93,17 +148,24 @@ export default async function (data) {
value['volumes'].push(volume); value['volumes'].push(volume);
} }
} }
if (dockerComposeConfiguration[key].port) { if (dockerComposeConfiguration[key]?.port) {
value['expose'] = [dockerComposeConfiguration[key].port]; value['expose'] = [dockerComposeConfiguration[key].port];
} }
if (value['networks']?.length > 0) { value['networks'] = [network];
value['networks'].forEach((network) => { if (value['build']?.network) {
networks[network] = { delete value['build']['network'];
name: network
};
});
} }
value['networks'] = [...(value['networks'] || ''), network]; // if (value['networks']?.length > 0) {
// value['networks'].forEach((network) => {
// networks[network] = {
// name: network
// };
// });
// value['networks'] = [...(value['networks'] || ''), network];
// } else {
// value['networks'] = [network];
// }
dockerComposeYaml.services[key] = { dockerComposeYaml.services[key] = {
...dockerComposeYaml.services[key], ...dockerComposeYaml.services[key],
restart: defaultComposeConfiguration(network).restart, restart: defaultComposeConfiguration(network).restart,
@@ -116,7 +178,6 @@ export default async function (data) {
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } }); dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } });
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml)); await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
console.log(yaml.dump(dockerComposeYaml));
await executeCommand({ await executeCommand({
debug, debug,
buildId, buildId,

View File

@@ -36,6 +36,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN deno cache ${denoMainFile}`); Dockerfile.push(`RUN deno cache ${denoMainFile}`);
Dockerfile.push(`ENV NO_COLOR true`); Dockerfile.push(`ENV NO_COLOR true`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`); Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -8,10 +8,11 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => {
Dockerfile.push(`FROM ${imageforBuild}`); Dockerfile.push(`FROM ${imageforBuild}`);
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -30,6 +30,7 @@ const createDockerfile = async (data, image): Promise<void> => {
`COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json` `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json`
); );
Dockerfile.push(`COPY --chown=application:application . ./`); Dockerfile.push(`COPY --chown=application:application . ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -2,7 +2,7 @@ import { promises as fs } from 'fs';
import { buildCacheImageWithNode, buildImage } from './common'; import { buildCacheImageWithNode, buildImage } from './common';
const createDockerfile = async (data, image): Promise<void> => { const createDockerfile = async (data, image): Promise<void> => {
const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data; const { buildId, applicationId, tag, port, startCommand, workdir, publishDirectory } = data;
const Dockerfile: Array<string> = []; const Dockerfile: Array<string> = [];
const isPnpm = startCommand.includes('pnpm'); const isPnpm = startCommand.includes('pnpm');
@@ -12,8 +12,8 @@ const createDockerfile = async (data, image): Promise<void> => {
if (isPnpm) { if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7');
} }
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`); Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -36,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`RUN ${buildCommand}`); Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`); Dockerfile.push(`CMD ${startCommand}`);
} else if (deploymentType === 'static') { } else if (deploymentType === 'static') {
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE 80`); Dockerfile.push(`EXPOSE 80`);
} }

View File

@@ -34,6 +34,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`RUN ${buildCommand}`); Dockerfile.push(`RUN ${buildCommand}`);
} }
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`CMD ${startCommand}`); Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -36,13 +36,15 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`); Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`RUN ${buildCommand}`); Dockerfile.push(`RUN ${buildCommand}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`); Dockerfile.push(`CMD ${startCommand}`);
} else if (deploymentType === 'static') { } else if (deploymentType === 'static') {
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE 80`); Dockerfile.push(`EXPOSE 80`);
} }

View File

@@ -28,6 +28,7 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
} }
Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`); Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -52,7 +52,7 @@ const createDockerfile = async (data, image): Promise<void> => {
} else { } else {
Dockerfile.push(`CMD python ${pythonModule}`); Dockerfile.push(`CMD python ${pythonModule}`);
} }
Dockerfile.push('RUN rm -fr .git');
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -20,6 +20,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
); );
Dockerfile.push(`RUN update-ca-certificates`); Dockerfile.push(`RUN update-ca-certificates`);
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`);
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ["/app/${name}"]`); Dockerfile.push(`CMD ["/app/${name}"]`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -31,13 +31,14 @@ const createDockerfile = async (data, image): Promise<void> => {
}); });
} }
if (buildCommand) { if (buildCommand) {
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
} else { } else {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
} }
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -8,10 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`FROM ${image}`); Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /app'); Dockerfile.push('WORKDIR /app');
Dockerfile.push(`LABEL coolify.buildId=${buildId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app${publishDirectory} ./`);
if (baseImage?.includes('nginx')) { if (baseImage?.includes('nginx')) {
Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`);
} }
Dockerfile.push('RUN rm -fr .git');
Dockerfile.push(`EXPOSE ${port}`); Dockerfile.push(`EXPOSE ${port}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));
}; };

View File

@@ -1,6 +1,5 @@
import { exec } from 'node:child_process';
import util from 'util';
import fs from 'fs/promises'; import fs from 'fs/promises';
import fsNormal from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import forge from 'node-forge'; import forge from 'node-forge';
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
@@ -8,21 +7,22 @@ import type { Config } from 'unique-names-generator';
import generator from 'generate-password'; import generator from 'generate-password';
import crypto from 'crypto'; import crypto from 'crypto';
import { promises as dns } from 'dns'; import { promises as dns } from 'dns';
import * as Sentry from '@sentry/node';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import os from 'os'; import os from 'os';
import sshConfig from 'ssh-config'; import * as SSHConfig from 'ssh-config/src/ssh-config';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { checkContainer, removeContainer } from './docker'; import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs'; import { day } from './dayjs';
import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common'; import { saveBuildLog } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa'; import type { ExecaChildProcess } from 'execa';
import { FastifyReply } from 'fastify';
export const version = '3.12.13'; export const version = '3.12.39';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
export const sentryDSN = export const proxyPort = process.env.COOLIFY_PROXY_PORT;
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216'; export const proxySecurePort = process.env.COOLIFY_PROXY_SECURE_PORT;
const algorithm = 'aes-256-ctr'; const algorithm = 'aes-256-ctr';
const customConfig: Config = { const customConfig: Config = {
dictionaries: [adjectives, colors, animals], dictionaries: [adjectives, colors, animals],
@@ -170,13 +170,19 @@ export const base64Encode = (text: string): string => {
export const base64Decode = (text: string): string => { export const base64Decode = (text: string): string => {
return Buffer.from(text, 'base64').toString('ascii'); return Buffer.from(text, 'base64').toString('ascii');
}; };
export const getSecretKey = () => {
if (process.env['COOLIFY_SECRET_KEY_BETTER']) {
return process.env['COOLIFY_SECRET_KEY_BETTER'];
}
return process.env['COOLIFY_SECRET_KEY'];
};
export const decrypt = (hashString: string) => { export const decrypt = (hashString: string) => {
if (hashString) { if (hashString) {
try { try {
const hash = JSON.parse(hashString); const hash = JSON.parse(hashString);
const decipher = crypto.createDecipheriv( const decipher = crypto.createDecipheriv(
algorithm, algorithm,
process.env['COOLIFY_SECRET_KEY'], getSecretKey(),
Buffer.from(hash.iv, 'hex') Buffer.from(hash.iv, 'hex')
); );
const decrpyted = Buffer.concat([ const decrpyted = Buffer.concat([
@@ -193,7 +199,7 @@ export const decrypt = (hashString: string) => {
export const encrypt = (text: string) => { export const encrypt = (text: string) => {
if (text) { if (text) {
const iv = crypto.randomBytes(16); const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algorithm, process.env['COOLIFY_SECRET_KEY'], iv); const cipher = crypto.createCipheriv(algorithm, getSecretKey(), iv);
const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]); const encrypted = Buffer.concat([cipher.update(text.trim()), cipher.final()]);
return JSON.stringify({ return JSON.stringify({
iv: iv.toString('hex'), iv: iv.toString('hex'),
@@ -400,8 +406,8 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'MongoDB', fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb', baseImage: 'bitnami/mongodb',
baseImageARM: 'mongo', baseImageARM: 'mongo',
versions: ['5.0', '4.4', '4.2'], versions: ['6.0', '5.0', '4.4', '4.2'],
versionsARM: ['5.0', '4.4', '4.2'] versionsARM: ['6.0', '5.0', '4.4', '4.2']
}, },
{ {
name: 'mysql', name: 'mysql',
@@ -416,16 +422,16 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'MariaDB', fancyName: 'MariaDB',
baseImage: 'bitnami/mariadb', baseImage: 'bitnami/mariadb',
baseImageARM: 'mariadb', baseImageARM: 'mariadb',
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'], versions: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'] versionsARM: ['10.11', '10.10', '10.9', '10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
}, },
{ {
name: 'postgresql', name: 'postgresql',
fancyName: 'PostgreSQL', fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql', baseImage: 'bitnami/postgresql',
baseImageARM: 'postgres', baseImageARM: 'postgres',
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'], versions: ['15.2.0', '14.7.0', '14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22'] versionsARM: ['15.2', '14.7', '14.5', '13.8', '12.12', '11.17', '10.22']
}, },
{ {
name: 'redis', name: 'redis',
@@ -440,14 +446,14 @@ export const supportedDatabaseTypesAndVersions = [
fancyName: 'CouchDB', fancyName: 'CouchDB',
baseImage: 'bitnami/couchdb', baseImage: 'bitnami/couchdb',
baseImageARM: 'couchdb', baseImageARM: 'couchdb',
versions: ['3.2.2', '3.1.2', '2.3.1'], versions: ['3.3.1', '3.2.2', '3.1.2', '2.3.1'],
versionsARM: ['3.2.2', '3.1.2', '2.3.1'] versionsARM: ['3.3', '3.2.2', '3.1.2', '2.3.1']
}, },
{ {
name: 'edgedb', name: 'edgedb',
fancyName: 'EdgeDB', fancyName: 'EdgeDB',
baseImage: 'edgedb/edgedb', baseImage: 'edgedb/edgedb',
versions: ['latest', '2.1', '2.0', '1.4'] versions: ['latest', '2.9', '2.8', '2.7']
} }
]; ];
@@ -496,33 +502,56 @@ export async function getFreeSSHLocalPort(id: string): Promise<number | boolean>
return false; return false;
} }
/**
* Update the ssh config file with a host
*
* @param id Destination ID
* @returns
*/
export async function createRemoteEngineConfiguration(id: string) { export async function createRemoteEngineConfiguration(id: string) {
const homedir = os.homedir();
const sshKeyFile = `/tmp/id_rsa-${id}`; const sshKeyFile = `/tmp/id_rsa-${id}`;
const localPort = await getFreeSSHLocalPort(id); const localPort = await getFreeSSHLocalPort(id);
const { const {
sshKey: { privateKey }, sshKey: { privateKey },
network,
remoteIpAddress, remoteIpAddress,
remotePort, remotePort,
remoteUser remoteUser
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }); } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
// Write new keyfile
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }); await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
const config = sshConfig.parse('');
const Host = `${remoteIpAddress}-remote`; const Host = `${remoteIpAddress}-remote`;
// Removes previous ssh-keys
try { try {
await executeCommand({ command: `ssh-keygen -R ${Host}` }); await executeCommand({ command: `ssh-keygen -R ${Host}` });
await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` }); await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` });
await executeCommand({ command: `ssh-keygen -R localhost:${localPort}` }); await executeCommand({ command: `ssh-keygen -R localhost:${localPort}` });
} catch (error) {} } catch (error) {
//
}
const homedir = os.homedir();
let currentConfigFileContent = '';
try {
// Read the current config file
currentConfigFileContent = (await fs.readFile(`${homedir}/.ssh/config`)).toString();
} catch (error) {
// File doesn't exist, so we do nothing, a new one is going to be created
}
// Parse the config file
const config = SSHConfig.parse(currentConfigFileContent);
// Remove current config for the given host
const found = config.find({ Host }); const found = config.find({ Host });
const foundIp = config.find({ Host: remoteIpAddress }); const foundIp = config.find({ Host: remoteIpAddress });
if (found) config.remove({ Host }); if (found) config.remove({ Host });
if (foundIp) config.remove({ Host: remoteIpAddress }); if (foundIp) config.remove({ Host: remoteIpAddress });
// Create the new config
config.append({ config.append({
Host, Host,
Hostname: remoteIpAddress, Hostname: remoteIpAddress,
@@ -535,13 +564,17 @@ export async function createRemoteEngineConfiguration(id: string) {
ControlPersist: '10m' ControlPersist: '10m'
}); });
// Check if .ssh folder exists, and if not create one
try { try {
await fs.stat(`${homedir}/.ssh/`); await fs.stat(`${homedir}/.ssh/`);
} catch (error) { } catch (error) {
await fs.mkdir(`${homedir}/.ssh/`); await fs.mkdir(`${homedir}/.ssh/`);
} }
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config));
// Write the config
return await fs.writeFile(`${homedir}/.ssh/config`, SSHConfig.stringify(config));
} }
export async function executeCommand({ export async function executeCommand({
command, command,
dockerId = null, dockerId = null,
@@ -550,7 +583,8 @@ export async function executeCommand({
stream = false, stream = false,
buildId, buildId,
applicationId, applicationId,
debug debug,
timeout = 0
}: { }: {
command: string; command: string;
sshCommand?: boolean; sshCommand?: boolean;
@@ -560,6 +594,7 @@ export async function executeCommand({
buildId?: string; buildId?: string;
applicationId?: string; applicationId?: string;
debug?: boolean; debug?: boolean;
timeout?: number;
}): Promise<ExecaChildProcess<string>> { }): Promise<ExecaChildProcess<string>> {
const { execa, execaCommand } = await import('execa'); const { execa, execaCommand } = await import('execa');
const { parse } = await import('shell-quote'); const { parse } = await import('shell-quote');
@@ -584,20 +619,26 @@ export async function executeCommand({
} }
if (sshCommand) { if (sshCommand) {
if (shell) { if (shell) {
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`); return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, {
timeout
});
} }
return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs]); return await execa('ssh', [`${remoteIpAddress}-remote`, dockerCommand, ...dockerArgs], {
timeout
});
} }
if (stream) { if (stream) {
return await new Promise(async (resolve, reject) => { return await new Promise(async (resolve, reject) => {
let subprocess = null; let subprocess = null;
if (shell) { if (shell) {
subprocess = execaCommand(command, { subprocess = execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} else { } else {
subprocess = execa(dockerCommand, dockerArgs, { subprocess = execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} }
const logs = []; const logs = [];
@@ -651,19 +692,26 @@ export async function executeCommand({
} else { } else {
if (shell) { if (shell) {
return await execaCommand(command, { return await execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} else { } else {
return await execa(dockerCommand, dockerArgs, { return await execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine } env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine },
timeout
}); });
} }
} }
} else { } else {
if (shell) { if (shell) {
return execaCommand(command, { shell: true }); return execaCommand(command, {
shell: true,
timeout
});
} }
return await execa(dockerCommand, dockerArgs); return await execa(dockerCommand, dockerArgs, {
timeout
});
} }
} }
@@ -712,8 +760,8 @@ export async function startTraefikProxy(id: string): Promise<void> {
-v coolify-traefik-letsencrypt:/etc/traefik/acme \ -v coolify-traefik-letsencrypt:/etc/traefik/acme \
-v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/docker.sock:/var/run/docker.sock \
--network coolify-infra \ --network coolify-infra \
-p "80:80" \ -p ${proxyPort ? `${proxyPort}:80` : `80:80`} \
-p "443:443" \ -p ${proxySecurePort ? `${proxySecurePort}:443` : `443:443`} \
${isDev ? '-p "8080:8080"' : ''} \ ${isDev ? '-p "8080:8080"' : ''} \
--name coolify-proxy \ --name coolify-proxy \
-d ${defaultTraefikImage} \ -d ${defaultTraefikImage} \
@@ -797,7 +845,7 @@ export function generateToken() {
{ {
nbf: Math.floor(Date.now() / 1000) - 30 nbf: Math.floor(Date.now() / 1000) - 30
}, },
process.env['COOLIFY_SECRET_KEY'] getSecretKey()
); );
} }
export function generatePassword({ export function generatePassword({
@@ -820,101 +868,101 @@ export function generatePassword({
type DatabaseConfiguration = type DatabaseConfiguration =
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MYSQL_DATABASE: string; MYSQL_DATABASE: string;
MYSQL_PASSWORD: string; MYSQL_PASSWORD: string;
MYSQL_ROOT_USER: string; MYSQL_ROOT_USER: string;
MYSQL_USER: string; MYSQL_USER: string;
MYSQL_ROOT_PASSWORD: string; MYSQL_ROOT_PASSWORD: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MONGO_INITDB_ROOT_USERNAME?: string; MONGO_INITDB_ROOT_USERNAME?: string;
MONGO_INITDB_ROOT_PASSWORD?: string; MONGO_INITDB_ROOT_PASSWORD?: string;
MONGODB_ROOT_USER?: string; MONGODB_ROOT_USER?: string;
MONGODB_ROOT_PASSWORD?: string; MONGODB_ROOT_PASSWORD?: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
MARIADB_ROOT_USER: string; MARIADB_ROOT_USER: string;
MARIADB_ROOT_PASSWORD: string; MARIADB_ROOT_PASSWORD: string;
MARIADB_USER: string; MARIADB_USER: string;
MARIADB_PASSWORD: string; MARIADB_PASSWORD: string;
MARIADB_DATABASE: string; MARIADB_DATABASE: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
POSTGRES_PASSWORD?: string; POSTGRES_PASSWORD?: string;
POSTGRES_USER?: string; POSTGRES_USER?: string;
POSTGRES_DB?: string; POSTGRES_DB?: string;
POSTGRESQL_POSTGRES_PASSWORD?: string; POSTGRESQL_POSTGRES_PASSWORD?: string;
POSTGRESQL_USERNAME?: string; POSTGRESQL_USERNAME?: string;
POSTGRESQL_PASSWORD?: string; POSTGRESQL_PASSWORD?: string;
POSTGRESQL_DATABASE?: string; POSTGRESQL_DATABASE?: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
REDIS_AOF_ENABLED: string; REDIS_AOF_ENABLED: string;
REDIS_PASSWORD: string; REDIS_PASSWORD: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
COUCHDB_PASSWORD: string; COUCHDB_PASSWORD: string;
COUCHDB_USER: string; COUCHDB_USER: string;
}; };
} }
| { | {
volume: string; volume: string;
image: string; image: string;
command?: string; command?: string;
ulimits: Record<string, unknown>; ulimits: Record<string, unknown>;
privatePort: number; privatePort: number;
environmentVariables: { environmentVariables: {
EDGEDB_SERVER_PASSWORD: string; EDGEDB_SERVER_PASSWORD: string;
EDGEDB_SERVER_USER: string; EDGEDB_SERVER_USER: string;
EDGEDB_SERVER_DATABASE: string; EDGEDB_SERVER_DATABASE: string;
EDGEDB_SERVER_TLS_CERT_MODE: string; EDGEDB_SERVER_TLS_CERT_MODE: string;
}; };
}; };
export function generateDatabaseConfiguration(database: any, arch: string): DatabaseConfiguration { export function generateDatabaseConfiguration(database: any): DatabaseConfiguration {
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } = const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
database; database;
const baseImage = getDatabaseImage(type, arch); const baseImage = getDatabaseImage(type);
if (type === 'mysql') { if (type === 'mysql') {
const configuration = { const configuration = {
privatePort: 3306, privatePort: 3306,
@@ -929,7 +977,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/mysql/data`, volume: `${id}-${type}-data:/bitnami/mysql/data`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`; configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
} }
return configuration; return configuration;
@@ -947,7 +995,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/mariadb`, volume: `${id}-${type}-data:/bitnami/mariadb`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`; configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
} }
return configuration; return configuration;
@@ -962,7 +1010,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/mongodb`, volume: `${id}-${type}-data:/bitnami/mongodb`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.environmentVariables = { configuration.environmentVariables = {
MONGO_INITDB_ROOT_USERNAME: rootUser, MONGO_INITDB_ROOT_USERNAME: rootUser,
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
@@ -983,8 +1031,8 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/postgresql`, volume: `${id}-${type}-data:/bitnami/postgresql`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/var/lib/postgresql`; configuration.volume = `${id}-${type}-data:/var/lib/postgresql/data`;
configuration.environmentVariables = { configuration.environmentVariables = {
POSTGRES_PASSWORD: dbUserPassword, POSTGRES_PASSWORD: dbUserPassword,
POSTGRES_USER: dbUser, POSTGRES_USER: dbUser,
@@ -1007,11 +1055,10 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/redis/data`, volume: `${id}-${type}-data:/bitnami/redis/data`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/data`; configuration.volume = `${id}-${type}-data:/data`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${ configuration.command = `/usr/local/bin/redis-server --appendonly ${appendOnly ? 'yes' : 'no'
appendOnly ? 'yes' : 'no' } --requirepass ${dbUserPassword}`;
} --requirepass ${dbUserPassword}`;
} }
return configuration; return configuration;
} else if (type === 'couchdb') { } else if (type === 'couchdb') {
@@ -1025,7 +1072,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
volume: `${id}-${type}-data:/bitnami/couchdb`, volume: `${id}-${type}-data:/bitnami/couchdb`,
ulimits: {} ulimits: {}
}; };
if (isARM(arch)) { if (isARM()) {
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`; configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
} }
return configuration; return configuration;
@@ -1045,16 +1092,17 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
return configuration; return configuration;
} }
} }
export function isARM(arch: string) { export function isARM() {
const arch = process.arch;
if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') { if (arch === 'arm' || arch === 'arm64' || arch === 'aarch' || arch === 'aarch64') {
return true; return true;
} }
return false; return false;
} }
export function getDatabaseImage(type: string, arch: string): string { export function getDatabaseImage(type: string): string {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type); const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) { if (found) {
if (isARM(arch)) { if (isARM()) {
return found.baseImageARM || found.baseImage; return found.baseImageARM || found.baseImage;
} }
return found.baseImage; return found.baseImage;
@@ -1062,10 +1110,10 @@ export function getDatabaseImage(type: string, arch: string): string {
return ''; return '';
} }
export function getDatabaseVersions(type: string, arch: string): string[] { export function getDatabaseVersions(type: string): string[] {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type); const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) { if (found) {
if (isARM(arch)) { if (isARM()) {
return found.versionsARM || found.versions; return found.versionsARM || found.versions;
} }
return found.versions; return found.versions;
@@ -1095,12 +1143,12 @@ export type ComposeFileService = {
command?: string; command?: string;
ports?: string[]; ports?: string[];
build?: build?:
| { | {
context: string; context: string;
dockerfile: string; dockerfile: string;
args?: Record<string, unknown>; args?: Record<string, unknown>;
} }
| string; | string;
deploy?: { deploy?: {
restart_policy?: { restart_policy?: {
condition?: string; condition?: string;
@@ -1171,7 +1219,7 @@ export const createDirectories = async ({
let workdirFound = false; let workdirFound = false;
try { try {
workdirFound = !!(await fs.stat(workdir)); workdirFound = !!(await fs.stat(workdir));
} catch (error) {} } catch (error) { }
if (workdirFound) { if (workdirFound) {
await executeCommand({ command: `rm -fr ${workdir}` }); await executeCommand({ command: `rm -fr ${workdir}` });
} }
@@ -1631,8 +1679,8 @@ export function errorHandler({
type?: string | null; type?: string | null;
}) { }) {
if (message.message) message = message.message; if (message.message) message = message.message;
if (type === 'normal') { if (message.includes('Unique constraint failed')) {
Sentry.captureException(message); message = 'This data is unique and already exists. Please try again with a different value.';
} }
throw { status, message }; throw { status, message };
} }
@@ -1695,7 +1743,7 @@ export async function stopBuild(buildId, applicationId) {
} }
} }
count++; count++;
} catch (error) {} } catch (error) { }
}, 100); }, 100);
}); });
} }
@@ -1714,77 +1762,28 @@ export function convertTolOldVolumeNames(type) {
} }
} }
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) { export async function cleanupDockerStorage(dockerId, volumes = false) {
// Cleanup old coolify images // Cleanup images that are not used by any container
try { try {
let { stdout: images } = await executeCommand({ await executeCommand({ dockerId, command: `docker image prune -af` });
} catch (error) { }
// Prune coolify managed containers
try {
await executeCommand({
dockerId, dockerId,
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`, command: `docker container prune -f --filter "label=coolify.managed=true"`
shell: true
}); });
} catch (error) { }
images = images.trim(); // Cleanup build caches
if (images) { try {
await executeCommand({ await executeCommand({ dockerId, command: `docker builder prune -af` });
dockerId, } catch (error) { }
command: `docker rmi -f ${images}" -q | xargs -r`, if (volumes) {
shell: true
});
}
} catch (error) {}
if (lowDiskSpace || force) {
// Cleanup images that are not used
try { try {
await executeCommand({ dockerId, command: `docker image prune -f` }); await executeCommand({ dockerId, command: `docker volume prune -af` });
} catch (error) {} } catch (error) { }
const { numberOfDockerImagesKeptLocally } = await prisma.setting.findUnique({
where: { id: '0' }
});
const { stdout: images } = await executeCommand({
dockerId,
command: `docker images|grep -v "<none>"|grep -v REPOSITORY|awk '{print $1, $2}'`,
shell: true
});
const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
let deleteImage = [];
for (const image of imagesSet) {
let keepImage = [];
for (const image2 of imagesArray) {
if (image2.startsWith(image)) {
if (force) {
deleteImage.push(image2);
continue;
}
if (keepImage.length >= numberOfDockerImagesKeptLocally) {
deleteImage.push(image2);
} else {
keepImage.push(image2);
}
}
}
}
for (const image of deleteImage) {
try {
await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
} catch (error) {
console.log(error);
}
}
// Prune coolify managed containers
try {
await executeCommand({
dockerId,
command: `docker container prune -f --filter "label=coolify.managed=true"`
});
} catch (error) {}
// Cleanup build caches
try {
await executeCommand({ dockerId, command: `docker builder prune -a -f` });
} catch (error) {}
} }
} }
@@ -1948,3 +1947,49 @@ export function generateSecrets(
} }
return envs; return envs;
} }
export async function backupDatabaseNow(database, reply) {
const backupFolder = '/tmp'
const fileName = `${database.id}-${new Date().getTime()}.gz`
const backupFileName = `${backupFolder}/${fileName}`
const backupStorageFilename = `/app/backups/${fileName}`
let command = null
switch (database?.type) {
case 'postgresql':
command = `docker exec ${database.id} sh -c "PGPASSWORD=${database.rootUserPassword} pg_dumpall -U postgres | gzip > ${backupFileName}"`
break;
case 'mongodb':
command = `docker exec ${database.id} sh -c "mongodump --archive=${backupFileName} --gzip --username=${database.rootUser} --password=${database.rootUserPassword}"`
break;
case 'mysql':
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
break;
case 'mariadb':
command = `docker exec ${database.id} sh -c "mysqldump --all-databases --single-transaction --quick --lock-tables=false --user=${database.rootUser} --password=${database.rootUserPassword} | gzip > ${backupFileName}"`
break;
case 'couchdb':
command = `docker exec ${database.id} sh -c "tar -czvf ${backupFileName} /bitnami/couchdb/data"`
break;
default:
return;
}
await executeCommand({
dockerId: database.destinationDockerId,
command,
});
const copyCommand = `docker cp ${database.id}:${backupFileName} ${backupFileName}`
await executeCommand({
dockerId: database.destinationDockerId,
command: copyCommand
});
await executeCommand({
dockerId: database.destinationDockerId,
command: `docker cp ${database.id}:${backupFileName} /app/backups/`
});
const stream = fsNormal.createReadStream(backupFileName);
reply.header('Content-Type', 'application/octet-stream');
reply.header('Content-Disposition', `attachment; filename=${fileName}`);
reply.header('Content-Length', fsNormal.statSync(backupFileName).size);
reply.header('Content-Transfer-Encoding', 'binary');
return reply.send(stream)
}

View File

@@ -6,7 +6,7 @@ export async function getTemplates() {
try { try {
let data = await open.readFile({ encoding: 'utf-8' }); let data = await open.readFile({ encoding: 'utf-8' });
let jsonData = JSON.parse(data); let jsonData = JSON.parse(data);
if (isARM(process.arch)) { if (isARM()) {
jsonData = jsonData.filter((d) => d.arch !== 'amd64'); jsonData = jsonData.filter((d) => d.arch !== 'amd64');
} }
return jsonData; return jsonData;

View File

@@ -40,7 +40,7 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId }); const service = await getServiceFromDB({ id, teamId });
const arm = isARM(service.arch); const arm = isARM();
const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } = const { type, destinationDockerId, destinationDocker, persistentStorage, exposePort } =
service; service;
@@ -50,24 +50,12 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
const config = {}; const config = {};
for (const s in template.services) { for (const s in template.services) {
let newEnvironments = [] let newEnvironments = []
if (arm) { if (template.services[s]?.environment?.length > 0) {
if (template.services[s]?.environmentArm?.length > 0) { for (const environment of template.services[s].environment) {
for (const environment of template.services[s].environmentArm) { let [env, ...value] = environment.split("=");
let [env, ...value] = environment.split("="); value = value.join("=")
value = value.join("=") if (!value.startsWith('$$secret') && value !== '') {
if (!value.startsWith('$$secret') && value !== '') { newEnvironments.push(`${env}=${value}`)
newEnvironments.push(`${env}=${value}`)
}
}
}
} else {
if (template.services[s]?.environment?.length > 0) {
for (const environment of template.services[s].environment) {
let [env, ...value] = environment.split("=");
value = value.join("=")
if (!value.startsWith('$$secret') && value !== '') {
newEnvironments.push(`${env}=${value}`)
}
} }
} }
} }
@@ -87,12 +75,13 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
} }
const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } }) const customVolumes = await prisma.servicePersistentStorage.findMany({ where: { serviceId: id } })
let volumes = new Set() let volumes = new Set()
if (arm) { if (arm && template.services[s]?.volumesArm?.length > 0) {
template.services[s]?.volumesArm && template.services[s].volumesArm.length > 0 && template.services[s].volumesArm.forEach(v => volumes.add(v)) template.services[s].volumesArm.forEach(v => volumes.add(v))
} else { } else {
template.services[s]?.volumes && template.services[s].volumes.length > 0 && template.services[s].volumes.forEach(v => volumes.add(v)) if (template.services[s]?.volumes?.length > 0) {
template.services[s].volumes.forEach(v => volumes.add(v))
}
} }
// Workaround: old plausible analytics service wrong volume id name // Workaround: old plausible analytics service wrong volume id name
if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) { if (service.type === 'plausibleanalytics' && service.plausibleAnalytics?.id) {
let temp = Array.from(volumes) let temp = Array.from(volumes)

View File

@@ -624,7 +624,7 @@ export const glitchTip = [{
isEncrypted: false isEncrypted: false
}, },
{ {
name: 'emailSmtpUseSsl', name: 'emailSmtpUseTls',
isEditable: true, isEditable: true,
isLowerCase: false, isLowerCase: false,
isNumber: false, isNumber: false,

View File

@@ -1,33 +1,37 @@
import fp from 'fastify-plugin' import fp from 'fastify-plugin';
import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt' import fastifyJwt, { FastifyJWTOptions } from '@fastify/jwt';
declare module "@fastify/jwt" { declare module '@fastify/jwt' {
interface FastifyJWT { interface FastifyJWT {
user: { user: {
userId: string, userId: string;
teamId: string, teamId: string;
permission: string, permission: string;
isAdmin: boolean isAdmin: boolean;
} };
} }
} }
export default fp<FastifyJWTOptions>(async (fastify, opts) => { export default fp<FastifyJWTOptions>(async (fastify, opts) => {
fastify.register(fastifyJwt, { let secretKey = fastify.config.COOLIFY_SECRET_KEY_BETTER;
secret: fastify.config.COOLIFY_SECRET_KEY if (!secretKey) {
}) secretKey = fastify.config.COOLIFY_SECRET_KEY;
}
fastify.register(fastifyJwt, {
secret: secretKey
});
fastify.decorate("authenticate", async function (request, reply) { fastify.decorate('authenticate', async function (request, reply) {
try { try {
await request.jwtVerify() await request.jwtVerify();
} catch (err) { } catch (err) {
reply.send(err) reply.send(err);
} }
}) });
}) });
declare module 'fastify' { declare module 'fastify' {
export interface FastifyInstance { export interface FastifyInstance {
authenticate(): Promise<void> authenticate(): Promise<void>;
} }
} }

View File

@@ -117,7 +117,7 @@ export async function getImages(request: FastifyRequest<GetImages>) {
export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) { export async function cleanupUnconfiguredApplications(request: FastifyRequest<any>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
let applications = await prisma.application.findMany({ const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true } include: { settings: true, destinationDocker: true, teams: true }
}); });
@@ -167,7 +167,7 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
const { teamId } = request.user; const { teamId } = request.user;
let payload = []; const payload = [];
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) { if (application?.destinationDockerId) {
if (application.buildPack === 'compose') { if (application.buildPack === 'compose') {
@@ -398,7 +398,9 @@ export async function saveApplication(
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile, simpleDockerfile,
dockerRegistryImageName dockerRegistryImageName,
basicAuthPw,
basicAuthUser,
} = request.body; } = request.body;
if (port) port = Number(port); if (port) port = Number(port);
if (exposePort) { if (exposePort) {
@@ -453,6 +455,8 @@ export async function saveApplication(
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile, simpleDockerfile,
dockerRegistryImageName, dockerRegistryImageName,
basicAuthPw,
basicAuthUser,
...defaultConfiguration, ...defaultConfiguration,
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } } connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
} }
@@ -476,6 +480,8 @@ export async function saveApplication(
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile, simpleDockerfile,
basicAuthPw,
basicAuthUser,
dockerRegistryImageName, dockerRegistryImageName,
...defaultConfiguration ...defaultConfiguration
} }
@@ -499,12 +505,11 @@ export async function saveApplicationSettings(
previews, previews,
dualCerts, dualCerts,
autodeploy, autodeploy,
branch,
projectId,
isBot, isBot,
isDBBranching, isDBBranching,
isCustomSSL, isCustomSSL,
isHttp2 isHttp2,
basicAuth,
} = request.body; } = request.body;
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
@@ -519,7 +524,8 @@ export async function saveApplicationSettings(
isBot, isBot,
isDBBranching, isDBBranching,
isCustomSSL, isCustomSSL,
isHttp2 isHttp2,
basicAuth,
} }
} }
}, },
@@ -565,7 +571,7 @@ export async function restartApplication(
const { id } = request.params; const { id } = request.params;
const { imageId = null } = request.body; const { imageId = null } = request.body;
const { teamId } = request.user; const { teamId } = request.user;
let application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) { if (application?.destinationDockerId) {
const buildId = cuid(); const buildId = cuid();
const { id: dockerId, network } = application.destinationDocker; const { id: dockerId, network } = application.destinationDocker;
@@ -640,9 +646,7 @@ export async function restartApplication(
const volumes = const volumes =
persistentStorage?.map((storage) => { persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
buildPack !== 'docker' ? '/app' : ''
}${storage.path}`;
}) || []; }) || [];
const composeVolumes = volumes.map((volume) => { const composeVolumes = volumes.map((volume) => {
return { return {
@@ -732,14 +736,15 @@ export async function deleteApplication(
) { ) {
try { try {
const { id } = request.params; const { id } = request.params;
const { force } = request.body;
const { teamId } = request.user; const { teamId } = request.user;
const application = await prisma.application.findUnique({ const application = await prisma.application.findUnique({
where: { id }, where: { id },
include: { destinationDocker: true } include: { destinationDocker: true, teams: true }
}); });
if (!force && application?.destinationDockerId && application.destinationDocker?.network) { if (teamId !== '0' && !application.teams.some((team) => team.id === teamId)) {
throw { status: 403, message: 'You are not allowed to delete this application.' };
}
if (application?.destinationDocker?.id && application.destinationDocker?.network) {
const { stdout: containers } = await executeCommand({ const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id, dockerId: application.destinationDocker.id,
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'` command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
@@ -1340,16 +1345,16 @@ export async function getStorages(request: FastifyRequest<OnlyId>) {
export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) { export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { path, newStorage, storageId } = request.body; const { hostPath, path, newStorage, storageId } = request.body;
if (newStorage) { if (newStorage) {
await prisma.applicationPersistentStorage.create({ await prisma.applicationPersistentStorage.create({
data: { path, application: { connect: { id } } } data: { hostPath, path, application: { connect: { id } } }
}); });
} else { } else {
await prisma.applicationPersistentStorage.update({ await prisma.applicationPersistentStorage.update({
where: { id: storageId }, where: { id: storageId },
data: { path } data: { hostPath, path }
}); });
} }
return reply.code(201).send(); return reply.code(201).send();
@@ -1376,7 +1381,7 @@ export async function restartPreview(
try { try {
const { id, pullmergeRequestId } = request.params; const { id, pullmergeRequestId } = request.params;
const { teamId } = request.user; const { teamId } = request.user;
let application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) { if (application?.destinationDockerId) {
const buildId = cuid(); const buildId = cuid();
const { id: dockerId, network } = application.destinationDocker; const { id: dockerId, network } = application.destinationDocker;
@@ -1427,9 +1432,8 @@ export async function restartPreview(
const volumes = const volumes =
persistentStorage?.map((storage) => { persistentStorage?.map((storage) => {
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ return `${applicationId}${storage.path.replace(/\//gi, '-')}:${buildPack !== 'docker' ? '/app' : ''
buildPack !== 'docker' ? '/app' : '' }${storage.path}`;
}${storage.path}`;
}) || []; }) || [];
const composeVolumes = volumes.map((volume) => { const composeVolumes = volumes.map((volume) => {
return { return {
@@ -1691,7 +1695,7 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
try { try {
await fs.stat(file); await fs.stat(file);
} catch (error) { } catch (error) {
let logs = await prisma.buildLog.findMany({ const logs = await prisma.buildLog.findMany({
where: { buildId, time: { gt: sequence } }, where: { buildId, time: { gt: sequence } },
orderBy: { time: 'asc' } orderBy: { time: 'asc' }
}); });
@@ -1707,9 +1711,9 @@ export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
status: data?.status || 'queued' status: data?.status || 'queued'
}; };
} }
let fileLogs = (await fs.readFile(file)).toString(); const fileLogs = (await fs.readFile(file)).toString();
let decryptedLogs = await csv({ noheader: true }).fromString(fileLogs); const decryptedLogs = await csv({ noheader: true }).fromString(fileLogs);
let logs = decryptedLogs const logs = decryptedLogs
.map((log) => { .map((log) => {
const parsed = { const parsed = {
time: log['field1'], time: log['field1'],

View File

@@ -28,6 +28,8 @@ export interface SaveApplication extends OnlyId {
dockerComposeConfiguration: string; dockerComposeConfiguration: string;
simpleDockerfile: string; simpleDockerfile: string;
dockerRegistryImageName: string; dockerRegistryImageName: string;
basicAuthPw: string;
basicAuthUser: string;
}; };
} }
export interface SaveApplicationSettings extends OnlyId { export interface SaveApplicationSettings extends OnlyId {
@@ -43,6 +45,7 @@ export interface SaveApplicationSettings extends OnlyId {
isDBBranching: boolean; isDBBranching: boolean;
isCustomSSL: boolean; isCustomSSL: boolean;
isHttp2: boolean; isHttp2: boolean;
basicAuth: boolean;
}; };
} }
export interface DeleteApplication extends OnlyId { export interface DeleteApplication extends OnlyId {
@@ -96,6 +99,7 @@ export interface DeleteSecret extends OnlyId {
} }
export interface SaveStorage extends OnlyId { export interface SaveStorage extends OnlyId {
Body: { Body: {
hostPath?: string;
path: string; path: string;
newStorage: boolean; newStorage: boolean;
storageId: string; storageId: string;

View File

@@ -20,7 +20,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true', whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON, whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,
isRegistrationEnabled: settings.isRegistrationEnabled, isRegistrationEnabled: settings.isRegistrationEnabled,
isARM: isARM(process.arch) isARM: isARM()
}; };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }); return errorHandler({ status, message });

View File

@@ -5,6 +5,7 @@ import yaml from 'js-yaml';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { import {
ComposeFile, ComposeFile,
backupDatabaseNow,
createDirectories, createDirectories,
decrypt, decrypt,
defaultComposeConfiguration, defaultComposeConfiguration,
@@ -302,7 +303,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
databaseSecret databaseSecret
} = database; } = database;
const { privatePort, command, environmentVariables, image, volume, ulimits } = const { privatePort, command, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database, arch); generateDatabaseConfiguration(database);
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const volumeName = volume.split(':')[0]; const volumeName = volume.split(':')[0];
@@ -351,6 +352,21 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
return errorHandler({ status, message }); return errorHandler({ status, message });
} }
} }
export async function backupDatabase(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
return await backupDatabaseNow(database, reply);
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function stopDatabase(request: FastifyRequest<OnlyId>) { export async function stopDatabase(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
@@ -427,19 +443,15 @@ export async function deleteDatabase(request: FastifyRequest<DeleteDatabase>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
const { force } = request.body;
const database = await prisma.database.findFirst({ const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true } include: { destinationDocker: true, settings: true }
}); });
if (!force) { if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); if (database.destinationDockerId) {
if (database.destinationDockerId) { const everStarted = await stopDatabaseContainer(database);
const everStarted = await stopDatabaseContainer(database); if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
}
} }
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } }); await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } }); await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });

View File

@@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; import { backupDatabase, cleanupUnconfiguredDatabases, deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@@ -39,6 +39,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request)); fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request)); fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
fastify.post<OnlyId>('/:id/backup', async (request, reply) => await backupDatabase(request, reply));
}; };
export default root; export default root;

View File

@@ -4,7 +4,7 @@ export interface SaveDatabaseType extends OnlyId {
Body: { type: string } Body: { type: string }
} }
export interface DeleteDatabase extends OnlyId { export interface DeleteDatabase extends OnlyId {
Body: { force: string } Body: { }
} }
export interface SaveVersion extends OnlyId { export interface SaveVersion extends OnlyId {
Body: { Body: {

View File

@@ -1,279 +1,384 @@
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import sshConfig from 'ssh-config' import {
import fs from 'fs/promises' errorHandler,
import os from 'os'; executeCommand,
listSettings,
import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; prisma,
startTraefikProxy,
stopTraefikProxy
} from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker'; import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types'; import type {
CheckDestination,
ListDestinations,
NewDestination,
Proxy,
SaveDestinationSettings
} from './types';
import { removeService } from '../../../../lib/services/common';
export async function listDestinations(request: FastifyRequest<ListDestinations>) { export async function listDestinations(request: FastifyRequest<ListDestinations>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { onlyVerified = false } = request.query const { onlyVerified = false } = request.query;
let destinations = [] let destinations = [];
if (teamId === '0') { if (teamId === '0') {
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } }); destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
} else { } else {
destinations = await prisma.destinationDocker.findMany({ destinations = await prisma.destinationDocker.findMany({
where: { teams: { some: { id: teamId } } }, where: { teams: { some: { id: teamId } } },
include: { teams: true } include: { teams: true }
}); });
} }
if (onlyVerified) { if (onlyVerified) {
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified)) destinations = destinations.filter(
} (destination) =>
return { destination.engine || (destination.remoteEngine && destination.remoteVerified)
destinations );
} }
} catch ({ status, message }) { return {
return errorHandler({ status, message }) destinations
} };
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function checkDestination(request: FastifyRequest<CheckDestination>) { export async function checkDestination(request: FastifyRequest<CheckDestination>) {
try { try {
const { network } = request.body; const { network } = request.body;
const found = await prisma.destinationDocker.findFirst({ where: { network } }); const found = await prisma.destinationDocker.findFirst({ where: { network } });
if (found) { if (found) {
throw { throw {
message: `Network already exists: ${network}` message: `Network already exists: ${network}`
}; };
} }
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getDestination(request: FastifyRequest<OnlyId>) { export async function getDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params;
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({ const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { sshKey: true, application: true, service: true, database: true } include: { sshKey: true, application: true, service: true, database: true }
}); });
if (!destination && id !== 'new') { if (!destination && id !== 'new') {
throw { status: 404, message: `Destination not found.` }; throw { status: 404, message: `Destination not found.` };
} }
const settings = await listSettings(); const settings = await listSettings();
const payload = { const payload = {
destination, destination,
settings settings
}; };
return { return {
...payload ...payload
}; };
} catch ({ status, message }) {
} catch ({ status, message }) { return errorHandler({ status, message });
return errorHandler({ status, message }) }
}
} }
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) { export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params const { id } = request.params;
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } =
if (id === 'new') { request.body;
if (engine) { if (id === 'new') {
const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` }); if (engine) {
if (stdout === '') { const { stdout } = await await executeCommand({
await await executeCommand({ command: `docker network create --attachable ${network}` }); command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'`
} });
await prisma.destinationDocker.create({ if (stdout === '') {
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed } await await executeCommand({ command: `docker network create --attachable ${network}` });
}); }
const destinations = await prisma.destinationDocker.findMany({ where: { engine } }); await prisma.destinationDocker.create({
const destination = destinations.find((destination) => destination.network === network); data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
if (destinations.length > 0) { });
const proxyConfigured = destinations.find( const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true const destination = destinations.find((destination) => destination.network === network);
); if (destinations.length > 0) {
if (proxyConfigured) { const proxyConfigured = destinations.find(
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed; (destination) =>
} destination.network !== network && destination.isCoolifyProxyUsed === true
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } }); );
} if (proxyConfigured) {
if (isCoolifyProxyUsed) { isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
await startTraefikProxy(destination.id); }
} await prisma.destinationDocker.updateMany({
return reply.code(201).send({ id: destination.id }); where: { engine },
} else { data: { isCoolifyProxyUsed }
const destination = await prisma.destinationDocker.create({ });
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort: Number(remotePort) } }
}); if (isCoolifyProxyUsed) {
return reply.code(201).send({ id: destination.id }) await startTraefikProxy(destination.id);
} }
} else { return reply.code(201).send({ id: destination.id });
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); } else {
return reply.code(201).send(); const destination = await prisma.destinationDocker.create({
} data: {
name,
} catch ({ status, message }) { teams: { connect: { id: teamId } },
return errorHandler({ status, message }) engine,
} network,
isCoolifyProxyUsed,
remoteEngine: true,
remoteIpAddress,
remoteUser,
remotePort: Number(remotePort)
}
});
return reply.code(201).send({ id: destination.id });
}
} else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send();
}
} catch ({ status, message }) {
return errorHandler({ status, message });
}
}
export async function forceDeleteDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params;
const services = await prisma.service.findMany({ where: { destinationDockerId: id } });
for (const service of services) {
await removeService({ id: service.id });
}
const applications = await prisma.application.findMany({ where: { destinationDockerId: id } });
for (const application of applications) {
await prisma.applicationSettings.deleteMany({ where: { application: { id: application.id } } });
await prisma.buildLog.deleteMany({ where: { applicationId: application.id } });
await prisma.build.deleteMany({ where: { applicationId: application.id } });
await prisma.secret.deleteMany({ where: { applicationId: application.id } });
await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: application.id } });
await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: application.id } });
await prisma.previewApplication.deleteMany({ where: { applicationId: application.id } });
}
const databases = await prisma.database.findMany({ where: { destinationDockerId: id } });
for (const database of databases) {
await prisma.databaseSettings.deleteMany({ where: { databaseId: database.id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: database.id } });
await prisma.database.delete({ where: { id: database.id } });
}
await prisma.destinationDocker.delete({ where: { id } });
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function deleteDestination(request: FastifyRequest<OnlyId>) { export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params;
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } }); const appFound = await prisma.application.findFirst({ where: { destinationDockerId: id } });
if (isCoolifyProxyUsed) { const serviceFound = await prisma.service.findFirst({ where: { destinationDockerId: id } });
if (engine || remoteVerified) { const databaseFound = await prisma.database.findFirst({ where: { destinationDockerId: id } });
const { stdout: found } = await executeCommand({ if (appFound || serviceFound || databaseFound) {
dockerId: id, throw {
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'` message: `Destination is in use.<br>Remove all applications, services and databases using this destination first.`
}) };
if (found) { }
await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` }) const { network, remoteVerified, engine, isCoolifyProxyUsed } =
await executeCommand({ dockerId: id, command: `docker network rm ${network}` }) await prisma.destinationDocker.findUnique({ where: { id } });
} if (isCoolifyProxyUsed) {
} if (engine || remoteVerified) {
} const { stdout: found } = await executeCommand({
await prisma.destinationDocker.delete({ where: { id } }); dockerId: id,
return {} command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
} catch ({ status, message }) { });
return errorHandler({ status, message }) if (found) {
} await executeCommand({
dockerId: id,
command: `docker network disconnect ${network} coolify-proxy`
});
await executeCommand({ dockerId: id, command: `docker network rm ${network}` });
}
}
}
await prisma.destinationDocker.delete({ where: { id } });
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) { export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
try { try {
const { engine, isCoolifyProxyUsed } = request.body; const { engine, isCoolifyProxyUsed } = request.body;
await prisma.destinationDocker.updateMany({ await prisma.destinationDocker.updateMany({
where: { engine }, where: { engine },
data: { isCoolifyProxyUsed } data: { isCoolifyProxyUsed }
}); });
return { return {
status: 202 status: 202
} };
// return reply.code(201).send(); // return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function startProxy(request: FastifyRequest<Proxy>) { export async function startProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params const { id } = request.params;
try { try {
await startTraefikProxy(id); await startTraefikProxy(id);
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
await stopTraefikProxy(id); await stopTraefikProxy(id);
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function stopProxy(request: FastifyRequest<Proxy>) { export async function stopProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params const { id } = request.params;
try { try {
await stopTraefikProxy(id); await stopTraefikProxy(id);
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function restartProxy(request: FastifyRequest<Proxy>) { export async function restartProxy(request: FastifyRequest<Proxy>) {
const { id } = request.params const { id } = request.params;
try { try {
await stopTraefikProxy(id); await stopTraefikProxy(id);
await startTraefikProxy(id); await startTraefikProxy(id);
await prisma.destinationDocker.update({ await prisma.destinationDocker.update({
where: { id }, where: { id },
data: { isCoolifyProxyUsed: true } data: { isCoolifyProxyUsed: true }
}); });
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
await prisma.destinationDocker.update({ await prisma.destinationDocker.update({
where: { id }, where: { id },
data: { isCoolifyProxyUsed: false } data: { isCoolifyProxyUsed: false }
}); });
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function assignSSHKey(request: FastifyRequest) { export async function assignSSHKey(request: FastifyRequest) {
try { try {
const { id: sshKeyId } = request.body; const { id: sshKeyId } = request.body;
const { id } = request.params; const { id } = request.params;
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } }) await prisma.destinationDocker.update({
return {} where: { id },
} catch ({ status, message }) { data: { sshKey: { connect: { id: sshKeyId } } }
return errorHandler({ status, message }) });
} return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function verifyRemoteDockerEngineFn(id: string) { export async function verifyRemoteDockerEngineFn(id: string) {
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } }) const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst(
const daemonJson = `daemon-${id}.json` { where: { id } }
try { );
await executeCommand({ sshCommand: true, command: `docker network inspect ${network}`, dockerId: id }); const daemonJson = `daemon-${id}.json`;
} catch (error) { try {
await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id }); await executeCommand({
} sshCommand: true,
command: `docker network inspect ${network}`,
dockerId: id
});
} catch (error) {
await executeCommand({
command: `docker network create --attachable ${network}`,
dockerId: id
});
}
try { try {
await executeCommand({ sshCommand: true, command: `docker network inspect coolify-infra`, dockerId: id }); await executeCommand({
} catch (error) { sshCommand: true,
await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id }); command: `docker network inspect coolify-infra`,
} dockerId: id
});
} catch (error) {
await executeCommand({
command: `docker network create --attachable coolify-infra`,
dockerId: id
});
}
if (isCoolifyProxyUsed) await startTraefikProxy(id); if (isCoolifyProxyUsed) await startTraefikProxy(id);
let isUpdated = false; let isUpdated = false;
let daemonJsonParsed = { let daemonJsonParsed = {
"live-restore": true, 'live-restore': true,
"features": { features: {
"buildkit": true buildkit: true
} }
}; };
try { try {
const { stdout: daemonJson } = await executeCommand({ sshCommand: true, dockerId: id, command: `cat /etc/docker/daemon.json` }); const { stdout: daemonJson } = await executeCommand({
daemonJsonParsed = JSON.parse(daemonJson); sshCommand: true,
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) { dockerId: id,
isUpdated = true; command: `cat /etc/docker/daemon.json`
daemonJsonParsed['live-restore'] = true });
daemonJsonParsed = JSON.parse(daemonJson);
} if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
if (!daemonJsonParsed?.features?.buildkit) { isUpdated = true;
isUpdated = true; daemonJsonParsed['live-restore'] = true;
daemonJsonParsed.features = { }
buildkit: true if (!daemonJsonParsed?.features?.buildkit) {
} isUpdated = true;
} daemonJsonParsed.features = {
} catch (error) { buildkit: true
isUpdated = true; };
} }
try { } catch (error) {
if (isUpdated) { isUpdated = true;
await executeCommand({ shell: true, command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}` }) }
await executeCommand({ dockerId: id, command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json` }); try {
await executeCommand({ command: `rm /tmp/${daemonJson}` }) if (isUpdated) {
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` }); await executeCommand({
} shell: true,
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } }) command: `echo '${JSON.stringify(daemonJsonParsed, null, 2)}' > /tmp/${daemonJson}`
} catch (error) { });
throw new Error('Error while verifying remote docker engine') await executeCommand({
} dockerId: id,
command: `scp /tmp/${daemonJson} ${remoteIpAddress}-remote:/etc/docker/daemon.json`
});
await executeCommand({ command: `rm /tmp/${daemonJson}` });
await executeCommand({ sshCommand: true, dockerId: id, command: `systemctl restart docker` });
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } });
} catch (error) {
console.log(error)
throw new Error('Error while verifying remote docker engine');
}
} }
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) { export async function verifyRemoteDockerEngine(
const { id } = request.params; request: FastifyRequest<OnlyId>,
try { reply: FastifyReply
await verifyRemoteDockerEngineFn(id); ) {
return reply.code(201).send() const { id } = request.params;
} catch ({ status, message }) { try {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } }) await verifyRemoteDockerEngineFn(id);
return errorHandler({ status, message }) return reply.code(201).send();
} } catch ({ status, message }) {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } });
return errorHandler({ status, message });
}
} }
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) { export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params;
const destination = await prisma.destinationDocker.findUnique({ where: { id } }) const destination = await prisma.destinationDocker.findUnique({ where: { id } });
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true }) const { found: isRunning } = await checkContainer({
return { dockerId: destination.id,
isRunning container: 'coolify-proxy',
} remove: true
} catch ({ status, message }) { });
return errorHandler({ status, message }) return {
} isRunning
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }

View File

@@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers'; import { assignSSHKey, checkDestination, deleteDestination, forceDeleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types'; import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
@@ -14,6 +14,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request)); fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply)); fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
fastify.delete<OnlyId>('/:id/force', async (request) => await forceDeleteDestination(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request)); fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request)); fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));

View File

@@ -12,7 +12,6 @@ import {
prisma, prisma,
uniqueName, uniqueName,
version, version,
sentryDSN,
executeCommand executeCommand
} from '../../../lib/common'; } from '../../../lib/common';
import { scheduler } from '../../../lib/scheduler'; import { scheduler } from '../../../lib/scheduler';
@@ -20,8 +19,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import type { Login, Update } from '.'; import type { Login, Update } from '.';
import type { GetCurrentUser } from './types'; import type { GetCurrentUser } from './types';
export async function hashPassword(password: string): Promise<string> { export async function hashPassword(password: string, saltRounds = 15): Promise<string> {
const saltRounds = 15;
return bcrypt.hash(password, saltRounds); return bcrypt.hash(password, saltRounds);
} }
@@ -59,7 +57,7 @@ export async function cleanupManually(request: FastifyRequest) {
const destination = await prisma.destinationDocker.findUnique({ const destination = await prisma.destinationDocker.findUnique({
where: { id: serverId } where: { id: serverId }
}); });
await cleanupDockerStorage(destination.id, true, true); await cleanupDockerStorage(destination.id, true);
return {}; return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }); return errorHandler({ status, message });
@@ -78,7 +76,7 @@ export async function refreshTags() {
tags = JSON.parse(tags).concat(JSON.parse(testTags)); tags = JSON.parse(tags).concat(JSON.parse(testTags));
} }
} }
} catch (error) {} } catch (error) { }
await fs.writeFile('./tags.json', tags); await fs.writeFile('./tags.json', tags);
} else { } else {
const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text(); const tags = await got.get('https://get.coollabs.io/coolify/service-tags.json').text();
@@ -103,7 +101,7 @@ export async function refreshTemplates() {
if (await fs.stat('./testTemplate.yaml')) { if (await fs.stat('./testTemplate.yaml')) {
templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8')); templates = templates + (await fs.readFile('./testTemplate.yaml', 'utf8'));
} }
} catch (error) {} } catch (error) { }
const response = await fs.readFile('./devTemplates.yaml', 'utf8'); const response = await fs.readFile('./devTemplates.yaml', 'utf8');
await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response))); await fs.writeFile('./templates.json', JSON.stringify(yaml.load(response)));
} else { } else {
@@ -156,14 +154,21 @@ export async function update(request: FastifyRequest<Update>) {
try { try {
if (!isDev) { if (!isDev) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` }); let image = `ghcr.io/coollabsio/coolify:${latestVersion}`;
await executeCommand({ shell: true, command: `env | grep COOLIFY > .env` }); try {
await executeCommand({ command: `docker pull ${image}` });
} catch (error) {
image = `coollabsio/coolify:${latestVersion}`;
await executeCommand({ command: `docker pull ${image}` });
}
await executeCommand({ shell: true, command: `ls .env || env | grep "^COOLIFY" | sort > .env` });
await executeCommand({ await executeCommand({
command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env`
}); });
await executeCommand({ await executeCommand({
shell: true, shell: true,
command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db ${image} /bin/sh -c "env | grep "^COOLIFY" | sort > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
}); });
return {}; return {};
} else { } else {
@@ -445,7 +450,6 @@ export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fa
}); });
return { return {
settings: await prisma.setting.findUnique({ where: { id: '0' } }), settings: await prisma.setting.findUnique({ where: { id: '0' } }),
sentryDSN,
pendingInvitations, pendingInvitations,
token, token,
...request.user ...request.user

View File

@@ -50,6 +50,7 @@ import type {
SetWordpressSettings SetWordpressSettings
} from './types'; } from './types';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import { refreshTags, refreshTemplates } from '../handlers';
export async function listServices(request: FastifyRequest) { export async function listServices(request: FastifyRequest) {
try { try {
@@ -412,22 +413,23 @@ export async function saveServiceType(
if (foundTemplate.variables) { if (foundTemplate.variables) {
if (foundTemplate.variables.length > 0) { if (foundTemplate.variables.length > 0) {
for (const variable of foundTemplate.variables) { for (const variable of foundTemplate.variables) {
const { defaultValue } = variable; let { defaultValue } = variable;
defaultValue = defaultValue.toString();
const regex = /^\$\$.*\((\d+)\)$/g; const regex = /^\$\$.*\((\d+)\)$/g;
const length = Number(regex.exec(defaultValue)?.[1]) || undefined; const length = Number(regex.exec(defaultValue)?.[1]) || undefined;
if (variable.defaultValue.startsWith('$$generate_password')) { if (defaultValue.startsWith('$$generate_password')) {
variable.value = generatePassword({ length }); variable.value = generatePassword({ length });
} else if (variable.defaultValue.startsWith('$$generate_hex')) { } else if (defaultValue.startsWith('$$generate_hex')) {
variable.value = generatePassword({ length, isHex: true }); variable.value = generatePassword({ length, isHex: true });
} else if (variable.defaultValue.startsWith('$$generate_username')) { } else if (defaultValue.startsWith('$$generate_username')) {
variable.value = cuid(); variable.value = cuid();
} else if (variable.defaultValue.startsWith('$$generate_token')) { } else if (defaultValue.startsWith('$$generate_token')) {
variable.value = generateToken(); variable.value = generateToken();
} else { } else {
variable.value = variable.defaultValue || ''; variable.value = defaultValue || '';
} }
const foundVariableSomewhereElse = foundTemplate.variables.find((v) => const foundVariableSomewhereElse = foundTemplate.variables.find((v) =>
v.defaultValue.includes(variable.id) v.defaultValue.toString().includes(variable.id)
); );
if (foundVariableSomewhereElse) { if (foundVariableSomewhereElse) {
foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll( foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(
@@ -475,7 +477,7 @@ export async function saveServiceType(
const [volumeName, path] = volume.split(':'); const [volumeName, path] = volume.split(':');
if (!volumeName.startsWith('/')) { if (!volumeName.startsWith('/')) {
const found = await prisma.servicePersistentStorage.findFirst({ const found = await prisma.servicePersistentStorage.findFirst({
where: { volumeName, serviceId: id } where: { volumeName, serviceId: id, path }
}); });
if (!found) { if (!found) {
await prisma.servicePersistentStorage.create({ await prisma.servicePersistentStorage.create({
@@ -617,6 +619,29 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
export async function deleteService(request: FastifyRequest<OnlyId>) { export async function deleteService(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId;
const { destinationDockerId } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) {
const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}`
});
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({
dockerId: destinationDockerId,
command: `docker stop -t 0 ${container}`
});
await executeCommand({
dockerId: destinationDockerId,
command: `docker rm --force ${container}`
});
}
}
}
}
await removeService({ id }); await removeService({ id });
return {}; return {};
} catch ({ status, message }) { } catch ({ status, message }) {
@@ -723,7 +748,10 @@ export async function saveService(request: FastifyRequest<SaveService>, reply: F
let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting; let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting;
if (value) { if (value) {
if (changed) { if (changed) {
await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } }); await prisma.serviceSetting.update({
where: { id: settingId },
data: { value: value.replace(/\n/, '\\n') }
});
} }
if (isNew) { if (isNew) {
if (!variableName) { if (!variableName) {
@@ -958,11 +986,22 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId }); const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) { if (destinationDockerId) {
await executeCommand({ const logTables = await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`, command: `docker exec ${id}-clickhouse clickhouse-client -q "SELECT name FROM system.tables;"`,
shell: true shell: false
}); });
if (logTables.stdout !== '') {
const tables = logTables.stdout.split('\n').filter((t) => t.includes('_log'));
for (const table of tables) {
console.log(`Truncating table ${table}`)
await executeCommand({
dockerId: destinationDocker.id,
command: `docker exec ${id}-clickhouse clickhouse-client -q "TRUNCATE TABLE system.${table};"`,
shell: false
});
}
}
return await reply.code(201).send(); return await reply.code(201).send();
} }
throw { status: 500, message: 'Could cleanup logs.' }; throw { status: 500, message: 'Could cleanup logs.' };
@@ -1078,17 +1117,14 @@ export async function activateWordpressFtp(
shell: true shell: true
}); });
} }
} catch (error) {} } catch (error) { }
const volumes = [ const volumes = [
`${id}-wordpress-data:/home/${ftpUser}/wordpress`, `${id}-wordpress-data:/home/${ftpUser}/wordpress`,
`${ `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`,
`${ `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`,
`${ `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys'
}/${id}.sh:/etc/sftp.d/chmod.sh` }/${id}.sh:/etc/sftp.d/chmod.sh`
]; ];
@@ -1158,6 +1194,6 @@ export async function activateWordpressFtp(
await executeCommand({ await executeCommand({
command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh`
}); });
} catch (error) {} } catch (error) { }
} }
} }

View File

@@ -1,235 +1,312 @@
import { promises as dns } from 'dns'; import { promises as dns } from 'dns';
import { X509Certificate } from 'node:crypto'; import { X509Certificate } from 'node:crypto';
import * as Sentry from '@sentry/node';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common'; import {
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types'; checkDomainsIsValidInDNS,
decrypt,
encrypt,
errorHandler,
executeCommand,
getDomain,
isDev,
isDNSValid,
isDomainConfigured,
listSettings,
prisma
} from '../../../../lib/common';
import {
AddDefaultRegistry,
CheckDNS,
CheckDomain,
DeleteDomain,
OnlyIdInBody,
SaveSettings,
SaveSSHKey,
SetDefaultRegistry
} from './types';
export async function listAllSettings(request: FastifyRequest) { export async function listAllSettings(request: FastifyRequest) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const settings = await listSettings(); const settings = await listSettings();
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } }) const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } });
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } }) let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } });
registries = registries.map((registry) => { registries = registries.map((registry) => {
if (registry.password) { if (registry.password) {
registry.password = decrypt(registry.password) registry.password = decrypt(registry.password);
} }
return registry return registry;
}) });
const unencryptedKeys = [] const unencryptedKeys = [];
if (sshKeys.length > 0) { if (sshKeys.length > 0) {
for (const key of sshKeys) { for (const key of sshKeys) {
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt }) unencryptedKeys.push({
} id: key.id,
} name: key.name,
const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } }) privateKey: decrypt(key.privateKey),
let cns = []; createdAt: key.createdAt
for (const certificate of certificates) { });
const x509 = new X509Certificate(certificate.cert); }
cns.push({ commonName: x509.subject.split('\n').find((s) => s.startsWith('CN=')).replace('CN=', ''), id: certificate.id, createdAt: certificate.createdAt }) }
} const certificates = await prisma.certificate.findMany({ where: { team: { id: teamId } } });
let cns = [];
for (const certificate of certificates) {
const x509 = new X509Certificate(certificate.cert);
cns.push({
commonName: x509.subject
.split('\n')
.find((s) => s.startsWith('CN='))
.replace('CN=', ''),
id: certificate.id,
createdAt: certificate.createdAt
});
}
return { return {
settings, settings,
certificates: cns, certificates: cns,
sshKeys: unencryptedKeys, sshKeys: unencryptedKeys,
registries registries
} };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) { export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
try { try {
let { let {
previewSeparator, previewSeparator,
numberOfDockerImagesKeptLocally, numberOfDockerImagesKeptLocally,
doNotTrack, doNotTrack,
fqdn, fqdn,
isAPIDebuggingEnabled, isAPIDebuggingEnabled,
isRegistrationEnabled, isRegistrationEnabled,
dualCerts, dualCerts,
minPort, minPort,
maxPort, maxPort,
isAutoUpdateEnabled, isAutoUpdateEnabled,
isDNSCheckEnabled, isDNSCheckEnabled,
DNSServers, DNSServers,
proxyDefaultRedirect proxyDefaultRedirect
} = request.body } = request.body;
const { id, previewSeparator: SetPreviewSeparator } = await listSettings(); const { id, previewSeparator: SetPreviewSeparator } = await listSettings();
if (numberOfDockerImagesKeptLocally) { if (numberOfDockerImagesKeptLocally) {
numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally) numberOfDockerImagesKeptLocally = Number(numberOfDockerImagesKeptLocally);
} }
if (previewSeparator == '') { if (previewSeparator == '') {
previewSeparator = '.' previewSeparator = '.';
} }
if (SetPreviewSeparator != previewSeparator) { if (SetPreviewSeparator != previewSeparator) {
const applications = await prisma.application.findMany({ where: { previewApplication: { some: { id: { not: undefined } } } }, include: { previewApplication: true } }) const applications = await prisma.application.findMany({
for (const application of applications) { where: { previewApplication: { some: { id: { not: undefined } } } },
for (const preview of application.previewApplication) { include: { previewApplication: true }
const { protocol } = new URL(preview.customDomain) });
const { pullmergeRequestId } = preview for (const application of applications) {
const { fqdn } = application for (const preview of application.previewApplication) {
const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(fqdn)}` const { protocol } = new URL(preview.customDomain);
await prisma.previewApplication.update({ where: { id: preview.id }, data: { customDomain: newPreviewDomain } }) const { pullmergeRequestId } = preview;
} const { fqdn } = application;
} const newPreviewDomain = `${protocol}//${pullmergeRequestId}${previewSeparator}${getDomain(
} fqdn
)}`;
await prisma.previewApplication.update({
where: { id: preview.id },
data: { customDomain: newPreviewDomain }
});
}
}
}
await prisma.setting.update({ await prisma.setting.update({
where: { id }, where: { id },
data: { previewSeparator, numberOfDockerImagesKeptLocally, doNotTrack, isRegistrationEnabled, dualCerts, isAutoUpdateEnabled, isDNSCheckEnabled, DNSServers, isAPIDebuggingEnabled } data: {
}); previewSeparator,
if (fqdn) { numberOfDockerImagesKeptLocally,
await prisma.setting.update({ where: { id }, data: { fqdn } }); doNotTrack,
} isRegistrationEnabled,
await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } }); dualCerts,
if (minPort && maxPort) { isAutoUpdateEnabled,
await prisma.setting.update({ where: { id }, data: { minPort, maxPort } }); isDNSCheckEnabled,
} DNSServers,
if (doNotTrack === false) { isAPIDebuggingEnabled
// Sentry.init({ }
// dsn: sentryDSN, });
// environment: isDev ? 'development' : 'production', if (fqdn) {
// release: version await prisma.setting.update({ where: { id }, data: { fqdn } });
// }); }
// console.log('Sentry initialized') await prisma.setting.update({ where: { id }, data: { proxyDefaultRedirect } });
} if (minPort && maxPort) {
return reply.code(201).send() await prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
} catch ({ status, message }) { }
return errorHandler({ status, message }) if (doNotTrack === false) {
} // Sentry.init({
// dsn: sentryDSN,
// environment: isDev ? 'development' : 'production',
// release: version
// });
// console.log('Sentry initialized')
}
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) { export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
try { try {
const { fqdn } = request.body const { fqdn } = request.body;
const { DNSServers } = await listSettings(); const { DNSServers } = await listSettings();
if (DNSServers) { if (DNSServers) {
dns.setServers([...DNSServers.split(',')]); dns.setServers([...DNSServers.split(',')]);
} }
let ip; let ip;
try { try {
ip = await dns.resolve(fqdn); ip = await dns.resolve(fqdn);
} catch (error) { } catch (error) {
// Do not care. // Do not care.
} }
await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } }); await prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined) return reply.redirect(302, ip ? `http://${ip[0]}:3000/settings` : undefined);
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function checkDomain(request: FastifyRequest<CheckDomain>) { export async function checkDomain(request: FastifyRequest<CheckDomain>) {
try { try {
const { id } = request.params; const { id } = request.params;
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body;
if (fqdn) fqdn = fqdn.toLowerCase(); if (fqdn) fqdn = fqdn.toLowerCase();
const found = await isDomainConfigured({ id, fqdn }); const found = await isDomainConfigured({ id, fqdn });
if (found) { if (found) {
throw { message: "Domain already configured" }; throw { message: 'Domain already configured' };
} }
if (isDNSCheckEnabled && !forceSave && !isDev) { if (isDNSCheckEnabled && !forceSave && !isDev) {
const hostname = request.hostname.split(':')[0] const hostname = request.hostname.split(':')[0];
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }); return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
} }
return {}; return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function checkDNS(request: FastifyRequest<CheckDNS>) { export async function checkDNS(request: FastifyRequest<CheckDNS>) {
try { try {
const { domain } = request.params; const { domain } = request.params;
await isDNSValid(request.hostname, domain); await isDNSValid(request.hostname, domain);
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) { export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { privateKey, name } = request.body; const { privateKey, name } = request.body;
const found = await prisma.sshKey.findMany({ where: { name } }) const found = await prisma.sshKey.findMany({ where: { name } });
if (found.length > 0) { if (found.length > 0) {
throw { throw {
message: "Name already used. Choose another one please." message: 'Name already used. Choose another one please.'
} };
} }
const encryptedSSHKey = encrypt(privateKey) const encryptedSSHKey = encrypt(privateKey);
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } }) await prisma.sshKey.create({
return reply.code(201).send() data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } }
} catch ({ status, message }) { });
return errorHandler({ status, message }) return reply.code(201).send();
} } catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) { export async function deleteSSHKey(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.body; const { id } = request.body;
await prisma.sshKey.deleteMany({ where: { id, teamId } }) await prisma.sshKey.deleteMany({ where: { id, teamId } });
return reply.code(201).send() return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) { export async function deleteCertificates(
try { request: FastifyRequest<OnlyIdInBody>,
const teamId = request.user.teamId; reply: FastifyReply
const { id } = request.body; ) {
await executeCommand({ command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`, shell: true }) try {
await prisma.certificate.deleteMany({ where: { id, teamId } }) const teamId = request.user.teamId;
return reply.code(201).send() const { id } = request.body;
} catch ({ status, message }) { await executeCommand({
return errorHandler({ status, message }) command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`,
} shell: true
});
await prisma.certificate.deleteMany({ where: { id, teamId } });
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegistry>, reply: FastifyReply) { export async function setDockerRegistry(
try { request: FastifyRequest<SetDefaultRegistry>,
const teamId = request.user.teamId; reply: FastifyReply
const { id, username, password } = request.body; ) {
try {
const teamId = request.user.teamId;
const { id, username, password } = request.body;
let encryptedPassword = '' let encryptedPassword = '';
if (password) encryptedPassword = encrypt(password) if (password) encryptedPassword = encrypt(password);
if (teamId === '0') { if (teamId === '0') {
await prisma.dockerRegistry.update({ where: { id }, data: { username, password: encryptedPassword } }) await prisma.dockerRegistry.update({
} else { where: { id },
await prisma.dockerRegistry.updateMany({ where: { id, teamId }, data: { username, password: encryptedPassword } }) data: { username, password: encryptedPassword }
} });
return reply.code(201).send() } else {
} catch ({ status, message }) { await prisma.dockerRegistry.updateMany({
return errorHandler({ status, message }) where: { id, teamId },
} data: { username, password: encryptedPassword }
});
}
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) { export async function addDockerRegistry(
try { request: FastifyRequest<AddDefaultRegistry>,
const teamId = request.user.teamId; reply: FastifyReply
const { name, url, username, password } = request.body; ) {
try {
const teamId = request.user.teamId;
const { name, url, username, password } = request.body;
let encryptedPassword = '' let encryptedPassword = '';
if (password) encryptedPassword = encrypt(password) if (password) encryptedPassword = encrypt(password);
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } }) await prisma.dockerRegistry.create({
data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } }
});
return reply.code(201).send() return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
}
export async function deleteDockerRegistry(
request: FastifyRequest<OnlyIdInBody>,
reply: FastifyReply
) {
try {
const teamId = request.user.teamId;
const { id } = request.body;
await prisma.application.updateMany({
where: { dockerRegistryId: id },
data: { dockerRegistryId: null }
});
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } });
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.body;
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -1,191 +1,231 @@
import cuid from 'cuid'; import cuid from 'cuid';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common'; import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
import { OnlyId } from '../../../../types'; import { OnlyId } from '../../../../types';
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types'; import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
export async function listSources(request: FastifyRequest) { export async function listSources(request: FastifyRequest) {
try { try {
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const sources = await prisma.gitSource.findMany({ const sources = await prisma.gitSource.findMany({
where: { OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] }, where: {
include: { teams: true, githubApp: true, gitlabApp: true } OR: [
}); { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
return { { isSystemWide: true }
sources ]
} },
} catch ({ status, message }) { include: { teams: true, githubApp: true, gitlabApp: true }
return errorHandler({ status, message }) });
} return {
sources
};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function saveSource(request, reply) { export async function saveSource(request, reply) {
try { try {
const { id } = request.params const { id } = request.params;
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort) if (customPort) customPort = Number(customPort);
await prisma.gitSource.update({ await prisma.gitSource.update({
where: { id }, where: { id },
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
}); });
return reply.code(201).send() return reply.code(201).send();
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function getSource(request: FastifyRequest<OnlyId>) { export async function getSource(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params;
const { teamId } = request.user const { teamId } = request.user;
const settings = await prisma.setting.findFirst({}); const settings = await prisma.setting.findFirst({});
if (id === 'new') { if (id === 'new') {
return { return {
source: { source: {
name: null, name: null,
type: null, type: null,
htmlUrl: null, htmlUrl: null,
apiUrl: null, apiUrl: null,
organization: null, organization: null,
customPort: 22, customPort: 22,
customUser: 'git', customUser: 'git'
}, },
settings settings
} };
} }
const source = await prisma.gitSource.findFirst({ const source = await prisma.gitSource.findFirst({
where: { id, OR: [{ teams: { some: { id: teamId === "0" ? undefined : teamId } } }, { isSystemWide: true }] }, where: {
include: { githubApp: true, gitlabApp: true } id,
}); OR: [
if (!source) { { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
throw { status: 404, message: 'Source not found.' } { isSystemWide: true }
} ]
},
include: { githubApp: true, gitlabApp: true }
});
if (!source) {
throw { status: 404, message: 'Source not found.' };
}
if (source?.githubApp?.clientSecret) if (source?.githubApp?.clientSecret)
source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret); source.githubApp.clientSecret = decrypt(source.githubApp.clientSecret);
if (source?.githubApp?.webhookSecret) if (source?.githubApp?.webhookSecret)
source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret); source.githubApp.webhookSecret = decrypt(source.githubApp.webhookSecret);
if (source?.githubApp?.privateKey) source.githubApp.privateKey = decrypt(source.githubApp.privateKey); if (source?.githubApp?.privateKey)
if (source?.gitlabApp?.appSecret) source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret); source.githubApp.privateKey = decrypt(source.githubApp.privateKey);
if (source?.gitlabApp?.appSecret)
source.gitlabApp.appSecret = decrypt(source.gitlabApp.appSecret);
return { return {
source, source,
settings settings
}; };
} catch ({ status, message }) {
} catch ({ status, message }) { return errorHandler({ status, message });
return errorHandler({ status, message }) }
}
} }
export async function deleteSource(request) { export async function deleteSource(request) {
try { try {
const { id } = request.params const { id } = request.params;
const source = await prisma.gitSource.delete({ const gitAppFound = await prisma.application.findFirst({ where: { gitSourceId: id } });
where: { id }, if (gitAppFound) {
include: { githubApp: true, gitlabApp: true } throw {
}); status: 400,
if (source.githubAppId) { message: 'This source is used by an application. Please remove the application first.'
await prisma.githubApp.delete({ where: { id: source.githubAppId } }); };
} }
if (source.gitlabAppId) { const source = await prisma.gitSource.delete({
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } }); where: { id },
} include: { githubApp: true, gitlabApp: true }
return {} });
} catch ({ status, message }) { if (source.githubAppId) {
return errorHandler({ status, message }) await prisma.githubApp.delete({ where: { id: source.githubAppId } });
} }
if (source.gitlabAppId) {
await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
}
return {};
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) { export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
try { try {
const { teamId } = request.user const { teamId } = request.user;
const { id } = request.params const { id } = request.params;
let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body let { name, htmlUrl, apiUrl, organization, customPort, isSystemWide } = request.body;
if (customPort) customPort = Number(customPort) if (customPort) customPort = Number(customPort);
if (id === 'new') { if (id === 'new') {
const newId = cuid() const newId = cuid();
await prisma.gitSource.create({ await prisma.gitSource.create({
data: { data: {
id: newId, id: newId,
name, name,
htmlUrl, htmlUrl,
apiUrl, apiUrl,
organization, organization,
customPort, customPort,
isSystemWide, isSystemWide,
type: 'github', type: 'github',
teams: { connect: { id: teamId } } teams: { connect: { id: teamId } }
} }
}); });
return { return {
id: newId id: newId
} };
} }
throw { status: 500, message: 'Wrong request.' } throw { status: 500, message: 'Wrong request.' };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }
export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) { export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
try { try {
const { id } = request.params const { id } = request.params;
const { teamId } = request.user const { teamId } = request.user;
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } = let {
request.body type,
name,
htmlUrl,
apiUrl,
oauthId,
appId,
appSecret,
groupName,
customPort,
customUser
} = request.body;
if (oauthId) oauthId = Number(oauthId); if (oauthId) oauthId = Number(oauthId);
if (customPort) customPort = Number(customPort) if (customPort) customPort = Number(customPort);
const encryptedAppSecret = encrypt(appSecret); const encryptedAppSecret = encrypt(appSecret);
if (id === 'new') { if (id === 'new') {
const newId = cuid() const newId = cuid();
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } }); await prisma.gitSource.create({
await prisma.gitlabApp.create({ data: {
data: { id: newId,
teams: { connect: { id: teamId } }, type,
appId, apiUrl,
oauthId, htmlUrl,
groupName, name,
appSecret: encryptedAppSecret, customPort,
gitSource: { connect: { id: newId } } customUser,
} teams: { connect: { id: teamId } }
}); }
return { });
status: 201, await prisma.gitlabApp.create({
id: newId data: {
} teams: { connect: { id: teamId } },
} else { appId,
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } }); oauthId,
await prisma.gitlabApp.update({ groupName,
where: { id }, appSecret: encryptedAppSecret,
data: { gitSource: { connect: { id: newId } }
appId, }
oauthId, });
groupName, return {
appSecret: encryptedAppSecret, status: 201,
} id: newId
}); };
} } else {
return { status: 201 }; await prisma.gitSource.update({
where: { id },
} catch ({ status, message }) { data: { type, apiUrl, htmlUrl, name, customPort, customUser }
return errorHandler({ status, message }) });
} await prisma.gitlabApp.update({
where: { id },
data: {
appId,
oauthId,
groupName,
appSecret: encryptedAppSecret
}
});
}
return { status: 201 };
} catch ({ status, message }) {
return errorHandler({ status, message });
}
} }
export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) { export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
try { try {
const { oauthId } = request.body const { oauthId } = request.body;
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } }); const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });
if (found) { if (found) {
throw { status: 500, message: 'OAuthID already configured in Coolify.' } throw { status: 500, message: 'OAuthID already configured in Coolify.' };
} }
return {} return {};
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }

View File

@@ -1,7 +1,9 @@
import { FastifyRequest } from 'fastify'; import { FastifyRequest } from 'fastify';
import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common'; import { errorHandler, executeCommand, getDomain, isDev, prisma } from '../../../lib/common';
import { getTemplates } from '../../../lib/services'; import { getTemplates } from '../../../lib/services';
import { OnlyId } from '../../../types'; import { OnlyId } from '../../../types';
import { parseAndFindServiceTemplates } from '../../api/v1/services/handlers';
import { hashPassword } from '../../api/v1/handlers';
function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) { function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
if (isHttp2) { if (isHttp2) {
@@ -38,7 +40,7 @@ function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps
} }
}; };
} }
function generateRouters( async function generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
@@ -47,17 +49,22 @@ function generateRouters(
isWWW, isWWW,
isDualCerts, isDualCerts,
isCustomSSL, isCustomSSL,
isHttp2 = false isHttp2 = false,
) { httpBasicAuth = null,
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`; }) {
let http: any = { const rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
const ruleWWW = `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''
}`;
const http: any = {
entrypoints: ['web'], entrypoints: ['web'],
rule, rule,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
middlewares: [] middlewares: []
}; };
let https: any = { const https: any = {
entrypoints: ['websecure'], entrypoints: ['websecure'],
rule, rule,
service: `${serviceId}`, service: `${serviceId}`,
@@ -67,16 +74,16 @@ function generateRouters(
}, },
middlewares: [] middlewares: []
}; };
let httpWWW: any = { const httpWWW: any = {
entrypoints: ['web'], entrypoints: ['web'],
rule, rule: ruleWWW,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
middlewares: [] middlewares: []
}; };
let httpsWWW: any = { const httpsWWW: any = {
entrypoints: ['websecure'], entrypoints: ['websecure'],
rule, rule: ruleWWW,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
tls: { tls: {
@@ -93,6 +100,10 @@ function generateRouters(
httpsWWW.middlewares.push('redirect-to-non-www'); httpsWWW.middlewares.push('redirect-to-non-www');
delete https.tls; delete https.tls;
delete httpsWWW.tls; delete httpsWWW.tls;
if (httpBasicAuth) {
http.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
} }
// 3. http + www only // 3. http + www only
@@ -104,6 +115,10 @@ function generateRouters(
https.middlewares.push('redirect-to-www'); https.middlewares.push('redirect-to-www');
delete https.tls; delete https.tls;
delete httpsWWW.tls; delete httpsWWW.tls;
if (httpBasicAuth) {
httpWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
} }
// 5. https + non-www only // 5. https + non-www only
if (isHttps && !isWWW) { if (isHttps && !isWWW) {
@@ -132,6 +147,10 @@ function generateRouters(
}; };
} }
} }
if (httpBasicAuth) {
https.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
} }
// 6. https + www only // 6. https + www only
if (isHttps && isWWW) { if (isHttps && isWWW) {
@@ -141,6 +160,11 @@ function generateRouters(
http.middlewares.push('redirect-to-www'); http.middlewares.push('redirect-to-www');
https.middlewares.push('redirect-to-www'); https.middlewares.push('redirect-to-www');
} }
if (httpBasicAuth) {
httpsWWW.middlewares.push(`${serviceId}-${pathPrefix}-basic-auth`);
}
if (isCustomSSL) { if (isCustomSSL) {
if (isDualCerts) { if (isDualCerts) {
https.tls = true; https.tls = true;
@@ -162,23 +186,23 @@ function generateRouters(
} }
} }
if (isHttp2) { if (isHttp2) {
let http2 = { const http2 = {
...http, ...http,
service: `${serviceId}-http2`, service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)` rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
}; };
let http2WWW = { const http2WWW = {
...httpWWW, ...httpWWW,
service: `${serviceId}-http2`, service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)` rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
}; };
let https2 = { const https2 = {
...https, ...https,
service: `${serviceId}-http2`, service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)` rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
}; };
let https2WWW = { const https2WWW = {
...httpsWWW, ...httpsWWW,
service: `${serviceId}-http2`, service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)` rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
@@ -194,14 +218,17 @@ function generateRouters(
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW } [`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
}; };
} }
return {
const result = {
[`${serviceId}-${pathPrefix}`]: { ...http }, [`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-secure`]: { ...https }, [`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW }, [`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW } [`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
}; };
return result;
} }
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) { export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote = false) {
const traefik = { const traefik = {
tls: { tls: {
certificates: [] certificates: []
@@ -294,7 +321,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
}); });
} }
let parsedCertificates = []; const parsedCertificates = [];
for (const certificate of certificates) { for (const certificate of certificates) {
parsedCertificates.push({ parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`, certFile: `${sslpath}/${certificate.id}-cert.pem`,
@@ -365,7 +392,10 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
dockerComposeConfiguration, dockerComposeConfiguration,
destinationDocker, destinationDocker,
destinationDockerId, destinationDockerId,
settings settings,
basicAuthUser,
basicAuthPw,
settings: { basicAuth: isBasicAuthEnabled }
} = application; } = application;
if (!destinationDockerId) { if (!destinationDockerId) {
continue; continue;
@@ -378,6 +408,14 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
) { ) {
continue; continue;
} }
let httpBasicAuth = null;
if (basicAuthUser && basicAuthPw && isBasicAuthEnabled) {
httpBasicAuth = {
basicAuth: {
users: [basicAuthUser + ':' + await hashPassword(basicAuthPw, 1)]
}
};
}
if (buildPack === 'compose') { if (buildPack === 'compose') {
const services = Object.entries(JSON.parse(dockerComposeConfiguration)); const services = Object.entries(JSON.parse(dockerComposeConfiguration));
if (services.length > 0) { if (services.length > 0) {
@@ -400,27 +438,33 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL isCustomSSL,
) httpBasicAuth
})
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,
...generateServices(serviceId, containerId, port) ...generateServices(serviceId, containerId, port)
}; };
if (httpBasicAuth) {
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
...httpBasicAuth
};
}
} }
} }
} }
continue; continue;
} }
const { previews, dualCerts, isCustomSSL, isHttp2 } = settings; const { previews, dualCerts, isCustomSSL, isHttp2, basicAuth } = settings;
const { network, id: dockerId } = destinationDocker; const { network, id: dockerId } = destinationDocker;
if (!fqdn) { if (!fqdn) {
continue; continue;
@@ -433,22 +477,28 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const serviceId = `${id}-${port || 'default'}`; const serviceId = `${id}-${port || 'default'}`;
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL, isCustomSSL,
isHttp2 isHttp2,
) httpBasicAuth
})
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,
...generateServices(serviceId, id, port, isHttp2, isHttps) ...generateServices(serviceId, id, port, isHttp2, isHttps)
}; };
if (httpBasicAuth) {
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
...httpBasicAuth
};
}
if (previews) { if (previews) {
const { stdout } = await executeCommand({ const { stdout } = await executeCommand({
dockerId, dockerId,
@@ -462,29 +512,35 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
.map((c) => c.replace(/"/g, '')); .map((c) => c.replace(/"/g, ''));
if (containers.length > 0) { if (containers.length > 0) {
for (const container of containers) { for (const container of containers) {
const previewDomain = `${container.split('-')[1]}${ const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator
coolifySettings.previewSeparator }${domain}`;
}${domain}`;
const nakedDomain = previewDomain.replace(/^www\./, ''); const nakedDomain = previewDomain.replace(/^www\./, '');
const pathPrefix = '/'; const pathPrefix = '/';
const serviceId = `${container}-${port || 'default'}`; const serviceId = `${container}-${port || 'default'}`;
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
previewDomain, domain: previewDomain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL isCustomSSL,
) isHttp2: false,
httpBasicAuth
})
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,
...generateServices(serviceId, container, port, isHttp2) ...generateServices(serviceId, container, port, isHttp2)
}; };
if (httpBasicAuth) {
traefik.http.middlewares[`${serviceId}-${pathPrefix}-basic-auth`] = {
...httpBasicAuth
};
}
} }
} }
} }
@@ -533,11 +589,15 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
} }
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id)); found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
for (const oneService of Object.keys(found.services)) { for (const oneService of Object.keys(found.services)) {
const isDomainConfiguration = const isDomainAndProxyConfiguration =
found?.services[oneService]?.proxy?.filter((p) => p.domain) ?? []; found?.services[oneService]?.proxy?.filter((p) => p.port) ?? [];
if (isDomainConfiguration.length > 0) { if (isDomainAndProxyConfiguration.length > 0) {
const { proxy } = found.services[oneService]; const template: any = await parseAndFindServiceTemplates(service, null, true);
for (let configuration of proxy) { const { proxy } = template.services[oneService] || found.services[oneService];
for (const configuration of proxy) {
if (configuration.hostPort) {
continue;
}
if (configuration.domain) { if (configuration.domain) {
const setting = serviceSetting.find( const setting = serviceSetting.find(
(a) => a.variableName === configuration.domain (a) => a.variableName === configuration.domain
@@ -574,16 +634,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const serviceId = `${oneService}-${port || 'default'}`; const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL isCustomSSL,
) })
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,
@@ -611,16 +671,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const serviceId = `${oneService}-${port || 'default'}`; const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL isCustomSSL
) })
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,
@@ -652,16 +712,16 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const serviceId = `${id}-${port || 'default'}`; const serviceId = `${id}-${port || 'default'}`;
traefik.http.routers = { traefik.http.routers = {
...traefik.http.routers, ...traefik.http.routers,
...generateRouters( ...await generateRouters({
serviceId, serviceId,
domain, domain,
nakedDomain, nakedDomain,
pathPrefix, pathPrefix,
isHttps, isHttps,
isWWW, isWWW,
dualCerts, isDualCerts: dualCerts,
isCustomSSL isCustomSSL
) })
}; };
traefik.http.services = { traefik.http.services = {
...traefik.http.services, ...traefik.http.services,

View File

@@ -4,9 +4,11 @@ import { proxyConfiguration, otherProxyConfiguration } from './handlers';
import { OtherProxyConfiguration } from './types'; import { OtherProxyConfiguration } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<OnlyId>('/main.json', async (request, reply) => proxyConfiguration(request, false)); fastify.get<OnlyId>('/main.json', async (request) => proxyConfiguration(request, false));
fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true)); fastify.get<OnlyId>('/remote/:id', async (request) => proxyConfiguration(request, true));
fastify.get<OtherProxyConfiguration>('/other.json', async (request, reply) => otherProxyConfiguration(request)); fastify.get<OtherProxyConfiguration>('/other.json', async (request) =>
otherProxyConfiguration(request)
);
}; };
export default root; export default root;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,20 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
plugins: ['svelte3', '@typescript-eslint'],
ignorePatterns: ['*.cjs'],
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
settings: {
'svelte3/typescript': () => require('typescript')
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020
},
env: {
browser: true,
es2017: true,
node: true
}
};

View File

@@ -1,10 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -1 +0,0 @@
engine-strict=true

View File

@@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,9 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -1 +0,0 @@
# SvelteKit Static site

View File

@@ -1,54 +0,0 @@
{
"name": "client",
"description": "Coolify's SvelteKit UI",
"license": "Apache-2.0",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build && cp -Pr build/ ../../build/public",
"preview": "vite preview",
"test": "playwright test",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "1.28.1",
"@sveltejs/adapter-static": "1.0.0-next.48",
"@sveltejs/kit": "1.0.0-next.572",
"@types/js-cookie": "3.0.2",
"@typescript-eslint/eslint-plugin": "5.44.0",
"@typescript-eslint/parser": "5.44.0",
"autoprefixer": "10.4.13",
"eslint": "8.28.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "4.0.0",
"postcss": "8.4.19",
"postcss-load-config": "4.0.1",
"prettier": "2.8.0",
"prettier-plugin-svelte": "2.8.1",
"svelte": "3.53.1",
"svelte-check": "2.9.2",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "3.2.4",
"tslib": "2.4.1",
"typescript": "4.9.3",
"vite": "3.2.4"
},
"type": "module",
"dependencies": {
"@trpc/client": "10.1.0",
"@trpc/server": "10.1.0",
"cuid": "2.1.8",
"daisyui": "2.41.0",
"dayjs": "1.11.6",
"flowbite-svelte": "0.28.0",
"js-cookie": "3.0.1",
"js-yaml": "4.1.0",
"p-limit": "4.0.0",
"server": "workspace:*",
"superjson": "1.11.0",
"svelte-select": "4.4.7"
}
}

View File

@@ -1,10 +0,0 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173
}
};
export default config;

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
};
module.exports = config;

View File

@@ -1,12 +0,0 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
// and what to do when importing types
declare namespace App {
// interface Locals {}
// interface PageData {}
// interface Error {}
// interface Platform {}
}
declare const GITPOD_WORKSPACE_URL: string;
declare const CODESANDBOX_HOST: string;

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body>
<div class="h-screen">%sveltekit.body%</div>
</body>
</html>

View File

@@ -1,284 +0,0 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-regular.woff2') format('woff2'),
url('/poppins-v19-latin-ext_latin_devanagari-regular.woff') format('woff');
}
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 500;
src: local(''), url('/poppins-v19-latin-ext_latin_devanagari-500.woff2') format('woff2'),
url('/poppins-v19-latin-ext_latin_devanagari-500.woff') format('woff');
}
button {
@apply text-sm !important;
}
html {
@apply h-full min-h-full overflow-y-scroll;
}
body {
@apply min-h-screen overflow-x-hidden bg-coolblack text-sm text-white scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200;
}
input,
.input {
@apply h-12 w-96 rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent disabled:bg-coolblack md:text-sm;
}
textarea {
@apply min-w-[14rem] rounded border border-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:border disabled:border-dashed disabled:border-coolgray-200 disabled:bg-transparent md:text-sm;
}
#svelte .custom-select-wrapper .selectContainer.disabled input {
@apply placeholder:text-stone-600;
}
#svelte .custom-select-wrapper .selectContainer input {
@apply text-white;
}
#svelte .custom-select-wrapper .selectContainer {
@apply h-12 rounded bg-coolgray-200 p-2 px-0 text-xs tracking-tight outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 md:text-sm;
}
#svelte .listContainer {
@apply bg-coolgray-400 text-white scrollbar-w-2 scrollbar-thumb-green-500 scrollbar-track-coolgray-200;
}
#svelte .selectedItem {
@apply pl-2;
}
#svelte .item.hover {
@apply bg-coollabs text-white !important;
}
#svelte .item.active {
@apply bg-coolgray-100 text-white;
}
select {
@apply h-12 w-96 rounded bg-coolgray-200 p-2 text-xs font-bold tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:text-stone-600 md:text-sm;
}
.custom-select-wrapper {
--background: rgb(32 32 32);
--inputColor: white;
--multiItemPadding: 0;
--multiSelectPadding: 0 0.5rem 0 0.5rem;
--border: none;
--placeholderColor: rgb(87 83 78);
--listBackground: rgb(32 32 32);
--itemColor: white;
--itemHoverBG: rgb(107 22 237);
--multiItemBG: rgb(32 32 32);
--multiClearHoverBG: transparent;
--multiClearHoverFill: rgb(239 68 68);
--multiItemActiveBG: transparent;
--multiClearBG: transparent;
--clearSelectFocusColor: white;
--clearSelectHoverColor: rgb(239 68 68);
--multiItemBorderRadius: 0.25rem;
--listShadow: none;
}
label {
@apply inline-block;
}
.btn {
@apply text-white text-base min-w-fit no-animation;
}
a {
@apply underline hover:text-white;
}
.content {
@apply p-2 px-4;
}
.title {
@apply text-lg lg:text-2xl font-bold;
}
.subtitle {
@apply text-lg lg:text-xl font-bold text-indigo-300;
}
.label {
@apply text-sm leading-6 font-semibold text-sky-500 dark:text-sky-400;
}
.card {
@apply border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2 sticky top-4 mb-2 items-center;
}
.icon-holder {
overflow: hidden;
height: 30px;
border-radius: 5px;
margin-right: 8px;
background: linear-gradient(0deg, #999, #ddd);
}
.instance-status-running {
box-shadow: 1px 4px 5px #3df721;
}
.instance-status-stopped {
box-shadow: 1px 4px 5px rgb(110, 191, 225);
}
.instance-status-error {
box-shadow: 1px 4px 5px #fb00ff;
}
.instance-status-degraded {
box-shadow: 1px 4px 5px #f7b121;
}
.badge-status-healthy,
.badge-status-running {
@apply text-green-500;
}
.badge-status-degraded {
@apply text-green-500;
}
.badge-status-stopped {
@apply text-sky-500;
}
.delete-button {
@apply bg-red-600;
}
.delete-button:hover {
@apply bg-red-500;
}
/* Interchange menu position */
.menu-left {
display: flex;
flex-direction: row;
}
.menu-left .menu-bar {
display: flex;
flex-direction: column;
}
.menu-left .menu-bar > * {
display: flex;
flex-direction: column;
}
.menu-top {
display: flex;
flex-direction: column;
}
.menu-top .menu-bar {
display: flex;
flex-direction: row;
}
.menu-top .menu-bar > * {
display: flex;
flex-direction: row;
}
.nav-main {
@apply fixed top-0 left-0 min-h-screen w-16 min-w-[4rem] overflow-hidden border-r border-stone-800 bg-coolgray-200 scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200 xl:overflow-visible;
}
.nav-side {
@apply absolute right-0 top-0 z-50 m-5 flex flex-wrap items-center justify-end space-x-2 bg-coolblack/40 text-white;
}
.add-icon {
@apply rounded p-1 transition duration-200;
}
.icons {
@apply rounded p-2 transition duration-200 hover:bg-coolgray-500 disabled:bg-coolblack disabled:text-coolgray-500 !important;
}
.arrow-right-applications {
@apply -ml-6 px-2 font-bold text-green-500;
}
.border-gradient {
border-bottom: 2px solid transparent;
-o-border-image: linear-gradient(
0.25turn,
rgba(255, 249, 34),
rgba(255, 0, 128),
rgba(56, 2, 155, 0)
);
border-image: linear-gradient(
0.25turn,
rgba(255, 249, 34),
rgba(255, 0, 128),
rgba(56, 2, 155, 0)
);
border-image-slice: 1;
}
.border-gradient-full {
border: 4px solid transparent;
-o-border-image: linear-gradient(
0.25turn,
rgba(255, 249, 34),
rgba(255, 0, 128),
rgba(56, 2, 155, 0)
);
border-image: linear-gradient(
0.25turn,
rgba(255, 249, 34),
rgba(255, 0, 128),
rgba(56, 2, 155, 0)
);
border-image-slice: 1;
}
.box-selection {
@apply min-w-[16rem] justify-center rounded border-transparent bg-coolgray-200 p-6 hover:border-transparent hover:bg-coolgray-400;
}
.lds-heart {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
@keyframes lds-heart {
0% {
transform: scale(1);
}
5% {
transform: scale(1.2);
}
39% {
transform: scale(0.85);
}
45% {
transform: scale(1);
}
60% {
transform: scale(0.95);
}
100% {
transform: scale(0.9);
}
}
.sub-menu {
@apply w-48 text-base font-bold hover:bg-coolgray-500 rounded p-2 hover:text-white text-stone-200 cursor-pointer;
}
.sub-menu-active {
@apply bg-coolgray-500 text-white;
}
.table tbody td,
.table tbody th,
.table thead th {
background-color: transparent;
}
.table * {
border: none;
}
.header {
@apply flex flex-row z-10 w-full py-5 px-5;
}
.burger {
@apply block m-[2px] h-[3px] w-5 rounded;
}
.bg-coollabs-gradient {
@apply bg-gradient-to-r from-purple-500 via-pink-500 to-red-500;
}

View File

@@ -1,210 +0,0 @@
import { dev } from '$app/environment';
import { addToast } from './store';
import Cookies from 'js-cookie';
export const asyncSleep = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay));
export function dashify(str: string, options?: any): string {
if (typeof str !== 'string') return str;
return str
.trim()
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
.toLowerCase();
}
export function errorNotification(error: any | { message: string }): void {
if (error instanceof Error) {
console.error(error.message)
addToast({
message: error.message,
type: 'error'
});
} else {
console.error(error)
addToast({
message: error,
type: 'error'
});
}
}
export function getRndInteger(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function getDomain(domain: string) {
return domain?.replace('https://', '').replace('http://', '');
}
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel', 'heroku'];
export const staticDeployments = [
'react',
'vuejs',
'static',
'svelte',
'gatsby',
'php',
'astro',
'eleventy'
];
export function getAPIUrl() {
if (GITPOD_WORKSPACE_URL) {
const { href } = new URL(GITPOD_WORKSPACE_URL);
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '');
return newURL;
}
if (CODESANDBOX_HOST) {
return `https://${CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
}
return dev ? `http://${window.location.hostname}:3001` : 'http://localhost:3000';
}
export function getWebhookUrl(type: string) {
if (GITPOD_WORKSPACE_URL) {
const { href } = new URL(GITPOD_WORKSPACE_URL);
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '');
if (type === 'github') {
return `${newURL}/webhooks/github/events`;
}
if (type === 'gitlab') {
return `${newURL}/webhooks/gitlab/events`;
}
}
if (CODESANDBOX_HOST) {
const newURL = `https://${CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
if (type === 'github') {
return `${newURL}/webhooks/github/events`;
}
if (type === 'gitlab') {
return `${newURL}/webhooks/gitlab/events`;
}
}
return `https://webhook.site/0e5beb2c-4e9b-40e2-a89e-32295e570c21/events`;
}
async function send({
method,
path,
data = null,
headers,
timeout = 120000
}: {
method: string;
path: string;
data?: any;
headers?: any;
timeout?: number;
}): Promise<Record<string, unknown>> {
const token = Cookies.get('token');
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const opts: any = { method, headers: {}, body: null, signal: controller.signal };
if (data && Object.keys(data).length > 0) {
const parsedData = data;
for (const [key, value] of Object.entries(data)) {
if (value === '') {
parsedData[key] = null;
}
}
if (parsedData) {
opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(parsedData);
}
}
if (headers) {
opts.headers = {
...opts.headers,
...headers
};
}
if (token && !path.startsWith('https://')) {
opts.headers = {
...opts.headers,
Authorization: `Bearer ${token}`
};
}
if (!path.startsWith('https://')) {
path = `/api/v1${path}`;
}
if (dev && !path.startsWith('https://')) {
path = `${getAPIUrl()}${path}`;
}
if (method === 'POST' && data && !opts.body) {
opts.body = data;
}
const response = await fetch(`${path}`, opts);
clearTimeout(id);
const contentType = response.headers.get('content-type');
let responseData = {};
if (contentType) {
if (contentType?.indexOf('application/json') !== -1) {
responseData = await response.json();
} else if (contentType?.indexOf('text/plain') !== -1) {
responseData = await response.text();
} else {
return {};
}
} else {
return {};
}
if (!response.ok) {
if (
response.status === 401 &&
!path.startsWith('https://api.github') &&
!path.includes('/v4/')
) {
Cookies.remove('token');
}
throw responseData;
}
return responseData;
}
export function get(path: string, headers?: Record<string, unknown>): Promise<Record<string, any>> {
return send({ method: 'GET', path, headers });
}
export function del(
path: string,
data: Record<string, unknown>,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'DELETE', path, data, headers });
}
export function post(
path: string,
data: Record<string, unknown> | FormData,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'POST', path, data, headers });
}
export function put(
path: string,
data: Record<string, unknown>,
headers?: Record<string, unknown>
): Promise<Record<string, any>> {
return send({ method: 'PUT', path, data, headers });
}
export function changeQueryParams(buildId: string) {
const queryParams = new URLSearchParams(window.location.search);
queryParams.set('buildId', buildId);
// @ts-ignore
return history.pushState(null, null, '?' + queryParams.toString());
}
export const dateOptions: any = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};

View File

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

View File

@@ -1,156 +0,0 @@
<script lang="ts">
import { browser } from '$app/environment';
import { addToast } from '$lib/store';
let showPassword = false;
export let value: string;
export let disabled = false;
export let isPasswordField = false;
export let readonly = false;
export let textarea = false;
export let required = false;
export let pattern: string | null | undefined = null;
export let id: string;
export let name: string;
export let placeholder = '';
export let inputStyle = '';
let disabledClass = 'input input-primary bg-coolback disabled:bg-coolblack w-full';
let isHttps = browser && window.location.protocol === 'https:';
function copyToClipboard() {
if (isHttps && navigator.clipboard) {
navigator.clipboard.writeText(value);
addToast({
message: 'Copied to clipboard.',
type: 'success'
});
}
}
</script>
<div class="relative">
{#if !isPasswordField || showPassword}
{#if textarea}
<textarea
style={inputStyle}
rows="5"
class={disabledClass}
class:pr-10={true}
class:pr-20={value && isHttps}
class:border={required && !value}
class:border-red-500={required && !value}
{placeholder}
type="text"
{id}
{pattern}
{required}
{readonly}
{disabled}
{name}>{value}</textarea
>
{:else}
<input
style={inputStyle}
class={disabledClass}
type="text"
class:pr-10={true}
class:pr-20={value && isHttps}
class:border={required && !value}
class:border-red-500={required && !value}
{id}
{name}
{required}
{pattern}
{readonly}
bind:value
{disabled}
{placeholder}
/>
{/if}
{:else}
<input
style={inputStyle}
class={disabledClass}
class:pr-10={true}
class:pr-20={value && isHttps}
class:border={required && !value}
class:border-red-500={required && !value}
type="password"
{id}
{name}
{readonly}
{pattern}
{required}
bind:value
{disabled}
{placeholder}
/>
{/if}
<div class="absolute top-0 right-0 flex justify-center items-center h-full cursor-pointer text-stone-600 hover:text-white mr-3">
<div class="flex space-x-2">
{#if isPasswordField}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div on:click={() => (showPassword = !showPassword)}>
{#if showPassword}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
</svg>
{/if}
</div>
{/if}
{#if value && isHttps}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div on:click={copyToClipboard}>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="8" y="8" width="12" height="12" rx="2" />
<path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2" />
</svg>
</div>
{/if}
</div>
</div>
</div>

View File

@@ -1,44 +0,0 @@
<script lang="ts">
import ExternalLink from './ExternalLink.svelte';
import Tooltip from './Tooltip.svelte';
export let url = 'https://docs.coollabs.io';
export let text: any = '';
export let isExternal = false;
let id =
'cool-' +
url
.split('')
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
.join('')
.slice(-16);
</script>
<a
{id}
href={url}
target="_blank noreferrer"
class="flex no-underline inline-block cursor-pointer"
class:icons={!text}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z"
/>
</svg>
{text}
{#if isExternal}
<ExternalLink />
{/if}
</a>
{#if !text}
<Tooltip triggeredBy={`#${id}`}>See details in the documentation</Tooltip>
{/if}

View File

@@ -1,38 +0,0 @@
<script lang="ts">
// import { onMount } from 'svelte';
// import Tooltip from './Tooltip.svelte';
export let explanation = '';
export let position = 'dropdown-right';
// let id: any;
// let self: any;
// onMount(() => {
// id = `info-${self.offsetLeft}-${self.offsetTop}`;
// });
</script>
<div class={`dropdown dropdown-end ${position}`}>
<!-- svelte-ignore a11y-label-has-associated-control -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<label tabindex="0" class="btn btn-circle btn-ghost btn-xs text-sky-500">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="w-4 h-4 stroke-current"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
</label>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<div tabindex="0" class="card compact dropdown-content shadow bg-coolgray-400 rounded w-64">
<div class="card-body">
<!-- <h2 class="card-title">You needed more info?</h2> -->
<p class="text-xs font-normal">{@html explanation}</p>
</div>
</div>
</div>

View File

@@ -1,10 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25" />
</svg>

Before

Width:  |  Height:  |  Size: 261 B

View File

@@ -1,87 +0,0 @@
<script lang="ts">
import Beta from './Beta.svelte';
import Explaner from './Explainer.svelte';
import Tooltip from './Tooltip.svelte';
export let id: any;
export let customClass: any = null;
export let setting: any;
export let title: any;
export let isBeta: any = false;
export let description: any = null;
export let isCenter = true;
export let disabled = false;
export let dataTooltip: any = null;
export let loading = false;
let triggeredBy = `#${id}`;
</script>
<div class="flex items-center py-4 pr-8">
<div class="flex w-96 flex-col">
<!-- svelte-ignore a11y-label-has-associated-control -->
<label>
{title}
{#if isBeta}
<Beta />
{/if}
{#if description && description !== ''}
<Explaner explanation={description} />
{/if}
</label>
</div>
</div>
<div class:text-center={isCenter} class={`flex justify-center ${customClass}`}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:click
aria-pressed="false"
class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
class:opacity-50={disabled || loading}
class:bg-green-600={!loading && setting}
class:bg-stone-700={!loading && !setting}
class:bg-yellow-500={loading}
{id}
>
<span class="sr-only">Use setting</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow transition duration-200 ease-in-out"
class:translate-x-5={setting}
class:translate-x-0={!setting}
>
<span
class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in"
class:opacity-0={setting}
class:opacity-100={!setting}
class:animate-spin={loading}
aria-hidden="true"
>
<svg class="h-3 w-3 bg-white text-red-600" fill="none" viewBox="0 0 12 12">
<path
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span
class="absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-100 ease-out"
aria-hidden="true"
class:opacity-100={setting}
class:opacity-0={!setting}
class:animate-spin={loading}
>
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
<path
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
/>
</svg>
</span>
</span>
</div>
</div>
{#if dataTooltip}
<Tooltip {triggeredBy} placement="top">{dataTooltip}</Tooltip>
{/if}

View File

@@ -1,6 +0,0 @@
<script lang="ts">
export let text: string;
export let customClass = 'max-w-[24rem]';
</script>
<div class="p-2 text-xs text-stone-400 {customClass}">{@html text}</div>

View File

@@ -1,64 +0,0 @@
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let type = 'info';
function success() {
if (type === 'success') {
return 'bg-dark lg:bg-primary';
}
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:click={() => dispatch('click')}
on:mouseover={() => dispatch('pause')}
on:focus={() => dispatch('pause')}
on:mouseout={() => dispatch('resume')}
on:blur={() => dispatch('resume')}
class={` flex flex-row justify-center alert shadow-lg text-white hover:scale-105 transition-all duration-100 cursor-pointer rounded ${success()}`}
class:alert-error={type === 'error'}
class:alert-info={type === 'info'}
>
{#if type === 'success'}
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current flex-shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
{:else if type === 'error'}
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current flex-shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
{:else if type === 'info'}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current flex-shrink-0 w-6 h-6"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/></svg
>
{/if}
<slot />
</div>

View File

@@ -1,25 +0,0 @@
<script lang="ts">
import Toast from './Toast.svelte';
import { dismissToast, pauseToast, resumeToast, toasts } from '$lib/store';
</script>
{#if $toasts.length > 0}
<section>
<article class="toast toast-top toast-center rounded-none w-2/3 lg:w-[20rem]" role="alert">
{#each $toasts as toast (toast.id)}
<Toast
type={toast.type}
on:resume={() => resumeToast(toast.id)}
on:pause={() => pauseToast(toast.id)}
on:click={() => dismissToast(toast.id)}>{@html toast.message}</Toast
>
{/each}
</article>
</section>
{/if}
<style lang="postcss">
section {
@apply fixed top-0 left-0 right-0 w-full flex flex-col mt-4 justify-center z-[1000];
}
</style>

View File

@@ -1,10 +0,0 @@
<script lang="ts">
import { Tooltip } from 'flowbite-svelte';
export let placement = 'bottom';
export let color = 'bg-coollabs';
export let triggeredBy = '#tooltip-default';
</script>
<Tooltip {triggeredBy} {placement} arrow={false} defaultClass={color + ' font-thin text-xs text-left border-none p-2'} style="custom"
><slot /></Tooltip
>

View File

@@ -1,206 +0,0 @@
<script lang="ts">
import { dev } from '$app/environment';
import {
addToast,
appSession,
features,
updateLoading,
isUpdateAvailable,
latestVersion
} from '$lib/store';
import { asyncSleep, errorNotification } from '$lib/common';
import { onMount } from 'svelte';
import Tooltip from './Tooltip.svelte';
let updateStatus: any = {
found: false,
loading: false,
success: null
};
async function update() {
updateStatus.loading = true;
try {
if (dev) {
localStorage.setItem('lastVersion', $appSession.version);
await asyncSleep(1000);
updateStatus.loading = false;
return window.location.reload();
} else {
localStorage.setItem('lastVersion', $appSession.version);
// await post(`/update`, { type: 'update', latestVersion: $latestVersion });
addToast({
message: 'Update completed.<br><br>Waiting for the new version to start...',
type: 'success'
});
let reachable = false;
let tries = 0;
do {
await asyncSleep(4000);
try {
// await get(`/undead`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
addToast({
message: 'New version reachable. Reloading...',
type: 'success'
});
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
}
} catch (error) {
updateStatus.success = false;
updateStatus.loading = false;
return errorNotification(error);
}
}
onMount(async () => {
if ($appSession.userId) {
const overrideVersion = $features.latestVersion;
if ($appSession.teamId === '0') {
if ($updateLoading === true) return;
try {
$updateLoading = true;
// const data = await get(`/update`);
if (overrideVersion || data?.isUpdateAvailable) {
$latestVersion = overrideVersion || data.latestVersion;
if (overrideVersion) {
$isUpdateAvailable = true;
} else {
$isUpdateAvailable = data.isUpdateAvailable;
}
}
} catch (error) {
return errorNotification(error);
} finally {
$updateLoading = false;
}
}
}
});
</script>
<div class="py-0 lg:py-2">
{#if $appSession.teamId === '0'}
{#if $isUpdateAvailable}
<button
id="update"
disabled={updateStatus.success === false}
on:click={update}
class="icons bg-coollabs-gradient text-white duration-75 hover:scale-105 w-full"
>
{#if updateStatus.loading}
<svg
xmlns="http://www.w3.org/2000/svg"
class="lds-heart h-8 w-8 mx-auto"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"
/>
</svg>
{:else if updateStatus.success === null}
<div class="flex items-center justify-center space-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<line x1="12" y1="8" x2="8" y2="12" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="8" />
</svg>
<span class="flex lg:hidden">Update available</span>
</div>
{:else if updateStatus.success}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-8 w-8"
><path
fill="#DD2E44"
d="M11.626 7.488c-.112.112-.197.247-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"
/><path
fill="#EA596E"
d="M13 12L.416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"
/><path
fill="#A0041E"
d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"
/><path
fill="#AA8DD8"
d="M18.59 13.609c-.199.161-.459.245-.734.215-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532-.549-.06-.947-.552-.886-1.102.059-.549.55-.946 1.101-.886 2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532.548.06.947.553.886 1.102-.028.274-.167.511-.366.671z"
/><path
fill="#77B255"
d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095.53-.149 1.084.16 1.233.691.148.532-.161 1.084-.693 1.234-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096-.532.15-1.083-.16-1.233-.691-.149-.53.161-1.082.693-1.232z"
/><path
fill="#AA8DD8"
d="M23.001 20.16c-.294 0-.584-.129-.782-.375-.345-.432-.274-1.061.156-1.406.218-.175 5.418-4.259 12.767-3.208.547.078.927.584.849 1.131-.078.546-.58.93-1.132.848-6.493-.922-11.187 2.754-11.233 2.791-.186.148-.406.219-.625.219z"
/><path
fill="#77B255"
d="M5.754 16c-.095 0-.192-.014-.288-.042-.529-.159-.829-.716-.67-1.245 1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071.042.551-.372 1.031-.922 1.072-.559.034-1.031-.372-1.072-.923-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192-.13.433-.528.712-.958.712z"
/><circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5" /><circle
fill="#9266CC"
cx="2"
cy="18"
r="2"
/><circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5" /><circle
fill="#5C913B"
cx="23.5"
cy="31.5"
r="1.5"
/><circle fill="#FFCC4D" cx="28" cy="4" r="2" /><circle
fill="#FFCC4D"
cx="32.5"
cy="8.5"
r="1.5"
/><circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5" /><circle
fill="#FFCC4D"
cx="7.5"
cy="23.5"
r="1.5"
/></svg
>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" class="h-9 w-8"
><path
fill="#FFCC4D"
d="M36 18c0 9.941-8.059 18-18 18S0 27.941 0 18 8.059 0 18 0s18 8.059 18 18"
/><path
fill="#664500"
d="M22 27c0 2.763-1.791 3-4 3-2.21 0-4-.237-4-3 0-2.761 1.79-6 4-6 2.209 0 4 3.239 4 6zm8-12c-.124 0-.25-.023-.371-.072-5.229-2.091-7.372-5.241-7.461-5.374-.307-.46-.183-1.081.277-1.387.459-.306 1.077-.184 1.385.274.019.027 1.93 2.785 6.541 4.629.513.206.763.787.558 1.3-.157.392-.533.63-.929.63zM6 15c-.397 0-.772-.238-.929-.629-.205-.513.044-1.095.557-1.3 4.612-1.844 6.523-4.602 6.542-4.629.308-.456.929-.577 1.387-.27.457.308.581.925.275 1.383-.089.133-2.232 3.283-7.46 5.374C6.25 14.977 6.124 15 6 15z"
/><path fill="#5DADEC" d="M24 16h4v19l-4-.046V16zM8 35l4-.046V16H8v19z" /><path
fill="#664500"
d="M14.999 18c-.15 0-.303-.034-.446-.105-3.512-1.756-7.07-.018-7.105 0-.495.249-1.095.046-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.498-2.197 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552zm14 0c-.15 0-.303-.034-.446-.105-3.513-1.756-7.07-.018-7.105 0-.494.248-1.094.047-1.342-.447-.247-.494-.047-1.095.447-1.342.182-.09 4.501-2.196 8.895 0 .494.247.694.848.447 1.342-.176.35-.529.552-.896.552z"
/><ellipse fill="#5DADEC" cx="18" cy="34" rx="18" ry="2" /><ellipse
fill="#E75A70"
cx="18"
cy="27"
rx="3"
ry="2"
/></svg
>
{/if}
</button>
<Tooltip triggeredBy="#update" placement="right" color="bg-coolgray-200 text-white"
>New Version Available!</Tooltip
>
{/if}
{/if}
</div>

View File

@@ -1,17 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<line x1="4" y1="7" x2="20" y2="7" />
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
</svg>

Before

Width:  |  Height:  |  Size: 486 B

View File

@@ -1,10 +0,0 @@
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25" />
</svg>

Before

Width:  |  Height:  |  Size: 262 B

View File

@@ -1,47 +0,0 @@
<script lang="ts">
import * as Icons from '$lib/components/icons/applications';
export let application: any;
export let isAbsolute = true;
</script>
{#if application.buildPack?.toLowerCase() === 'rust'}
<Icons.Rust {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'node'}
<Icons.Nodejs {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'react'}
<Icons.React {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'svelte'}
<Icons.Svelte {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'vuejs'}
<Icons.Vuejs {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'php'}
<Icons.Php {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'python'}
<Icons.Python {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'static'}
<Icons.Static {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'nestjs'}
<Icons.Nestjs {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'nuxtjs'}
<Icons.Nuxtjs {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'nextjs'}
<Icons.Nextjs {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'gatsby'}
<Icons.Gatsby {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'docker'}
<Icons.Docker {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'astro'}
<Icons.Astro {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'eleventy'}
<Icons.Eleventy {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'deno'}
<Icons.Deno {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'laravel'}
<Icons.Laravel {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'heroku'}
<Icons.Heroku {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'compose'}
<Icons.Compose {isAbsolute} />
{:else if application.simpleDockerfile}
<Icons.Docker {isAbsolute} />
{/if}

View File

@@ -1,25 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-6 h-14 w-14' : 'mx-auto w-8 h-8'}
viewBox="0 0 256 256"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="a"
fill="#302649"
fill-rule="evenodd"
clip-rule="evenodd"
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
/>
<path
id="flame"
fill="#EF661E"
fill-rule="evenodd"
clip-rule="evenodd"
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
/>
</svg>

View File

@@ -1,9 +0,0 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="docker compose logo"
class={isAbsolute ? 'w-16 h-16 absolute top-0 left-0 -m-8' : 'w-8 h-8 mx-auto'}
src="/icons/compose.png"
/>

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg viewBox="0 0 128 128" class={isAbsolute ? 'absolute top-0 left-0 -m-5 h-12 w-12' : 'mx-auto w-10 h-10'}>
<path d="M124.8 52.1c-4.3-2.5-10-2.8-14.8-1.4-.6-5.2-4-9.7-8-12.9l-1.6-1.3-1.4 1.6c-2.7 3.1-3.5 8.3-3.1 12.3.3 2.9 1.2 5.9 3 8.3-1.4.8-2.9 1.9-4.3 2.4-2.8 1-5.9 2-8.9 2H79V49H66V24H51v12H26v13H13v14H1.8l-.2 1.5c-.5 6.4.3 12.6 3 18.5l1.1 2.2.1.2c7.9 13.4 21.7 19 36.8 19 29.2 0 53.3-13.1 64.3-40.6 7.4.4 15-1.8 18.6-8.9l.9-1.8-1.6-1zM28 39h10v11H28V39zm13.1 44.2c0 1.7-1.4 3.1-3.1 3.1-1.7 0-3.1-1.4-3.1-3.1 0-1.7 1.4-3.1 3.1-3.1 1.7.1 3.1 1.4 3.1 3.1zM28 52h10v11H28V52zm-13 0h11v11H15V52zm27.7 50.2c-15.8-.1-24.3-5.4-31.3-12.4 2.1.1 4.1.2 5.9.2 1.6 0 3.2 0 4.7-.1 3.9-.2 7.3-.7 10.1-1.5 2.3 5.3 6.5 10.2 14 13.8h-3.4zM51 63H40V52h11v11zm0-13H40V39h11v11zm13 13H53V52h11v11zm0-13H53V39h11v11zm0-13H53V26h11v11zm13 26H66V52h11v11zM38.8 81.2c-.2-.1-.5-.2-.8-.2-1.2 0-2.2 1-2.2 2.2 0 1.2 1 2.2 2.2 2.2s2.2-1 2.2-2.2c0-.3-.1-.6-.2-.8-.2.3-.4.5-.8.5-.5 0-.9-.4-.9-.9.1-.4.3-.7.5-.8z" fill="#019BC6"></path>
</svg>

View File

@@ -1,13 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
viewBox="0 0 128 128"
class={isAbsolute ? 'absolute top-0 left-0 -m-8 h-16 w-16' : 'mx-auto w-8 h-8'}
>
<path fill="transparent" d="M18 0h92v128H18z" /><path
d="M55.3 36.3h.4c1.1 0 1.5.9 1.5 2.3v41.8c0 1.8-.4 3-1.6 3l-4.8-.1c-1.2 0-1.6-1-1.6-3V45.5l-2.1.5c-1 0-1.5-.8-1.5-2.2v-3c0-1.2.4-2 1.4-2.2l8.3-2.2zm16 36.1l.1 3 .6 1.3.6.6.8.1h2.2c1 0 1.7.8 1.7 2v1.9c0 1.2-.6 2-1.8 2h-3.2l-2.3-.1c-.7-.2-1.4-.5-2.2-1a5.7 5.7 0 01-2-1.9c-.4-.8-.8-1.9-1-3.2-.4-1.4-.5-3-.5-4.8v-16h-1.5c-1.1 0-1.6-1-1.6-2.4v-1.7c0-1.4.5-2.3 1.6-2.3h1.5v-.1l.6-12.3c0-1.5.5-2.5 1.6-2.5h3.1c1.2 0 1.6 1 1.6 2.5v12.3h3.6c1.1 0 1.6 1 1.6 2.4V54c0 1.4-.5 2.3-1.6 2.3h-3.6v16.2zm9.4 15.7c0-2 .3-3.2 1.5-3.2.2 0 .4 0 1.4.4l1.1.3.2-.1.4-.7c.3-.6.4-1.6.4-3l-.6-3.3L79 52.7v-.9c0-1.2.3-2 1.2-2H84c.5 0 1 .3 1.3.6.3.4.5.9.6 1.6l3.4 18 2.6-17.8c.1-.8.3-1.3.6-1.7.3-.4.8-.6 1.3-.6h2.6c1 0 1.4.8 1.4 2l-.1.8L92 82.2c-.5 2.5-1 4.4-1.5 5.7a6.6 6.6 0 01-2 3c-.9.6-1.9.9-3.1.9h-.3c-2 0-3.3-.6-4.1-1.7-.3-.4-.4-1-.4-2zM31.3 38.8l8.2-2.1h.5c1 0 1.4.8 1.4 2.2v41.9c0 1.8-.4 2.9-1.6 2.9h-4.7c-1.2 0-1.6-1.1-1.6-3v-35l-2 .6c-1.2 0-1.6-.9-1.6-2.2v-3c0-1.2.4-2 1.4-2.3z"
fill="#FFF"
/>
</svg>

View File

@@ -1,13 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
viewBox="0 0 128 128"
>
<path
fill="#64328B"
d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"
/>
</svg>

View File

@@ -1,15 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8 '}
viewBox="0 0 72 80"
>
<path
xmlns="http://www.w3.org/2000/svg"
fill="#430098"
d="M64.8,0 L7.2,0 C3.224,0 0,3.224 0,7.2 L0,72.8 C0,76.776 3.224,80 7.2,80 L64.8,80 C68.776,80 72,76.776 72,72.8 L72,7.2 C72,3.224 68.776,0 64.8,0 Z M18,68 L18,52 L27,60 L18,68 Z M46,68 L46,44.11 C45.961,42.243 45.062,40 41,40 C32.866,40 23.742,44.091 23.651,44.132 L18,46.692 L18,12 L26,12 L26,34.711 C29.994,33.411 35.577,32 41,32 C45.945,32 48.905,33.944 50.517,35.575 C53.958,39.055 54.005,43.488 54.0002258,44 L54.0002258,68 L46,68 Z M48,25 L40,25 C43.144,20.875 45.118,16.534 46,12 L54,12 C53.46,16.544 51.618,20.9 48,25 Z"
/>
</svg>

View File

@@ -1,14 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10' : 'mx-auto w-8 h-8'}
viewBox="0 0 50 52"
xmlns="http://www.w3.org/2000/svg"
><title>Logomark</title><path
d="M49.626 11.564a.809.809 0 0 1 .028.209v10.972a.8.8 0 0 1-.402.694l-9.209 5.302V39.25c0 .286-.152.55-.4.694L20.42 51.01c-.044.025-.092.041-.14.058-.018.006-.035.017-.054.022a.805.805 0 0 1-.41 0c-.022-.006-.042-.018-.063-.026-.044-.016-.09-.03-.132-.054L.402 39.944A.801.801 0 0 1 0 39.25V6.334c0-.072.01-.142.028-.21.006-.023.02-.044.028-.067.015-.042.029-.085.051-.124.015-.026.037-.047.055-.071.023-.032.044-.065.071-.093.023-.023.053-.04.079-.06.029-.024.055-.05.088-.069h.001l9.61-5.533a.802.802 0 0 1 .8 0l9.61 5.533h.002c.032.02.059.045.088.068.026.02.055.038.078.06.028.029.048.062.072.094.017.024.04.045.054.071.023.04.036.082.052.124.008.023.022.044.028.068a.809.809 0 0 1 .028.209v20.559l8.008-4.611v-10.51c0-.07.01-.141.028-.208.007-.024.02-.045.028-.068.016-.042.03-.085.052-.124.015-.026.037-.047.054-.071.024-.032.044-.065.072-.093.023-.023.052-.04.078-.06.03-.024.056-.05.088-.069h.001l9.611-5.533a.801.801 0 0 1 .8 0l9.61 5.533c.034.02.06.045.09.068.025.02.054.038.077.06.028.029.048.062.072.094.018.024.04.045.054.071.023.039.036.082.052.124.009.023.022.044.028.068zm-1.574 10.718v-9.124l-3.363 1.936-4.646 2.675v9.124l8.01-4.611zm-9.61 16.505v-9.13l-4.57 2.61-13.05 7.448v9.216l17.62-10.144zM1.602 7.719v31.068L19.22 48.93v-9.214l-9.204-5.209-.003-.002-.004-.002c-.031-.018-.057-.044-.086-.066-.025-.02-.054-.036-.076-.058l-.002-.003c-.026-.025-.044-.056-.066-.084-.02-.027-.044-.05-.06-.078l-.001-.003c-.018-.03-.029-.066-.042-.1-.013-.03-.03-.058-.038-.09v-.001c-.01-.038-.012-.078-.016-.117-.004-.03-.012-.06-.012-.09v-.002-21.481L4.965 9.654 1.602 7.72zm8.81-5.994L2.405 6.334l8.005 4.609 8.006-4.61-8.006-4.608zm4.164 28.764l4.645-2.674V7.719l-3.363 1.936-4.646 2.675v20.096l3.364-1.937zM39.243 7.164l-8.006 4.609 8.006 4.609 8.005-4.61-8.005-4.608zm-.801 10.605l-4.646-2.675-3.363-1.936v9.124l4.645 2.674 3.364 1.937v-9.124zM20.02 38.33l11.743-6.704 5.87-3.35-8-4.606-9.211 5.303-8.395 4.833 7.993 4.524z"
fill="#FF2D20"
fill-rule="evenodd"
/></svg
>

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute
? 'absolute top-0 left-0 -m-4 h-10 w-10 fill-current text-blue-500'
: 'mx-auto w-8 h-8 fill-current text-blue-500'}
viewBox="0 0 128 128"
>
<path
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
/>
</svg>

View File

@@ -1,18 +0,0 @@
<script lang="ts">
export let isAbsolute = true;
</script>
<svg
class={isAbsolute ? 'absolute top-0 left-0 -m-4 h-10 w-10 text-green-500' : 'mx-auto w-8 h-8 text-green-500'}
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="node-js"
role="img"
viewBox="0 0 448 512"
><path
fill="currentColor"
d="M224 508c-6.7 0-13.5-1.8-19.4-5.2l-61.7-36.5c-9.2-5.2-4.7-7-1.7-8 12.3-4.3 14.8-5.2 27.9-12.7 1.4-.8 3.2-.5 4.6.4l47.4 28.1c1.7 1 4.1 1 5.7 0l184.7-106.6c1.7-1 2.8-3 2.8-5V149.3c0-2.1-1.1-4-2.9-5.1L226.8 37.7c-1.7-1-4-1-5.7 0L36.6 144.3c-1.8 1-2.9 3-2.9 5.1v213.1c0 2 1.1 4 2.9 4.9l50.6 29.2c27.5 13.7 44.3-2.4 44.3-18.7V167.5c0-3 2.4-5.3 5.4-5.3h23.4c2.9 0 5.4 2.3 5.4 5.3V378c0 36.6-20 57.6-54.7 57.6-10.7 0-19.1 0-42.5-11.6l-48.4-27.9C8.1 389.2.7 376.3.7 362.4V149.3c0-13.8 7.4-26.8 19.4-33.7L204.6 9c11.7-6.6 27.2-6.6 38.8 0l184.7 106.7c12 6.9 19.4 19.8 19.4 33.7v213.1c0 13.8-7.4 26.7-19.4 33.7L243.4 502.8c-5.9 3.4-12.6 5.2-19.4 5.2zm149.1-210.1c0-39.9-27-50.5-83.7-58-57.4-7.6-63.2-11.5-63.2-24.9 0-11.1 4.9-25.9 47.4-25.9 37.9 0 51.9 8.2 57.7 33.8.5 2.4 2.7 4.2 5.2 4.2h24c1.5 0 2.9-.6 3.9-1.7s1.5-2.6 1.4-4.1c-3.7-44.1-33-64.6-92.2-64.6-52.7 0-84.1 22.2-84.1 59.5 0 40.4 31.3 51.6 81.8 56.6 60.5 5.9 65.2 14.8 65.2 26.7 0 20.6-16.6 29.4-55.5 29.4-48.9 0-59.6-12.3-63.2-36.6-.4-2.6-2.6-4.5-5.3-4.5h-23.9c-3 0-5.3 2.4-5.3 5.3 0 31.1 16.9 68.2 97.8 68.2 58.4-.1 92-23.2 92-63.4z"
/>
</svg>

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