Compare commits

...

69 Commits

Author SHA1 Message Date
Andras Bacsai
e5537a33fb Merge pull request #223 from coollabsio/v2.0.30
v2.0.30
2022-03-19 15:11:42 +01:00
Andras Bacsai
35384deb68 fix: Remove build logs in case of app removed 2022-03-19 15:06:25 +01:00
Andras Bacsai
547ca60c2a fix: Better queue system + more support on monorepos 2022-03-19 15:04:52 +01:00
Andras Bacsai
376f6f7455 fix: Basedir for dockerfiles 2022-03-19 13:33:31 +01:00
Andras Bacsai
abe92dedff fix: no webhook secret found? 2022-03-15 17:35:37 +01:00
Andras Bacsai
4b521ceedc chore: version++ 2022-03-15 17:25:17 +01:00
Andras Bacsai
6dfcb9e52b fix: No error if GitSource is missing 2022-03-15 17:22:28 +01:00
Andras Bacsai
335e3216e2 fix: Missing session data 2022-03-15 17:21:18 +01:00
Andras Bacsai
5b22bb4818 fix: No cookie found 2022-03-15 17:04:15 +01:00
Andras Bacsai
0097004882 Merge pull request #217 from coollabsio/v2.0.29
v2.0.29
2022-03-12 00:28:26 +01:00
Andras Bacsai
1bc9e4c2d3 fix: Autodeploy true by default for GH repos 2022-03-11 23:56:11 +01:00
Andras Bacsai
36c7e1a3c3 feat: Install pnpm into docker image if pnpm lock file is used 2022-03-11 23:55:57 +01:00
Andras Bacsai
c6b4d04e26 Revert double build 2022-03-11 22:48:55 +01:00
Andras Bacsai
fa6cf068c7 feat: Autodeploy pause 2022-03-11 22:36:21 +01:00
Andras Bacsai
7c273a3a48 feat: Check ssl for new apps/services first 2022-03-11 21:28:27 +01:00
Andras Bacsai
3de2ea1523 chore: version++ 2022-03-11 21:19:03 +01:00
Andras Bacsai
c5c9f84503 feat: Webhooks inititate all applications with the correct branch 2022-03-11 21:18:12 +01:00
Andras Bacsai
16ea9a3e07 Update options request 2022-03-11 20:52:11 +01:00
Andras Bacsai
48f952c798 fix: Personal Gitlab repos 2022-03-11 20:47:26 +01:00
Andras Bacsai
f78ea5de07 Remove colors Tailwind 2022-03-11 20:47:13 +01:00
Andras Bacsai
5adbd5e784 Merge pull request #210 from coollabsio/v2.0.28
v2.0.28
2022-03-04 15:39:35 +01:00
Andras Bacsai
5b2afa79d7 chore: version++ 2022-03-04 15:20:03 +01:00
Andras Bacsai
dc4e6d02b7 feat: Service secrets 2022-03-04 15:14:25 +01:00
Andras Bacsai
8ae61c8f78 fix: do not error if proxy is not running 2022-03-04 14:20:20 +01:00
Andras Bacsai
684b8e0914 Merge pull request #207 from coollabsio/v2.0.27
v2.0.27
2022-03-02 21:15:32 +01:00
Andras Bacsai
7c3314abae force image deletion 2022-03-02 21:14:53 +01:00
Andras Bacsai
ab9f8ff356 Before latest image 2022-03-02 21:08:36 +01:00
Andras Bacsai
892d8cd5c1 Reload proxy after ssl renewal 2022-03-02 21:04:42 +01:00
Andras Bacsai
8b8b45778d fix: application state in UI 2022-03-02 21:01:25 +01:00
Andras Bacsai
6655fb182c fix: cleanup coolify images 2022-03-02 20:57:28 +01:00
Andras Bacsai
0926d40247 fix: Reload haproxy if new cert is added 2022-03-02 20:43:16 +01:00
Andras Bacsai
ddc4d36688 Fix cleanup process of old coolify images 2022-03-02 20:32:29 +01:00
Andras Bacsai
53e1f22eb1 fix: check when a container is running 2022-03-02 20:32:18 +01:00
Andras Bacsai
3d2a34737b prevent restarting container check 2022-03-02 15:52:22 +01:00
Andras Bacsai
ebde77008c Cleanup coolify image fix 2022-03-02 15:52:06 +01:00
Andras Bacsai
3d27fd04ba cleanup old images of coolify 2022-03-02 14:53:43 +01:00
Andras Bacsai
d9fcaf3473 update packages 2022-03-02 14:53:33 +01:00
Andras Bacsai
d266f761aa Stats open with auth 2022-03-02 14:38:51 +01:00
Andras Bacsai
1d01405412 chore: Version++ 2022-03-02 13:38:58 +01:00
Andras Bacsai
7c62eb5bd6 feat: Send version with update request 2022-03-02 13:38:45 +01:00
Andras Bacsai
4dcc76d366 fix: Update process 2022-03-02 13:37:06 +01:00
Andras Bacsai
d2fad19a11 Update package.json 2022-03-02 12:23:40 +01:00
Andras Bacsai
7c92c4c964 Merge pull request #206 from coollabsio/feat/languagetool
v2.0.26
2022-03-02 12:22:44 +01:00
Andras Bacsai
5a71d33236 chore: Version++ 2022-03-02 12:20:20 +01:00
Andras Bacsai
1b4db4f793 fix 2022-03-02 12:20:02 +01:00
Andras Bacsai
c084b22815 fix: volume name 2022-03-02 12:17:48 +01:00
Andras Bacsai
acacef95cd fix: reload proxy on ssl cert 2022-03-02 12:10:12 +01:00
Andras Bacsai
5d722183d3 feat: Languagetool service 2022-03-02 11:57:03 +01:00
Andras Bacsai
ac19ea5407 Merge pull request #202 from coollabsio/fix
v2.0.24
2022-03-02 11:18:51 +01:00
Andras Bacsai
d19b05b970 fix: update process 2022-03-02 11:00:08 +01:00
Andras Bacsai
a0795136ac fix 2022-03-01 15:30:39 +01:00
Andras Bacsai
d2566e345a fix 2022-03-01 15:27:33 +01:00
Andras Bacsai
66cd7cf90e remove debug logging 2022-03-01 15:27:06 +01:00
Andras Bacsai
9a599981ef fix 2022-03-01 15:25:13 +01:00
Andras Bacsai
f51f7bc82a fix 2022-03-01 15:22:11 +01:00
Andras Bacsai
dbcbac0137 cleanup 2022-03-01 14:29:15 +01:00
Andras Bacsai
e722f8a87c fix: Reconfigure proxy on restart 2022-03-01 14:02:46 +01:00
Andras Bacsai
61679749eb fix: null proxyhash on restart 2022-03-01 13:15:23 +01:00
Andras Bacsai
23e12c9c44 fix: ssl + sslrenew 2022-03-01 13:07:34 +01:00
Andras Bacsai
6da78cd3e5 remove proxy refresh 2022-03-01 11:45:29 +01:00
Andras Bacsai
78ce8100a3 migrate file 2022-03-01 11:19:40 +01:00
Andras Bacsai
76ba338b45 fix 2022-03-01 11:17:34 +01:00
Andras Bacsai
823fe2deb2 refactor 2022-03-01 11:10:10 +01:00
Andras Bacsai
cb90f692f2 WIP proxy 2022-03-01 00:20:28 +01:00
Andras Bacsai
0325343ede save 2022-03-01 00:12:54 +01:00
Andras Bacsai
69d1556a1d WIP better automatic proxy conf 2022-03-01 00:08:54 +01:00
Andras Bacsai
2daa043840 WIP 2022-02-28 16:55:02 +01:00
Andras Bacsai
f340ca9d05 WIP 2022-02-28 16:06:44 +01:00
Andras Bacsai
02abd038fa fix: Better proxy check 2022-02-28 11:20:46 +01:00
81 changed files with 1734 additions and 1522 deletions

View File

@@ -1,4 +1,5 @@
FROM node:16.14.0-alpine
RUN apk add --no-cache g++ cmake make python3
WORKDIR /app
COPY package*.json .
RUN yarn install

View File

@@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "2.0.23",
"version": "2.0.30",
"license": "AGPL-3.0",
"scripts": {
"dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
@@ -25,12 +25,12 @@
"prepare": "husky install"
},
"devDependencies": {
"@sveltejs/adapter-node": "1.0.0-next.69",
"@sveltejs/adapter-node": "1.0.0-next.70",
"@sveltejs/adapter-static": "1.0.0-next.28",
"@sveltejs/kit": "1.0.0-next.283",
"@sveltejs/kit": "1.0.0-next.288",
"@types/bcrypt": "5.0.0",
"@types/js-cookie": "3.0.1",
"@types/node": "17.0.20",
"@types/node": "17.0.21",
"@types/node-forge": "1.0.0",
"@typescript-eslint/eslint-plugin": "4.31.1",
"@typescript-eslint/parser": "4.31.1",
@@ -39,41 +39,42 @@
"cross-var": "1.1.0",
"eslint": "7.32.0",
"eslint-config-prettier": "8.4.0",
"eslint-plugin-svelte3": "3.4.0",
"eslint-plugin-svelte3": "3.4.1",
"husky": "7.0.4",
"lint-staged": "12.3.4",
"postcss": "8.4.6",
"postcss": "8.4.7",
"prettier": "2.5.1",
"prettier-plugin-svelte": "2.6.0",
"prettier-plugin-tailwindcss": "0.1.7",
"prettier-plugin-tailwindcss": "0.1.8",
"prisma": "3.10.0",
"svelte": "3.46.4",
"svelte-check": "2.4.5",
"svelte-preprocess": "4.10.4",
"tailwindcss": "3.0.23",
"ts-node": "10.5.0",
"ts-node": "10.6.0",
"tslib": "2.3.1",
"typescript": "4.5.5"
"typescript": "4.6.2"
},
"type": "module",
"dependencies": {
"@iarna/toml": "2.2.5",
"@prisma/client": "3.10.0",
"@sentry/node": "6.17.9",
"@sentry/node": "6.18.1",
"bcrypt": "5.0.1",
"bullmq": "1.74.2",
"bullmq": "1.76.0",
"compare-versions": "4.1.3",
"cookie": "0.4.2",
"cuid": "2.1.8",
"dayjs": "1.10.7",
"dayjs": "1.10.8",
"dockerode": "3.3.1",
"dotenv-extended": "2.9.0",
"generate-password": "1.7.0",
"get-port": "6.1.1",
"get-port": "6.1.2",
"got": "12.0.1",
"js-cookie": "3.0.1",
"js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1",
"mustache": "^4.2.0",
"node-forge": "1.2.1",
"svelte-kit-cookie-session": "2.1.2",
"tailwindcss-scrollbar": "^0.1.0",

274
pnpm-lock.yaml generated
View File

@@ -3,43 +3,44 @@ lockfileVersion: 5.3
specifiers:
'@iarna/toml': 2.2.5
'@prisma/client': 3.10.0
'@sentry/node': 6.17.9
'@sveltejs/adapter-node': 1.0.0-next.69
'@sentry/node': 6.18.1
'@sveltejs/adapter-node': 1.0.0-next.70
'@sveltejs/adapter-static': 1.0.0-next.28
'@sveltejs/kit': 1.0.0-next.283
'@sveltejs/kit': 1.0.0-next.288
'@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1
'@types/node': 17.0.20
'@types/node': 17.0.21
'@types/node-forge': 1.0.0
'@typescript-eslint/eslint-plugin': 4.31.1
'@typescript-eslint/parser': 4.31.1
'@zerodevx/svelte-toast': 0.7.0
autoprefixer: 10.4.2
bcrypt: 5.0.1
bullmq: 1.74.2
bullmq: 1.76.0
compare-versions: 4.1.3
cookie: 0.4.2
cross-var: 1.1.0
cuid: 2.1.8
dayjs: 1.10.7
dayjs: 1.10.8
dockerode: 3.3.1
dotenv-extended: 2.9.0
eslint: 7.32.0
eslint-config-prettier: 8.4.0
eslint-plugin-svelte3: 3.4.0
eslint-plugin-svelte3: 3.4.1
generate-password: 1.7.0
get-port: 6.1.1
get-port: 6.1.2
got: 12.0.1
husky: 7.0.4
js-cookie: 3.0.1
js-yaml: 4.1.0
jsonwebtoken: 8.5.1
lint-staged: 12.3.4
mustache: ^4.2.0
node-forge: 1.2.1
postcss: 8.4.6
postcss: 8.4.7
prettier: 2.5.1
prettier-plugin-svelte: 2.6.0
prettier-plugin-tailwindcss: 0.1.7
prettier-plugin-tailwindcss: 0.1.8
prisma: 3.10.0
svelte: 3.46.4
svelte-check: 2.4.5
@@ -47,64 +48,65 @@ specifiers:
svelte-preprocess: 4.10.4
tailwindcss: 3.0.23
tailwindcss-scrollbar: ^0.1.0
ts-node: 10.5.0
ts-node: 10.6.0
tslib: 2.3.1
typescript: 4.5.5
typescript: 4.6.2
unique-names-generator: 4.7.1
dependencies:
'@iarna/toml': 2.2.5
'@prisma/client': 3.10.0_prisma@3.10.0
'@sentry/node': 6.17.9
'@sentry/node': 6.18.1
bcrypt: 5.0.1
bullmq: 1.74.2
bullmq: 1.76.0
compare-versions: 4.1.3
cookie: 0.4.2
cuid: 2.1.8
dayjs: 1.10.7
dayjs: 1.10.8
dockerode: 3.3.1
dotenv-extended: 2.9.0
generate-password: 1.7.0
get-port: 6.1.1
get-port: 6.1.2
got: 12.0.1
js-cookie: 3.0.1
js-yaml: 4.1.0
jsonwebtoken: 8.5.1
mustache: 4.2.0
node-forge: 1.2.1
svelte-kit-cookie-session: 2.1.2
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.23
unique-names-generator: 4.7.1
devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.69
'@sveltejs/adapter-node': 1.0.0-next.70
'@sveltejs/adapter-static': 1.0.0-next.28
'@sveltejs/kit': 1.0.0-next.283_svelte@3.46.4
'@sveltejs/kit': 1.0.0-next.288_svelte@3.46.4
'@types/bcrypt': 5.0.0
'@types/js-cookie': 3.0.1
'@types/node': 17.0.20
'@types/node': 17.0.21
'@types/node-forge': 1.0.0
'@typescript-eslint/eslint-plugin': 4.31.1_5d7752337e5ea49772097d8af1823bf9
'@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5
'@typescript-eslint/eslint-plugin': 4.31.1_386b67ad67ef29c6a0ccaf3e9b60f945
'@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.6.2
'@zerodevx/svelte-toast': 0.7.0
autoprefixer: 10.4.2_postcss@8.4.6
autoprefixer: 10.4.2_postcss@8.4.7
cross-var: 1.1.0
eslint: 7.32.0
eslint-config-prettier: 8.4.0_eslint@7.32.0
eslint-plugin-svelte3: 3.4.0_eslint@7.32.0+svelte@3.46.4
eslint-plugin-svelte3: 3.4.1_eslint@7.32.0+svelte@3.46.4
husky: 7.0.4
lint-staged: 12.3.4
postcss: 8.4.6
postcss: 8.4.7
prettier: 2.5.1
prettier-plugin-svelte: 2.6.0_prettier@2.5.1+svelte@3.46.4
prettier-plugin-tailwindcss: 0.1.7_prettier@2.5.1
prettier-plugin-tailwindcss: 0.1.8_prettier@2.5.1
prisma: 3.10.0
svelte: 3.46.4
svelte-check: 2.4.5_postcss@8.4.6+svelte@3.46.4
svelte-preprocess: 4.10.4_88b359da5cac6d8f6ee1bbb7080a3fa9
tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
ts-node: 10.5.0_e04e69b201f218c8d0d59acefc9ea8a6
svelte-check: 2.4.5_postcss@8.4.7+svelte@3.46.4
svelte-preprocess: 4.10.4_e836cbb8ceb5bfaa7513362dd6308834
tailwindcss: 3.0.23_4b9e11f8e85900587b5e2272c5d4c20c
ts-node: 10.6.0_e79e62fe450383fd2d418267dc75e645
tslib: 2.3.1
typescript: 4.5.5
typescript: 4.6.2
packages:
/@babel/code-frame/7.12.11:
@@ -295,56 +297,56 @@ packages:
picomatch: 2.3.0
dev: true
/@sentry/core/6.17.9:
/@sentry/core/6.18.1:
resolution:
{
integrity: sha512-14KalmTholGUtgdh9TklO+jUpyQ/D3OGkhlH1rnGQGoJgFy2eYm+s+MnUEMxFdGIUCz5kOteuNqYZxaDmFagpQ==
integrity: sha512-9V8Q+3Asi+3RL67CSIMMZ9mjMsu2/hrpQszYStX7hPPpAZIlAKk2MT5B+na/r80iWKhy+3Ts6aDFF218QtnsVw==
}
engines: { node: '>=6' }
dependencies:
'@sentry/hub': 6.17.9
'@sentry/minimal': 6.17.9
'@sentry/types': 6.17.9
'@sentry/utils': 6.17.9
'@sentry/hub': 6.18.1
'@sentry/minimal': 6.18.1
'@sentry/types': 6.18.1
'@sentry/utils': 6.18.1
tslib: 1.14.1
dev: false
/@sentry/hub/6.17.9:
/@sentry/hub/6.18.1:
resolution:
{
integrity: sha512-34EdrweWDbBV9EzEFIXcO+JeoyQmKzQVJxpTKZoJA6PUwf2NrndaUdjlkDEtBEzjuLUTxhLxtOzEsYs1O6RVcg==
integrity: sha512-+zGzgc/xX3an/nKA3ELMn9YD9VmqbNaNwWZ5/SjNUvzsYHh2UNZ7YzT8WawQsRVOXLljyCKxkWpFB4EchiYGbw==
}
engines: { node: '>=6' }
dependencies:
'@sentry/types': 6.17.9
'@sentry/utils': 6.17.9
'@sentry/types': 6.18.1
'@sentry/utils': 6.18.1
tslib: 1.14.1
dev: false
/@sentry/minimal/6.17.9:
/@sentry/minimal/6.18.1:
resolution:
{
integrity: sha512-T3PMCHcKk6lkZq6zKgANrYJJxXBXKOe+ousV1Fas1rVBMv7dtKfsa4itqQHszcW9shusPDiaQKIJ4zRLE5LKmg==
integrity: sha512-dm+0MuasWNi/LASvHX+09oCo8IBZY5WpMK8qXvQMnwQ9FVfklrjcfEI3666WORDCmeUhDCSeL2MbjPDm+AmPLg==
}
engines: { node: '>=6' }
dependencies:
'@sentry/hub': 6.17.9
'@sentry/types': 6.17.9
'@sentry/hub': 6.18.1
'@sentry/types': 6.18.1
tslib: 1.14.1
dev: false
/@sentry/node/6.17.9:
/@sentry/node/6.18.1:
resolution:
{
integrity: sha512-jbn+q7qPGOh6D7nYoYGaAlmuvMDpQmyMwBtUVYybuZp2AALe43O3Z4LtoJ+1+F31XowpsIPZx1mwNs4ZrILskA==
integrity: sha512-aTb2gwfZUq0lGDRGH5zNOYDfFMOQZu6E0QcAsvH2ZBcEj3rUWZz3r25COFrHmfzHLUV1KcF2AmnWo1QU1jmm0g==
}
engines: { node: '>=6' }
dependencies:
'@sentry/core': 6.17.9
'@sentry/hub': 6.17.9
'@sentry/tracing': 6.17.9
'@sentry/types': 6.17.9
'@sentry/utils': 6.17.9
'@sentry/core': 6.18.1
'@sentry/hub': 6.18.1
'@sentry/tracing': 6.18.1
'@sentry/types': 6.18.1
'@sentry/utils': 6.18.1
cookie: 0.4.2
https-proxy-agent: 5.0.0
lru_map: 0.3.3
@@ -353,36 +355,36 @@ packages:
- supports-color
dev: false
/@sentry/tracing/6.17.9:
/@sentry/tracing/6.18.1:
resolution:
{
integrity: sha512-5Rb/OS4ryNJLvz2nv6wyjwhifjy6veqaF9ffLrwFYij/WDy7m62ASBblxgeiI3fbPLX0aBRFWIJAq1vko26+AQ==
integrity: sha512-OxozmSfxGx246Ae1XhO01I7ZWxO3briwMBh55E5KyjQb8fuS9gVE7Uy8ZRs5hhNjDutFAU7nMtC0zipfVxP6fg==
}
engines: { node: '>=6' }
dependencies:
'@sentry/hub': 6.17.9
'@sentry/minimal': 6.17.9
'@sentry/types': 6.17.9
'@sentry/utils': 6.17.9
'@sentry/hub': 6.18.1
'@sentry/minimal': 6.18.1
'@sentry/types': 6.18.1
'@sentry/utils': 6.18.1
tslib: 1.14.1
dev: false
/@sentry/types/6.17.9:
/@sentry/types/6.18.1:
resolution:
{
integrity: sha512-xuulX6qUCL14ayEOh/h6FUIvZtsi1Bx34dSOaWDrjXUOJHJAM7214uiqW1GZxPJ13YuaUIubjTSfDmSQ9CBzTw==
integrity: sha512-wp741NoBKnXE/4T9L723sWJ8EcNMxeTIT1smgNJOfbPwrsDICoYmGEt6JFa05XHpWBGI66WuNvnDjoHVeh6zhA==
}
engines: { node: '>=6' }
dev: false
/@sentry/utils/6.17.9:
/@sentry/utils/6.18.1:
resolution:
{
integrity: sha512-4eo9Z3JlJCGlGrQRbtZWL+L9NnlUXgTbfK3Lk7oO8D1ev8R5b5+iE6tZHTvU5rQRcq6zu+POT+tK5u9oxc/rnQ==
integrity: sha512-IFZmuvA+c5lDGlZEri13JSyUP0BHelzY0S4dcKxAzskPW+BtBdQDgYGV90iED1y+IRMLawWb34GF7HyJSouN1Q==
}
engines: { node: '>=6' }
dependencies:
'@sentry/types': 6.17.9
'@sentry/types': 6.18.1
tslib: 1.14.1
dev: false
@@ -394,10 +396,10 @@ packages:
engines: { node: '>=10' }
dev: false
/@sveltejs/adapter-node/1.0.0-next.69:
/@sveltejs/adapter-node/1.0.0-next.70:
resolution:
{
integrity: sha512-tVKwJ8vYG4NGFJ5L+tRuyRglGPaJ1khNqTKq4bYIUahk/pjXIu9USbMmFtNHd6IyDdxjPtRoVwHubAYfTQLTpg==
integrity: sha512-9EtqSTxVOipESyPQH8uVdkl+iSqsq3oRKS3RZoE2vAG9I849MuTTxr1g9L4kALbXaklsYRztwhmrLpTdcSM4vA==
}
dependencies:
tiny-glob: 0.2.9
@@ -412,10 +414,10 @@ packages:
tiny-glob: 0.2.9
dev: true
/@sveltejs/kit/1.0.0-next.283_svelte@3.46.4:
/@sveltejs/kit/1.0.0-next.288_svelte@3.46.4:
resolution:
{
integrity: sha512-QFhL7cdBKXUKhJ3sHZnL7q07Eohc54N7OpX2ZsJuNTA43Z6vX/HF/Jh4TK62kqT+thDVlT5J1iEEMppxA2IxoQ==
integrity: sha512-6ky4CUFNGKoU1QV+fY2LOIB7atHdUCIMRx3pX21B2g9yZzQzkot0zHBrMMBSKF0/4Wx19PLEWrlGxlhakYmi8Q==
}
engines: { node: '>=14.13' }
hasBin: true
@@ -504,7 +506,7 @@ packages:
integrity: sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==
}
dependencies:
'@types/node': 17.0.20
'@types/node': 17.0.21
dev: true
/@types/cacheable-request/6.0.2:
@@ -515,7 +517,7 @@ packages:
dependencies:
'@types/http-cache-semantics': 4.0.1
'@types/keyv': 3.1.3
'@types/node': 17.0.20
'@types/node': 17.0.21
'@types/responselike': 1.0.0
dev: false
@@ -546,7 +548,7 @@ packages:
integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==
}
dependencies:
'@types/node': 17.0.20
'@types/node': 17.0.21
dev: false
/@types/node-forge/1.0.0:
@@ -555,13 +557,13 @@ packages:
integrity: sha512-h0bgwPKq5u99T9Gor4qtV1lCZ41xNkai0pie1n/a2mh2/4+jENWOlo7AJ4YKxTZAnSZ8FRurUpdIN7ohaPPuHA==
}
dependencies:
'@types/node': 17.0.20
'@types/node': 17.0.21
dev: true
/@types/node/17.0.20:
/@types/node/17.0.21:
resolution:
{
integrity: sha512-Q15Clj3lZSLnhVA6yKw1G7SQz46DeL9gO1TEgfK1OQGvMdQ6TUWmCeWf1QBUNkw2BDfV52i2YuYd9OF3ZwGhjw==
integrity: sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==
}
/@types/parse-json/4.0.0:
@@ -584,7 +586,7 @@ packages:
integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
}
dependencies:
'@types/node': 17.0.20
'@types/node': 17.0.21
dev: false
/@types/sass/1.16.1:
@@ -593,10 +595,10 @@ packages:
integrity: sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==
}
dependencies:
'@types/node': 17.0.20
'@types/node': 17.0.21
dev: true
/@typescript-eslint/eslint-plugin/4.31.1_5d7752337e5ea49772097d8af1823bf9:
/@typescript-eslint/eslint-plugin/4.31.1_386b67ad67ef29c6a0ccaf3e9b60f945:
resolution:
{
integrity: sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==
@@ -610,21 +612,21 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/experimental-utils': 4.31.1_eslint@7.32.0+typescript@4.5.5
'@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.5.5
'@typescript-eslint/experimental-utils': 4.31.1_eslint@7.32.0+typescript@4.6.2
'@typescript-eslint/parser': 4.31.1_eslint@7.32.0+typescript@4.6.2
'@typescript-eslint/scope-manager': 4.31.1
debug: 4.3.3
eslint: 7.32.0
functional-red-black-tree: 1.0.1
regexpp: 3.2.0
semver: 7.3.5
tsutils: 3.21.0_typescript@4.5.5
typescript: 4.5.5
tsutils: 3.21.0_typescript@4.6.2
typescript: 4.6.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/experimental-utils/4.31.1_eslint@7.32.0+typescript@4.5.5:
/@typescript-eslint/experimental-utils/4.31.1_eslint@7.32.0+typescript@4.6.2:
resolution:
{
integrity: sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==
@@ -636,7 +638,7 @@ packages:
'@types/json-schema': 7.0.9
'@typescript-eslint/scope-manager': 4.31.1
'@typescript-eslint/types': 4.31.1
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.5.5
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.2
eslint: 7.32.0
eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@7.32.0
@@ -645,7 +647,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/parser/4.31.1_eslint@7.32.0+typescript@4.5.5:
/@typescript-eslint/parser/4.31.1_eslint@7.32.0+typescript@4.6.2:
resolution:
{
integrity: sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==
@@ -660,10 +662,10 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 4.31.1
'@typescript-eslint/types': 4.31.1
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.5.5
'@typescript-eslint/typescript-estree': 4.31.1_typescript@4.6.2
debug: 4.3.3
eslint: 7.32.0
typescript: 4.5.5
typescript: 4.6.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -687,7 +689,7 @@ packages:
engines: { node: ^8.10.0 || ^10.13.0 || >=11.10.1 }
dev: true
/@typescript-eslint/typescript-estree/4.31.1_typescript@4.5.5:
/@typescript-eslint/typescript-estree/4.31.1_typescript@4.6.2:
resolution:
{
integrity: sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==
@@ -705,8 +707,8 @@ packages:
globby: 11.0.4
is-glob: 4.0.3
semver: 7.3.5
tsutils: 3.21.0_typescript@4.5.5
typescript: 4.5.5
tsutils: 3.21.0_typescript@4.6.2
typescript: 4.6.2
transitivePeerDependencies:
- supports-color
dev: true
@@ -1008,7 +1010,7 @@ packages:
typpy: 2.3.11
dev: false
/autoprefixer/10.4.2_postcss@8.4.6:
/autoprefixer/10.4.2_postcss@8.4.7:
resolution:
{
integrity: sha512-9fOPpHKuDW1w/0EKfRmVnxTDt8166MAnLI3mgZ1JCnhNtYWxcJ6Ud5CO/AVOZi/AvFa8DY9RTy3h3+tFBlrrdQ==
@@ -1023,7 +1025,7 @@ packages:
fraction.js: 4.1.2
normalize-range: 0.1.2
picocolors: 1.0.0
postcss: 8.4.6
postcss: 8.4.7
postcss-value-parser: 4.2.0
dev: true
@@ -1748,10 +1750,10 @@ packages:
ieee754: 1.2.1
dev: false
/bullmq/1.74.2:
/bullmq/1.76.0:
resolution:
{
integrity: sha512-qf0xjF3NDbZqi6ovxAA9QPrPdEgl3BrdYzQO5Pr+ECNIfscLpSXz87kVXI0oTPeDqxw5EJx4sh8EFf2RvVPYTg==
integrity: sha512-e9No/JiE7B+wnLsoZnVBLTKTfc1An3bnuIaWewhjDImYNFq5aU/YmzoCdJRgUXrRWGc3EnATFSOLHiEY6Z2FpA==
}
dependencies:
cron-parser: 2.18.0
@@ -2136,10 +2138,10 @@ packages:
}
dev: false
/dayjs/1.10.7:
/dayjs/1.10.8:
resolution:
{
integrity: sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
integrity: sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==
}
dev: false
@@ -2718,10 +2720,10 @@ packages:
eslint: 7.32.0
dev: true
/eslint-plugin-svelte3/3.4.0_eslint@7.32.0+svelte@3.46.4:
/eslint-plugin-svelte3/3.4.1_eslint@7.32.0+svelte@3.46.4:
resolution:
{
integrity: sha512-MIQUTuRv3o7LyQ+360qOc9mLT35j1I5YzHr04g/UDcvJTpg0X/kHWELY99ve869Rp/9wjqD7I26Aq5H8OH5RIg==
integrity: sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw==
}
engines: { node: '>=10' }
peerDependencies:
@@ -3116,10 +3118,10 @@ packages:
engines: { node: '>=8' }
dev: false
/get-port/6.1.1:
/get-port/6.1.2:
resolution:
{
integrity: sha512-RQOsDPSd2PcoLwakY1dwEtLiAbTR7IfmnxsKswfcHEfRKKbhWAG2R5Qo7C8ga6Ne4Mq4lFbogXfDGNfqFxwAaw==
integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==
}
engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
dev: false
@@ -4091,6 +4093,14 @@ packages:
msgpackr-extract: 1.0.15
dev: false
/mustache/4.2.0:
resolution:
{
integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
}
hasBin: true
dev: false
/nan/2.15.0:
resolution:
{
@@ -4099,10 +4109,10 @@ packages:
dev: false
optional: true
/nanoid/3.2.0:
/nanoid/3.3.1:
resolution:
{
integrity: sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
}
engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 }
hasBin: true
@@ -4372,7 +4382,7 @@ packages:
engines: { node: '>=8.6' }
dev: true
/postcss-js/4.0.0_postcss@8.4.6:
/postcss-js/4.0.0_postcss@8.4.7:
resolution:
{
integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
@@ -4382,10 +4392,10 @@ packages:
postcss: ^8.3.3
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.6
postcss: 8.4.7
dev: true
/postcss-load-config/3.1.0_ts-node@10.5.0:
/postcss-load-config/3.1.0_ts-node@10.6.0:
resolution:
{
integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==
@@ -4399,11 +4409,11 @@ packages:
dependencies:
import-cwd: 3.0.0
lilconfig: 2.0.4
ts-node: 10.5.0_e04e69b201f218c8d0d59acefc9ea8a6
ts-node: 10.6.0_e79e62fe450383fd2d418267dc75e645
yaml: 1.10.2
dev: true
/postcss-nested/5.0.6_postcss@8.4.6:
/postcss-nested/5.0.6_postcss@8.4.7:
resolution:
{
integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
@@ -4412,7 +4422,7 @@ packages:
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss: 8.4.6
postcss: 8.4.7
postcss-selector-parser: 6.0.9
dev: true
@@ -4434,14 +4444,14 @@ packages:
}
dev: true
/postcss/8.4.6:
/postcss/8.4.7:
resolution:
{
integrity: sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
integrity: sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==
}
engines: { node: ^10 || ^12 || >=14 }
dependencies:
nanoid: 3.2.0
nanoid: 3.3.1
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
@@ -4467,10 +4477,10 @@ packages:
svelte: 3.46.4
dev: true
/prettier-plugin-tailwindcss/0.1.7_prettier@2.5.1:
/prettier-plugin-tailwindcss/0.1.8_prettier@2.5.1:
resolution:
{
integrity: sha512-tmBr45hCLuit2Cz9Pwow0/Jl1bGivYGsfcF29O+3sKcE++ybjz9dfie565S3ZsvAeV8uYer9SRMBWDsHPly2Lg==
integrity: sha512-hwarSBCswAXa+kqYtaAkFr3Vop9o04WOyZs0qo3NyvW8L7f1rif61wRyq0+ArmVThOuRBcJF5hjGXYk86cwemg==
}
engines: { node: '>=12.17.0' }
peerDependencies:
@@ -5162,7 +5172,7 @@ packages:
engines: { node: '>= 0.4' }
dev: true
/svelte-check/2.4.5_postcss@8.4.6+svelte@3.46.4:
/svelte-check/2.4.5_postcss@8.4.7+svelte@3.46.4:
resolution:
{
integrity: sha512-nRft8BbG2wcxyCdHDZ7X43xLcvDzua3xLwq6wzHGcAF3ka3Jyhv2rvgq0+SF9NwHLMefp9C2XkM6etzsxK/cMQ==
@@ -5179,8 +5189,8 @@ packages:
sade: 1.7.4
source-map: 0.7.3
svelte: 3.46.4
svelte-preprocess: 4.10.4_88b359da5cac6d8f6ee1bbb7080a3fa9
typescript: 4.5.5
svelte-preprocess: 4.10.4_e836cbb8ceb5bfaa7513362dd6308834
typescript: 4.6.2
transitivePeerDependencies:
- '@babel/core'
- coffeescript
@@ -5212,7 +5222,7 @@ packages:
}
dev: false
/svelte-preprocess/4.10.4_88b359da5cac6d8f6ee1bbb7080a3fa9:
/svelte-preprocess/4.10.4_e836cbb8ceb5bfaa7513362dd6308834:
resolution:
{
integrity: sha512-fuwol0N4UoHsNQolLFbMqWivqcJ9N0vfWO9IuPAiX/5okfoGXURyJ6nECbuEIv0nU3M8Xe2I1ONNje2buk7l6A==
@@ -5260,11 +5270,11 @@ packages:
'@types/sass': 1.16.1
detect-indent: 6.1.0
magic-string: 0.25.7
postcss: 8.4.6
postcss: 8.4.7
sorcery: 0.10.0
strip-indent: 3.0.0
svelte: 3.46.4
typescript: 4.5.5
typescript: 4.6.2
dev: true
/svelte/3.46.4:
@@ -5298,10 +5308,10 @@ packages:
peerDependencies:
tailwindcss: '>= 2.x.x'
dependencies:
tailwindcss: 3.0.23_c940fbabf228b85b1c73d314b43e31f1
tailwindcss: 3.0.23_4b9e11f8e85900587b5e2272c5d4c20c
dev: false
/tailwindcss/3.0.23_c940fbabf228b85b1c73d314b43e31f1:
/tailwindcss/3.0.23_4b9e11f8e85900587b5e2272c5d4c20c:
resolution:
{
integrity: sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
@@ -5312,7 +5322,7 @@ packages:
autoprefixer: ^10.0.2
dependencies:
arg: 5.0.1
autoprefixer: 10.4.2_postcss@8.4.6
autoprefixer: 10.4.2_postcss@8.4.7
chalk: 4.1.2
chokidar: 3.5.3
color-name: 1.1.4
@@ -5325,10 +5335,10 @@ packages:
is-glob: 4.0.3
normalize-path: 3.0.0
object-hash: 2.2.0
postcss: 8.4.6
postcss-js: 4.0.0_postcss@8.4.6
postcss-load-config: 3.1.0_ts-node@10.5.0
postcss-nested: 5.0.6_postcss@8.4.6
postcss: 8.4.7
postcss-js: 4.0.0_postcss@8.4.7
postcss-load-config: 3.1.0_ts-node@10.6.0
postcss-nested: 5.0.6_postcss@8.4.7
postcss-selector-parser: 6.0.9
postcss-value-parser: 4.2.0
quick-lru: 5.1.1
@@ -5420,10 +5430,10 @@ packages:
engines: { node: '>=0.10.0' }
dev: true
/ts-node/10.5.0_e04e69b201f218c8d0d59acefc9ea8a6:
/ts-node/10.6.0_e79e62fe450383fd2d418267dc75e645:
resolution:
{
integrity: sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw==
integrity: sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==
}
hasBin: true
peerDependencies:
@@ -5442,14 +5452,14 @@ packages:
'@tsconfig/node12': 1.0.9
'@tsconfig/node14': 1.0.1
'@tsconfig/node16': 1.0.2
'@types/node': 17.0.20
'@types/node': 17.0.21
acorn: 8.5.0
acorn-walk: 8.2.0
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 4.5.5
typescript: 4.6.2
v8-compile-cache-lib: 3.0.0
yn: 3.1.1
dev: true
@@ -5467,7 +5477,7 @@ packages:
}
dev: true
/tsutils/3.21.0_typescript@4.5.5:
/tsutils/3.21.0_typescript@4.6.2:
resolution:
{
integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
@@ -5477,7 +5487,7 @@ packages:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 4.5.5
typescript: 4.6.2
dev: true
/tweetnacl/0.14.5:
@@ -5510,10 +5520,10 @@ packages:
engines: { node: '>=10' }
dev: true
/typescript/4.5.5:
/typescript/4.6.2:
resolution:
{
integrity: sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
integrity: sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
}
engines: { node: '>=4.2.0' }
hasBin: true
@@ -5590,7 +5600,7 @@ packages:
optional: true
dependencies:
esbuild: 0.14.21
postcss: 8.4.6
postcss: 8.4.7
resolve: 1.22.0
rollup: 2.61.1
optionalDependencies:

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "proxyHash" TEXT;

View File

@@ -0,0 +1,13 @@
-- CreateTable
CREATE TABLE "ServiceSecret" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"value" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"serviceId" TEXT NOT NULL,
CONSTRAINT "ServiceSecret_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "ServiceSecret_name_serviceId_key" ON "ServiceSecret"("name", "serviceId");

View File

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

View File

@@ -16,6 +16,7 @@ model Setting {
maxPort Int @default(9100)
proxyPassword String
proxyUser String
proxyHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -103,6 +104,7 @@ model ApplicationSettings {
dualCerts Boolean @default(false)
debug Boolean @default(false)
previews Boolean @default(false)
autodeploy Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -121,6 +123,18 @@ model Secret {
@@unique([name, applicationId, isPRMRSecret])
}
model ServiceSecret {
id String @id @default(cuid())
name String
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
serviceId String
@@unique([name, serviceId])
}
model BuildLog {
id String @id @default(cuid())
applicationId String?
@@ -251,6 +265,7 @@ model Service {
minio Minio?
vscodeserver Vscodeserver?
wordpress Wordpress?
serviceSecret ServiceSecret[]
}
model PlausibleAnalytics {

View File

@@ -27,6 +27,15 @@ async function main() {
proxyUser: cuid()
}
});
} else {
await prisma.setting.update({
where: {
id: settingsFound.id
},
data: {
proxyHash: null
}
});
}
const localDocker = await prisma.destinationDocker.findFirst({
where: { engine: '/var/run/docker.sock' }

View File

@@ -84,7 +84,15 @@ export function makeLabelForServices(type) {
}
export const setDefaultConfiguration = async (data) => {
let { buildPack, port, installCommand, startCommand, buildCommand, publishDirectory } = data;
let {
buildPack,
port,
installCommand,
startCommand,
buildCommand,
publishDirectory,
baseDirectory
} = data;
const template = scanningTemplates[buildPack];
if (!port) {
port = template?.port || 3000;
@@ -97,6 +105,10 @@ export const setDefaultConfiguration = async (data) => {
if (!startCommand) startCommand = template?.startCommand || 'yarn start';
if (!buildCommand) buildCommand = template?.buildCommand || null;
if (!publishDirectory) publishDirectory = template?.publishDirectory || null;
if (baseDirectory) {
if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`;
if (!baseDirectory.endsWith('/')) baseDirectory = `${baseDirectory}/`;
}
return {
buildPack,
@@ -104,7 +116,8 @@ export const setDefaultConfiguration = async (data) => {
installCommand,
startCommand,
buildCommand,
publishDirectory
publishDirectory,
baseDirectory
};
};
@@ -175,3 +188,11 @@ export async function copyBaseConfigurationFiles(buildPack, workdir, buildId, ap
throw new Error(error);
}
}
export function checkPnpm(installCommand = null, buildCommand = null, startCommand = null) {
return (
installCommand?.includes('pnpm') ||
buildCommand?.includes('pnpm') ||
startCommand?.includes('pnpm')
);
}

View File

@@ -16,6 +16,7 @@ export default async function ({
let file = `${workdir}/Dockerfile`;
if (baseDirectory) {
file = `${workdir}/${baseDirectory}/Dockerfile`;
workdir = `${workdir}/${baseDirectory}`;
}
const Dockerfile: Array<string> = (await fs.readFile(`${file}`, 'utf8'))

View File

@@ -4,13 +4,19 @@ import { promises as fs } from 'fs';
const createDockerfile = async (data, image): Promise<void> => {
const { applicationId, tag, port, startCommand, workdir, baseDirectory } = data;
const Dockerfile: Array<string> = [];
const isPnpm = startCommand.includes('pnpm');
Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`);
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./`
);
Dockerfile.push(`EXPOSE ${port}`);
Dockerfile.push(`CMD ${startCommand}`);
await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n'));

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,7 +14,7 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = [];
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`);
@@ -32,17 +33,13 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try {
await fs.stat(`${workdir}/yarn.lock`);
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
} catch (error) {}
try {
await fs.stat(`${workdir}/pnpm-lock.yaml`);
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
} catch (error) {}
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
if (buildCommand) {
Dockerfile.push(`RUN ${buildCommand}`);
}

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,6 +14,7 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = [];
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app');
@@ -32,17 +34,12 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try {
await fs.stat(`${workdir}/yarn.lock`);
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
} catch (error) {}
try {
await fs.stat(`${workdir}/pnpm-lock.yaml`);
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
} catch (error) {}
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
if (buildCommand) {
Dockerfile.push(`RUN ${buildCommand}`);
}

View File

@@ -1,5 +1,6 @@
import { buildImage } from '$lib/docker';
import { promises as fs } from 'fs';
import { checkPnpm } from './common';
const createDockerfile = async (data, image): Promise<void> => {
const {
@@ -13,7 +14,7 @@ const createDockerfile = async (data, image): Promise<void> => {
pullmergeRequestId
} = data;
const Dockerfile: Array<string> = [];
const isPnpm = checkPnpm(installCommand, buildCommand, startCommand);
Dockerfile.push(`FROM ${image}`);
Dockerfile.push('WORKDIR /usr/src/app');
Dockerfile.push(`LABEL coolify.image=true`);
@@ -32,17 +33,12 @@ const createDockerfile = async (data, image): Promise<void> => {
}
});
}
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try {
await fs.stat(`${workdir}/yarn.lock`);
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
} catch (error) {}
try {
await fs.stat(`${workdir}/pnpm-lock.yaml`);
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
} catch (error) {}
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${installCommand}`);
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
if (buildCommand) {
Dockerfile.push(`RUN ${buildCommand}`);
}

View File

@@ -9,7 +9,7 @@ const createDockerfile = async (data, image): Promise<void> => {
Dockerfile.push(`LABEL coolify.image=true`);
Dockerfile.push('RUN a2enmod rewrite');
Dockerfile.push('WORKDIR /var/www/html');
Dockerfile.push(`COPY ./${baseDirectory || ''} /var/www/html`);
Dockerfile.push(`COPY .${baseDirectory || ''} /var/www/html`);
Dockerfile.push(`EXPOSE 80`);
Dockerfile.push('CMD ["apache2-foreground"]');
Dockerfile.push('RUN chown -R www-data /var/www/html');

View File

@@ -37,7 +37,7 @@ const createDockerfile = async (data, image): Promise<void> => {
`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`
);
} else {
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
}
Dockerfile.push(`EXPOSE 80`);
Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]');

View File

@@ -11,6 +11,7 @@ import { version as currentVersion } from '../../package.json';
import dayjs from 'dayjs';
import Cookie from 'cookie';
import os from 'os';
import cuid from 'cuid';
try {
if (!dev) {
@@ -68,9 +69,9 @@ export const isTeamIdTokenAvailable = (request) => {
export const getTeam = (event) => {
const cookies = Cookie.parse(event.request.headers.get('cookie'));
if (cookies.teamId) {
if (cookies?.teamId) {
return cookies.teamId;
} else if (event.locals.session.data.teamId) {
} else if (event.locals.session.data?.teamId) {
return event.locals.session.data.teamId;
}
return null;

View File

@@ -75,7 +75,7 @@
/>
{/if}
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
<div class="absolute top-0 right-0 m-3 cursor-pointer text-stone-600 hover:text-white">
<div class="flex space-x-2">
{#if isPasswordField}
<div on:click={() => (showPassword = !showPassword)}>

View File

@@ -0,0 +1,27 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
fill="none"
viewBox="0 0 140 140"
data-lt-extension-installed="true"
><g clip-path="url(#clip0)"
><path
fill="#fff"
fill-rule="evenodd"
d="M140 43.602c0-1.662.001-3.324-.01-4.987-.008-1.4-.024-2.8-.062-4.2-.082-3.05-.262-6.126-.805-9.142-.55-3.06-1.448-5.907-2.864-8.688A29.227 29.227 0 0 0 123.476 3.81c-2.783-1.416-5.634-2.314-8.697-2.864-3.016-.542-6.094-.722-9.144-.804-1.4-.038-2.801-.054-4.202-.063C99.77.068 98.107.07 96.444.07L77.135 0H62.694L43.726.07c-1.666 0-3.332-.002-4.998.008-1.404.01-2.807.025-4.21.063-3.058.082-6.142.262-9.166.805-3.067.55-5.922 1.447-8.709 2.862a29.293 29.293 0 0 0-7.419 5.377 29.223 29.223 0 0 0-5.389 7.4c-1.42 2.78-2.32 5.63-2.871 8.691-.543 3.016-.723 6.091-.806 9.14-.038 1.4-.054 2.8-.062 4.2C.086 40.277 0 42.342 0 44.004v33.3l.086 19.102c0 1.665 0 3.33.01 4.994a200.6 200.6 0 0 0 .062 4.205c.083 3.054.263 6.135.807 9.155.551 3.064 1.451 5.916 2.87 8.7a29.294 29.294 0 0 0 12.807 12.794c2.788 1.418 5.645 2.317 8.714 2.868 3.022.542 6.105.722 9.162.804 1.403.038 2.806.054 4.21.063 1.666.01 3.332.009 4.998.009l19.14.001h14.477l19.101-.001c1.663 0 3.326.001 4.989-.009a202.92 202.92 0 0 0 4.202-.063c3.052-.082 6.13-.262 9.148-.805 3.061-.551 5.911-1.45 8.692-2.867a29.215 29.215 0 0 0 7.405-5.384 29.22 29.22 0 0 0 5.378-7.409c1.417-2.785 2.315-5.639 2.866-8.704.542-3.02.722-6.099.804-9.152.038-1.402.054-2.804.062-4.205.011-1.665.01-3.33.01-4.993l-.001-19.103V62.694L140 43.602"
clip-rule="evenodd"
/><path
fill="#000"
fill-rule="evenodd"
d="M39.375 40.188h8.313a6.25 6.25 0 0 1 6.25 6.25v24.25h16.25v8.75h-18.75a6.25 6.25 0 0 1-6.25-6.25v-24.25h-5.813v-8.75zm63.563 6.25v6.5h-8.75v-4h-6.876v30.5h-8.75v-30.5h-6.874v4h-8.75v-6.5a6.25 6.25 0 0 1 6.25-6.25h27.5a6.25 6.25 0 0 1 6.25 6.25z"
clip-rule="evenodd"
/><path
fill="#239AFF"
d="M35.319 102.906l-8.138-5.812c2.39-3.347 4.857-5.936 7.452-7.753 2.884-2.018 5.948-3.091 9.117-3.091 2.942 0 5.491.714 7.768 2.08a17.622 17.622 0 0 1 2.615 1.94c.589.518 1.009.926 1.903 1.82 1.355 1.354 1.917 1.851 2.591 2.255.731.439 1.503.655 2.623.655 1.121 0 1.896-.217 2.631-.657.677-.405 1.245-.905 2.6-2.257l.012-.012c.89-.888 1.314-1.299 1.902-1.817a17.643 17.643 0 0 1 2.61-1.933c2.273-1.362 4.814-2.074 7.745-2.074s5.472.712 7.745 2.074c.916.55 1.758 1.183 2.61 1.933.589.518 1.013.929 1.902 1.817l.013.012c1.354 1.352 1.922 1.852 2.599 2.257.735.44 1.51.657 2.631.657.998 0 2.1-.386 3.383-1.284 1.572-1.1 3.272-2.886 5.048-5.372l8.138 5.812c-2.391 3.347-4.857 5.936-7.452 7.753-2.884 2.018-5.948 3.091-9.117 3.091-2.941 0-5.49-.713-7.769-2.078a17.627 17.627 0 0 1-2.619-1.938c-.59-.519-1.015-.93-1.906-1.82l-.013-.013c-1.351-1.348-1.917-1.846-2.59-2.25-.728-.436-1.494-.651-2.603-.651-1.109 0-1.875.215-2.603.651-.673.404-1.239.902-2.59 2.25l-.012.013c-.892.89-1.317 1.301-1.907 1.82-.855.752-1.7 1.388-2.62 1.938C66.74 104.287 64.192 105 61.25 105c-2.942 0-5.49-.714-7.768-2.08a17.654 17.654 0 0 1-2.615-1.939c-.588-.519-1.009-.927-1.902-1.82-1.355-1.355-1.918-1.852-2.592-2.256-.731-.439-1.503-.655-2.623-.655-.998 0-2.1.386-3.383 1.284-1.572 1.1-3.272 2.886-5.048 5.372z"
/></g
><defs><clipPath id="clip0"><path fill="#fff" d="M0 0h140v140H0z" /></clipPath></defs></svg
>

View File

@@ -13,7 +13,7 @@ export function findBuildPack(pack, packageManager = 'npm') {
if (pack === 'node') {
return {
...metaData,
installCommand: null,
...defaultBuildAndDeploy(packageManager),
buildCommand: null,
startCommand: null,
publishDirectory: null,

View File

@@ -1,5 +1,4 @@
import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy';
import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common';
@@ -59,29 +58,21 @@ export async function removeApplication({ id, teamId }) {
const id = containerObj.ID;
const preview = containerObj.Image.split('-')[1];
await removeDestinationDocker({ id, engine: destinationDocker.engine });
try {
if (preview) {
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
} else {
await removeProxyConfiguration({ domain });
}
} catch (error) {
console.log(error);
}
}
}
}
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
await prisma.build.deleteMany({ where: { applicationId: id } });
await prisma.secret.deleteMany({ where: { applicationId: id } });
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
}
export async function getApplicationWebhook({ projectId, branch }) {
try {
let body = await prisma.application.findFirst({
where: { projectId, branch },
let application = await prisma.application.findFirst({
where: { projectId, branch, settings: { autodeploy: true } },
include: {
destinationDocker: true,
settings: true,
@@ -89,30 +80,41 @@ export async function getApplicationWebhook({ projectId, branch }) {
secrets: true
}
});
if (body.gitSource?.githubApp?.clientSecret) {
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
if (!application) {
return null;
}
if (body.gitSource?.githubApp?.webhookSecret) {
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
if (application?.gitSource?.githubApp?.clientSecret) {
application.gitSource.githubApp.clientSecret = decrypt(
application.gitSource.githubApp.clientSecret
);
}
if (body.gitSource?.githubApp?.privateKey) {
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
if (application?.gitSource?.githubApp?.webhookSecret) {
application.gitSource.githubApp.webhookSecret = decrypt(
application.gitSource.githubApp.webhookSecret
);
}
if (body?.gitSource?.gitlabApp?.appSecret) {
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
if (application?.gitSource?.githubApp?.privateKey) {
application.gitSource.githubApp.privateKey = decrypt(
application.gitSource.githubApp.privateKey
);
}
if (body?.gitSource?.gitlabApp?.webhookToken) {
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken);
if (application?.gitSource?.gitlabApp?.appSecret) {
application.gitSource.gitlabApp.appSecret = decrypt(
application.gitSource.gitlabApp.appSecret
);
}
if (body?.secrets.length > 0) {
body.secrets = body.secrets.map((s) => {
if (application?.gitSource?.gitlabApp?.webhookToken) {
application.gitSource.gitlabApp.webhookToken = decrypt(
application.gitSource.gitlabApp.webhookToken
);
}
if (application?.secrets.length > 0) {
application.secrets = application.secrets.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
return { ...body };
return { ...application };
} catch (e) {
throw { status: 404, body: { message: e.message } };
}
@@ -136,13 +138,13 @@ export async function getApplication({ id, teamId }) {
}
});
if (body.gitSource?.githubApp?.clientSecret) {
if (body?.gitSource?.githubApp?.clientSecret) {
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
}
if (body.gitSource?.githubApp?.webhookSecret) {
if (body?.gitSource?.githubApp?.webhookSecret) {
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
}
if (body.gitSource?.githubApp?.privateKey) {
if (body?.gitSource?.githubApp?.privateKey) {
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
}
if (body?.gitSource?.gitlabApp?.appSecret) {
@@ -158,24 +160,41 @@ export async function getApplication({ id, teamId }) {
return { ...body };
}
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) {
export async function configureGitRepository({
id,
repository,
branch,
projectId,
webhookToken,
autodeploy
}) {
if (webhookToken) {
const encryptedWebhookToken = encrypt(webhookToken);
return await prisma.application.update({
await prisma.application.update({
where: { id },
data: {
repository,
branch,
projectId,
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } },
settings: { update: { autodeploy } }
}
});
} else {
return await prisma.application.update({
await prisma.application.update({
where: { id },
data: { repository, branch, projectId }
data: { repository, branch, projectId, settings: { update: { autodeploy } } }
});
}
if (!autodeploy) {
const applications = await prisma.application.findMany({ where: { branch, projectId } });
for (const application of applications) {
await prisma.applicationSettings.updateMany({
where: { applicationId: application.id },
data: { autodeploy: false }
});
}
}
}
export async function configureBuildPack({ id, buildPack }) {
@@ -210,10 +229,14 @@ export async function configureApplication({
});
}
export async function setApplicationSettings({ id, debug, previews, dualCerts }) {
export async function checkDoubleBranch(branch, projectId) {
const applications = await prisma.application.findMany({ where: { branch, projectId } });
return applications.length > 1;
}
export async function setApplicationSettings({ id, debug, previews, dualCerts, autodeploy }) {
return await prisma.application.update({
where: { id },
data: { settings: { update: { debug, previews, dualCerts } } },
data: { settings: { update: { debug, previews, dualCerts, autodeploy } } },
include: { destinationDocker: true }
});
}

View File

@@ -15,6 +15,9 @@ export async function isDockerNetworkExists({ network }) {
return await prisma.destinationDocker.findFirst({ where: { network } });
}
export async function isServiceSecretExists({ id, name }) {
return await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
}
export async function isSecretExists({ id, name, isPRMRSecret }) {
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
}

View File

@@ -156,6 +156,15 @@ export const supportedServiceTypesAndVersions = [
ports: {
main: 80
}
},
{
name: 'languagetool',
fancyName: 'LanguageTool',
baseImage: 'silviof/docker-languagetool',
versions: ['latest'],
ports: {
main: 8010
}
}
];

View File

@@ -1,6 +1,19 @@
import { encrypt, decrypt } from '$lib/crypto';
import { prisma } from './common';
export async function listServiceSecrets(serviceId: string) {
let secrets = await prisma.serviceSecret.findMany({
where: { serviceId },
orderBy: { createdAt: 'desc' }
});
secrets = secrets.map((secret) => {
secret.value = decrypt(secret.value);
return secret;
});
return secrets;
}
export async function listSecrets(applicationId: string) {
let secrets = await prisma.secret.findMany({
where: { applicationId },
@@ -14,6 +27,12 @@ export async function listSecrets(applicationId: string) {
return secrets;
}
export async function createServiceSecret({ id, name, value }) {
value = encrypt(value);
return await prisma.serviceSecret.create({
data: { name, value, service: { connect: { id } } }
});
}
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value);
return await prisma.secret.create({
@@ -21,10 +40,24 @@ export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecre
});
}
export async function updateServiceSecret({ id, name, value }) {
value = encrypt(value);
const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } });
if (found) {
return await prisma.serviceSecret.updateMany({
where: { serviceId: id, name },
data: { value }
});
} else {
return await prisma.serviceSecret.create({
data: { name, value, service: { connect: { id } } }
});
}
}
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
value = encrypt(value);
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
console.log(found);
if (found) {
return await prisma.secret.updateMany({
@@ -38,6 +71,10 @@ export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecre
}
}
export async function removeServiceSecret({ id, name }) {
return await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } });
}
export async function removeSecret({ id, name }) {
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
}

View File

@@ -19,7 +19,8 @@ export async function getService({ id, teamId }) {
plausibleAnalytics: true,
minio: true,
vscodeserver: true,
wordpress: true
wordpress: true,
serviceSecret: true
}
});
@@ -42,6 +43,12 @@ export async function getService({ id, teamId }) {
if (body.wordpress?.mysqlRootUserPassword)
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value);
return s;
});
}
return { ...body };
}
@@ -105,6 +112,13 @@ export async function configureServiceType({ id, type }) {
type
}
});
} else if (type === 'languagetool') {
await prisma.service.update({
where: { id },
data: {
type
}
});
}
}
export async function setServiceVersion({ id, version }) {
@@ -128,6 +142,9 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateLanguageToolService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
@@ -149,5 +166,7 @@ export async function removeService({ id }) {
await prisma.minio.deleteMany({ where: { serviceId: id } });
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.serviceSecret.deleteMany({ where: { serviceId: id } });
await prisma.service.delete({ where: { id } });
}

View File

@@ -1,5 +1,6 @@
import Dockerode from 'dockerode';
import { promises as fs } from 'fs';
import { checkPnpm } from './buildPacks/common';
import { saveBuildLog } from './common';
export async function buildCacheImageWithNode(data, imageForBuild) {
@@ -16,6 +17,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
secrets,
pullmergeRequestId
} = data;
const isPnpm = checkPnpm(installCommand, buildCommand);
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /usr/src/app');
@@ -35,20 +37,14 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
}
});
}
// TODO: If build command defined, install command should be the default yarn install
if (isPnpm) {
Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm');
Dockerfile.push('RUN pnpm add -g pnpm');
}
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
if (installCommand) {
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
try {
await fs.stat(`${workdir}/yarn.lock`);
Dockerfile.push(`COPY ./${baseDirectory || ''}yarn.lock ./`);
} catch (error) {}
try {
await fs.stat(`${workdir}/pnpm-lock.yaml`);
Dockerfile.push(`COPY ./${baseDirectory || ''}pnpm-lock.yaml ./`);
} catch (error) {}
Dockerfile.push(`RUN ${installCommand}`);
}
Dockerfile.push(`COPY ./${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${buildCommand}`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });

View File

@@ -0,0 +1,265 @@
import { dev } from '$app/env';
import got from 'got';
import mustache from 'mustache';
import crypto from 'crypto';
import * as db from '$lib/database';
import { checkContainer, checkHAProxy } from '.';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
let template = `program api
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
no option start-on-reload
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 debug
defaults
mode http
log global
timeout http-request 60s
timeout connect 10s
timeout client 60s
timeout server 60s
userlist haproxy-dataplaneapi
user admin insecure-password "\${HAPROXY_PASSWORD}"
frontend http
mode http
bind :80
bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,http/1.1
acl is_certbot path_beg /.well-known/acme-challenge/
{{#applications}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/applications}}
{{#services}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/services}}
{{#coolify}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/coolify}}
use_backend backend-certbot if is_certbot
use_backend %[req.hdr(host),lower]
frontend stats
bind *:8404
stats enable
stats uri /
stats admin if TRUE
stats auth "\${HAPROXY_USERNAME}:\${HAPROXY_PASSWORD}"
backend backend-certbot
mode http
server certbot host.docker.internal:9080
{{#applications}}
{{#isRunning}}
# updatedAt={{updatedAt}}
backend {{domain}}
option forwardfor
server {{id}} {{id}}:{{port}} check
{{/isRunning}}
{{/applications}}
{{#services}}
{{#isRunning}}
# updatedAt={{updatedAt}}
backend {{domain}}
option forwardfor
server {{id}} {{id}}:{{port}} check
{{/isRunning}}
{{/services}}
{{#coolify}}
backend {{domain}}
option forwardfor
option httpchk GET /undead.json
server {{id}} {{id}}:{{port}} check fall 10
{{/coolify}}
`;
export async function haproxyInstance() {
const { proxyPassword } = await db.listSettings();
return got.extend({
prefixUrl: url,
username: 'admin',
password: proxyPassword
});
}
export async function configureHAProxy() {
const haproxy = await haproxyInstance();
try {
await checkHAProxy(haproxy);
} catch (error) {
return 'Error: HAProxy is not running';
}
try {
const data = {
applications: [],
services: [],
coolify: []
};
const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true }
});
for (const application of applications) {
const {
fqdn,
id,
port,
destinationDocker,
destinationDockerId,
settings: { previews },
updatedAt
} = application;
if (destinationDockerId) {
const { engine, network } = destinationDocker;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) {
data.applications.push({
id,
port: port || 3000,
domain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
}
if (previews) {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
);
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
if (containers.length > 0) {
for (const container of containers) {
let previewDomain = `${container.split('-')[1]}.${domain}`;
data.applications.push({
id: container,
port: port || 3000,
domain: previewDomain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? previewDomain : 'www.' + previewDomain,
updatedAt: updatedAt.getTime()
});
}
}
}
}
}
}
const services = await db.prisma.service.findMany({
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
}
});
for (const service of services) {
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
if (destinationDockerId) {
const { engine } = destinationDocker;
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) {
data.services.push({
id,
port,
publicPort,
domain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
}
}
}
}
}
const { fqdn } = await db.prisma.setting.findFirst();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
data.coolify.push({
id: dev ? 'host.docker.internal' : 'coolify',
port: 3000,
domain,
isHttps,
redirectValue,
redirectTo: isWWW ? domain : 'www.' + domain
});
}
const output = mustache.render(template, data);
const newHash = crypto.createHash('md5').update(output).digest('hex');
const { proxyHash, id } = await db.listSettings();
if (proxyHash !== newHash) {
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
searchParams: {
skip_version: true
},
body: output,
headers: {
'Content-Type': 'text/plain'
}
});
}
} catch (error) {
throw error;
}
}

View File

@@ -1,5 +1,5 @@
import { dev } from '$app/env';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { asyncExecShell, getEngine } from '$lib/common';
import got from 'got';
import * as db from '$lib/database';
@@ -47,116 +47,14 @@ export async function completeTransaction(transactionId) {
const haproxy = await haproxyInstance();
return await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
}
export async function removeProxyConfiguration(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
}
export async function forceSSLOffApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) {
transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
}
}
} catch (error) {
console.log(error);
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function forceSSLOnApplication(domain) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
let nextRule = 0;
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
);
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
transactionId = await getNextTransactionId();
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: 'if',
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
type: 'redirect',
redir_type: 'scheme',
redir_value: 'https',
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}
export async function deleteProxy({ id }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await checkHAProxy(haproxy);
} catch (error) {
return 'Error: HAProxy is not running';
}
let transactionId;
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json();
@@ -177,7 +75,7 @@ export async function deleteProxy({ id }) {
})
.json();
} catch (error) {
console.log(error.response.body);
console.log(error.response?.body || error);
} finally {
if (transactionId) await completeTransaction(transactionId);
}
@@ -187,169 +85,6 @@ export async function reloadHaproxy(engine) {
const host = getEngine(engine);
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
}
export async function checkProxyConfigurations() {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
const stats: any = await haproxy.get(`v2/services/haproxy/stats/native`).json();
for (const stat of stats[0].stats) {
if (stat.stats.status === 'DOWN' && stat.type === 'server') {
const {
name,
backend_name: backendName,
stats: { lastchg }
} = stat;
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
if (backendName === domain) {
return;
}
}
const application = await db.getApplicationById(name);
if (!application) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
return await completeTransaction(transactionId);
}
const found = await checkContainer(application.destinationDocker.engine, name);
if (!found) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
return await completeTransaction(transactionId);
}
if (lastchg > 120) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
}
}
}
} catch (error) {
console.log(error);
}
}
export async function configureProxyForApplication({ domain, imageId, applicationId, port }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let serverConfigured = false;
let backendAvailable: any = null;
try {
backendAvailable = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
const server: any = await haproxy
.get(`v2/services/haproxy/configuration/servers/${imageId}`, {
searchParams: {
backend: domain
}
})
.json();
if (backendAvailable && server) {
// Very sophisticated way to check if the server is already configured in proxy
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') {
if (server.data.address === imageId) {
if (server.data.port === port) {
serverConfigured = true;
}
}
}
}
}
}
} catch (error) {
//console.log('error getting backend or server', error?.response?.body);
//
}
if (serverConfigured) return;
const transactionId = await getNextTransactionId();
if (backendAvailable) {
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
}
try {
await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: {
transaction_id: transactionId
},
json: {
'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' },
name: domain
}
});
await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: {
transaction_id: transactionId,
backend: domain
},
json: {
address: imageId,
check: 'enabled',
name: imageId,
port: port
}
});
} catch (error) {
throw error?.response?.body || error;
} finally {
await completeTransaction(transactionId);
}
}
export async function configureCoolifyProxyOff(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
if (isHttps) await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
} catch (error) {
throw error?.response?.body || error;
}
}
export async function checkHAProxy(haproxy?: any) {
if (!haproxy) haproxy = await haproxyInstance();
try {
@@ -361,76 +96,6 @@ export async function checkHAProxy(haproxy?: any) {
};
}
}
export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let serverConfigured = false;
let backendAvailable: any = null;
try {
backendAvailable = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
const server: any = await haproxy
.get(`v2/services/haproxy/configuration/servers/coolify`, {
searchParams: {
backend: domain
}
})
.json();
if (backendAvailable && server) {
// Very sophisticated way to check if the server is already configured in proxy
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') {
if (server.data.address === dev ? 'host.docker.internal' : 'coolify') {
if (server.data.port === 3000) {
serverConfigured = true;
}
}
}
}
}
}
} catch (error) {}
if (serverConfigured) return;
const transactionId = await getNextTransactionId();
try {
await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: {
transaction_id: transactionId
},
json: {
adv_check: 'httpchk',
httpchk_params: {
method: 'GET',
uri: '/undead.json'
},
'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' },
name: domain
}
});
await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: {
transaction_id: transactionId,
backend: domain
},
json: {
address: dev ? 'host.docker.internal' : 'coolify',
check: 'enabled',
fall: 10,
name: 'coolify',
port: 3000
}
});
} catch (error) {
console.log(error);
throw error;
} finally {
await completeTransaction(transactionId);
}
}
export async function stopTcpHttpProxy(destinationDocker, publicPort) {
const { engine } = destinationDocker;
@@ -494,7 +159,7 @@ export async function startHttpProxy(destinationDocker, id, publicPort, privateP
export async function startCoolifyProxy(engine) {
const host = getEngine(engine);
const found = await checkContainer(engine, 'coolify-haproxy');
const { proxyPassword, proxyUser } = await db.listSettings();
const { proxyPassword, proxyUser, id } = await db.listSettings();
if (!found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
@@ -503,6 +168,7 @@ export async function startCoolifyProxy(engine) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
);
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
}
await configureNetworkCoolifyProxy(engine);
}
@@ -514,11 +180,9 @@ export async function checkContainer(engine, container) {
const { stdout } = await asyncExecShell(
`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`
);
const parsedStdout = JSON.parse(stdout);
const status = parsedStdout.Status;
const isRunning = parsedStdout.Running;
const isRunning = status === 'running' ? true : false;
if (status === 'exited' || status === 'created') {
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
}
@@ -535,6 +199,8 @@ export async function stopCoolifyProxy(engine) {
const host = getEngine(engine);
const found = await checkContainer(engine, 'coolify-haproxy');
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
const { id } = await db.prisma.setting.findFirst({});
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
try {
if (found) {
await asyncExecShell(
@@ -559,171 +225,3 @@ export async function configureNetworkCoolifyProxy(engine) {
}
});
}
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let serverConfigured = false;
let backendAvailable: any = null;
try {
backendAvailable = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`)
.json();
const server: any = await haproxy
.get(`v2/services/haproxy/configuration/servers/${id}`, {
searchParams: {
backend: domain
}
})
.json();
if (backendAvailable && server) {
// Very sophisticated way to check if the server is already configured in proxy
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
if (backendAvailable.data.name === domain) {
if (server.data.check === 'enabled') {
if (server.data.address === id) {
if (server.data.port === port) {
serverConfigured = true;
}
}
}
}
}
}
} catch (error) {}
if (serverConfigured) return;
const transactionId = await getNextTransactionId();
await haproxy.post('v2/services/haproxy/configuration/backends', {
searchParams: {
transaction_id: transactionId
},
json: {
'init-addr': 'last,libc,none',
forwardfor: { enabled: 'enabled' },
name: domain
}
});
await haproxy.post('v2/services/haproxy/configuration/servers', {
searchParams: {
transaction_id: transactionId,
backend: domain
},
json: {
address: id,
check: 'enabled',
name: id,
port: port
}
});
await completeTransaction(transactionId);
}
export async function configureSimpleServiceProxyOff(fqdn) {
if (!fqdn) {
return;
}
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
} catch (error) {}
await forceSSLOffApplication(domain);
await removeWwwRedirection(fqdn);
return;
}
export async function removeWwwRedirection(fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const haproxy = await haproxyInstance();
await checkHAProxy();
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
if (rule) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
await completeTransaction(transactionId);
}
}
}
export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance();
await checkHAProxy(haproxy);
let transactionId;
try {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
let nextRule = 0;
if (rules.data.length > 0) {
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
if (rule) return;
nextRule = rules.data[rules.data.length - 1].index + 1;
}
transactionId = await getNextTransactionId();
await haproxy
.post(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
},
json: {
index: nextRule,
cond: 'if',
cond_test: contTest,
type: 'redirect',
redir_type: 'location',
redir_value: redirectValue,
redir_code: dev ? 302 : 301
}
})
.json();
} catch (error) {
console.log(error);
throw error;
} finally {
if (transactionId) await completeTransaction(transactionId);
}
}

View File

@@ -14,7 +14,7 @@ export default async function ({
buildId
}): Promise<any> {
try {
saveBuildLog({ line: 'GitHub importer started', buildId, applicationId });
saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');

View File

@@ -1,11 +1,11 @@
import { dev } from '$app/env';
import { forceSSLOffApplication, forceSSLOnApplication } from '$lib/haproxy';
import { asyncExecShell, getEngine } from './common';
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { checkContainer, reloadHaproxy } from '$lib/haproxy';
import * as db from '$lib/database';
import { dev } from '$app/env';
import cuid from 'cuid';
import getPort, { portNumbers } from 'get-port';
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
export async function letsEncrypt(domain, id = null, isCoolify = false) {
try {
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
@@ -47,8 +47,18 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
}
}
}
await forceSSLOffApplication(domain);
if (dualCerts) {
let found = false;
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${wwwDomain}.pem"`
);
found = true;
} catch (error) {
//
}
if (found) return;
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
dev ? '--test-cert' : ''
@@ -57,7 +67,18 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"`
);
await reloadHaproxy(host);
} else {
let found = false;
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "ls -1 /app/ssl/${domain}.pem"`
);
found = true;
} catch (error) {
//
}
if (found) return;
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
dev ? '--test-cert' : ''
@@ -66,14 +87,94 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"`
);
await reloadHaproxy(host);
}
} catch (error) {
if (error.code !== 0) {
throw error;
}
} finally {
if (!isCoolify) {
await forceSSLOnApplication(domain);
}
}
export async function generateSSLCerts() {
const ssls = [];
const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true },
orderBy: { createdAt: 'desc' }
});
for (const application of applications) {
const {
fqdn,
id,
destinationDocker: { engine, network },
settings: { previews }
} = application;
const isRunning = await checkContainer(engine, id);
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isRunning) {
if (isHttps) ssls.push({ domain, id, isCoolify: false });
}
if (previews) {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
);
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
if (containers.length > 0) {
for (const container of containers) {
let previewDomain = `${container.split('-')[1]}.${domain}`;
if (isHttps) ssls.push({ domain: previewDomain, id, isCoolify: false });
}
}
}
}
const services = await db.prisma.service.findMany({
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
},
orderBy: { createdAt: 'desc' }
});
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker: { engine }
} = service;
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isRunning = await checkContainer(engine, id);
if (isRunning) {
if (isHttps) ssls.push({ domain, id, isCoolify: false });
}
}
}
const { fqdn } = await db.prisma.setting.findFirst();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) ssls.push({ domain, id: 'coolify', isCoolify: true });
}
if (ssls.length > 0) {
for (const ssl of ssls) {
if (!dev) {
console.log('Checking SSL for', ssl.domain);
await letsEncrypt(ssl.domain, ssl.id, ssl.isCoolify);
} else {
console.log('Checking SSL for', ssl.domain);
}
}
}
}

View File

@@ -3,13 +3,14 @@ import fs from 'fs/promises';
import * as buildpacks from '../buildPacks';
import * as importers from '../importers';
import { dockerInstance } from '../docker';
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
import {
checkProxyConfigurations,
configureProxyForApplication,
reloadHaproxy,
setWwwRedirection
} from '../haproxy';
asyncExecShell,
asyncSleep,
createDirectories,
getDomain,
getEngine,
saveBuildLog
} from '../common';
import * as db from '$lib/database';
import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
@@ -18,7 +19,6 @@ import {
makeLabelForStandaloneApplication,
setDefaultConfiguration
} from '$lib/buildPacks/common';
import { letsEncrypt } from '$lib/letsencrypt';
export default async function (job) {
/*
@@ -51,7 +51,7 @@ export default async function (job) {
settings
} = job.data;
const { debug } = settings;
await asyncSleep(1000);
let imageId = applicationId;
let domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
@@ -73,17 +73,8 @@ export default async function (job) {
const docker = dockerInstance({ destinationDocker });
const host = getEngine(destinationDocker.engine);
const build = await db.createBuild({
id: buildId,
applicationId,
destinationDockerId: destinationDocker.id,
gitSourceId: gitSource.id,
githubAppId: gitSource.githubApp?.id,
gitlabAppId: gitSource.gitlabApp?.id,
type
});
const { workdir, repodir } = await createDirectories({ repository, buildId: build.id });
await db.prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
const { workdir, repodir } = await createDirectories({ repository, buildId });
const configuration = await setDefaultConfiguration(job.data);
@@ -93,6 +84,7 @@ export default async function (job) {
startCommand = configuration.startCommand;
buildCommand = configuration.buildCommand;
publishDirectory = configuration.publishDirectory;
baseDirectory = configuration.baseDirectory;
let commit = await importers[gitSource.type]({
applicationId,
@@ -103,7 +95,7 @@ export default async function (job) {
gitlabAppId: gitSource.gitlabApp?.id,
repository,
branch,
buildId: build.id,
buildId,
apiUrl: gitSource.apiUrl,
projectId,
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
@@ -115,7 +107,7 @@ export default async function (job) {
}
try {
await db.prisma.build.update({ where: { id: build.id }, data: { commit } });
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
} catch (err) {
console.log(err);
}
@@ -166,7 +158,7 @@ export default async function (job) {
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId);
if (buildpacks[buildPack])
await buildpacks[buildPack]({
buildId: build.id,
buildId,
applicationId,
domain,
name,
@@ -261,26 +253,6 @@ export default async function (job) {
sentry.captureException(error);
throw new Error(error);
}
try {
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
await checkProxyConfigurations();
await configureProxyForApplication({ domain, imageId, applicationId, port });
if (isHttps) await letsEncrypt({ domain, id: applicationId });
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
} else {
saveBuildLog({
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
buildId,
applicationId
});
}
} catch (error) {
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
sentry.captureException(error);
throw new Error(error);
}
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
}
}

View File

@@ -1,55 +1,71 @@
import { dev } from '$app/env';
import { asyncExecShell, getEngine } from '$lib/common';
import { asyncExecShell, getEngine, version } from '$lib/common';
import { prisma } from '$lib/database';
import { defaultProxyImageHttp, defaultProxyImageTcp } from '$lib/haproxy';
export default async function () {
const destinationDockers = await prisma.destinationDocker.findMany();
for (const destinationDocker of destinationDockers) {
const host = getEngine(destinationDocker.engine);
// Tagging images with labels
// Cleanup old coolify images
try {
const images = [
`coollabsio/${defaultProxyImageTcp}`,
`coollabsio/${defaultProxyImageHttp}`,
'certbot/certbot:latest',
'node:16.14.0-alpine',
'alpine:latest',
'nginx:stable-alpine',
'node:lts',
'php:apache',
'rust:latest'
];
for (const image of images) {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
} catch (error) {
await asyncExecShell(
`DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
);
}
let { stdout: images } = await asyncExecShell(
`DOCKER_HOST=${host} docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs `
);
images = images.trim();
if (images) {
await asyncExecShell(`DOCKER_HOST=${host} docker rmi -f ${images}`);
}
} catch (error) {}
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker container prune -f`);
} catch (error) {
console.log(error);
}
if (!dev) {
// Cleanup images that are not managed by coolify
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
);
} catch (error) {
console.log(error);
}
// Cleanup old images >3 days
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker image prune -f`);
} catch (error) {
console.log(error);
}
// Tagging images with labels
// try {
// const images = [
// `coollabsio/${defaultProxyImageTcp}`,
// `coollabsio/${defaultProxyImageHttp}`,
// 'certbot/certbot:latest',
// 'node:16.14.0-alpine',
// 'alpine:latest',
// 'nginx:stable-alpine',
// 'node:lts',
// 'php:apache',
// 'rust:latest'
// ];
// for (const image of images) {
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker image inspect ${image}`);
// } catch (error) {
// await asyncExecShell(
// `DOCKER_HOST=${host} docker pull ${image} && echo "FROM ${image}" | docker build --label coolify.image="true" -t "${image}" -`
// );
// }
// }
// } catch (error) {}
// if (!dev) {
// // Cleanup images that are not managed by coolify
// try {
// await asyncExecShell(
// `DOCKER_HOST=${host} docker image prune --filter 'label!=coolify.image=true' -a -f`
// );
// } catch (error) {
// console.log(error);
// }
// // Cleanup old images >3 days
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker image prune --filter "until=72h" -a -f`);
// } catch (error) {
// console.log(error);
// }
// }
}
}

View File

@@ -86,8 +86,8 @@ const cron = async () => {
);
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } });
await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
await queue.ssl.add('ssl', {}, { repeat: { every: dev ? 10000 : 60000 } });
if (!dev) await queue.cleanup.add('cleanup', {}, { repeat: { every: 300000 } });
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
const events = {
@@ -110,18 +110,18 @@ cron().catch((error) => {
const buildQueueName = 'build_queue';
const buildQueue = new Queue(buildQueueName, connectionOptions);
const buildWorker = new Worker(buildQueueName, async (job) => await builder(job), {
concurrency: 2,
concurrency: 1,
...connectionOptions
});
buildWorker.on('completed', async (job: Bullmq.Job) => {
try {
await prisma.build.update({ where: { id: job.data.build_id }, data: { status: 'success' } });
} catch (err) {
console.log(err);
} catch (error) {
console.log(error);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}/`;
await asyncExecShell(`rm -fr ${workdir}`);
const workdir = `/tmp/build-sources/${job.data.repository}/${job.data.build_id}`;
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
}
return;
});
@@ -133,7 +133,7 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
console.log(error);
} finally {
const workdir = `/tmp/build-sources/${job.data.repository}`;
await asyncExecShell(`rm -fr ${workdir}`);
if (!dev) await asyncExecShell(`rm -fr ${workdir}`);
}
saveBuildLog({
line: 'Failed to deploy!',

View File

@@ -1,116 +1,11 @@
import { getDomain } from '$lib/common';
import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import {
checkContainer,
checkProxyConfigurations,
configureCoolifyProxyOn,
configureProxyForApplication,
configureSimpleServiceProxyOn,
forceSSLOnApplication,
reloadHaproxy,
setWwwRedirection,
startCoolifyProxy,
startHttpProxy
} from '$lib/haproxy';
import * as db from '$lib/database';
// import { generateRemoteEngine } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { configureHAProxy } from '$lib/haproxy/configuration';
export default async function () {
try {
await checkProxyConfigurations();
return await configureHAProxy();
} catch (error) {
console.log(error);
}
try {
// Check destination containers and configure proxy if needed
const destinationDockers = await prisma.destinationDocker.findMany({});
for (const destination of destinationDockers) {
if (destination.isCoolifyProxyUsed) {
// if (destination.remoteEngine) {
// const engine = generateRemoteEngine(destination);
// }
const docker = dockerInstance({ destinationDocker: destination });
const containers = await docker.engine.listContainers();
const configurations = containers.filter(
(container) => container.Labels['coolify.managed']
);
for (const configuration of configurations) {
if (configuration.Labels['coolify.configuration']) {
const parsedConfiguration = JSON.parse(
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
);
if (
parsedConfiguration &&
configuration.Labels['coolify.type'] === 'standalone-application'
) {
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
if (fqdn) {
const found = await getApplicationById({ id: applicationId });
if (found) {
const domain = getDomain(fqdn);
await configureProxyForApplication({
domain,
imageId: pullmergeRequestId
? `${applicationId}-${pullmergeRequestId}`
: applicationId,
applicationId,
port
});
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
await setWwwRedirection(fqdn);
}
}
}
}
}
for (const container of containers) {
const image = container.Image.split(':')[0];
const found = supportedServiceTypesAndVersions.find((a) => a.baseImage === image);
if (found) {
const type = found.name;
const mainPort = found.ports.main;
const id = container.Names[0].replace('/', '');
const service = await db.prisma.service.findUnique({
where: { id },
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true
}
});
const { fqdn } = service;
const domain = getDomain(fqdn);
await configureSimpleServiceProxyOn({ id, domain, port: mainPort });
const publicPort = service[type]?.publicPort;
if (publicPort) {
const containerFound = await checkContainer(
destination.engine,
`haproxy-for-${publicPort}`
);
if (!containerFound) {
await startHttpProxy(destination, id, publicPort, 9000);
}
}
}
}
}
}
const services = await prisma.service.findMany({});
// Check Coolify FQDN and configure proxy if needed
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
}
} catch (error) {
throw error;
console.log(error.response?.body || error);
return ErrorHandler(error.response?.body || error);
}
}

View File

@@ -1,76 +1,9 @@
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { prisma } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { forceSSLOnApplication } from '$lib/haproxy';
import * as db from '$lib/database';
import { dev } from '$app/env';
import getPort, { portNumbers } from 'get-port';
import cuid from 'cuid';
import { generateSSLCerts } from '$lib/letsencrypt';
export default async function () {
try {
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const publicPort = await getPort({ port: portNumbers(minPort, maxPort) });
const randomCuid = cuid();
const destinationDockers = await prisma.destinationDocker.findMany({});
for (const destination of destinationDockers) {
if (destination.isCoolifyProxyUsed) {
const docker = dockerInstance({ destinationDocker: destination });
const containers = await docker.engine.listContainers();
const configurations = containers.filter(
(container) => container.Labels['coolify.managed']
);
for (const configuration of configurations) {
const parsedConfiguration = JSON.parse(
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
);
if (configuration.Labels['coolify.type'] === 'standalone-application') {
const { fqdn } = parsedConfiguration;
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) {
if (dev) {
console.log('DEV MODE: SSL is enabled');
} else {
const host = getEngine(destination.engine);
await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
);
if (stderr) throw new Error(stderr);
}
}
}
}
}
}
}
const { fqdn } = await db.listSettings();
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) {
if (dev) {
console.log('DEV MODE: SSL is enabled');
} else {
await asyncExecShell(
`docker run --rm --name certbot-${randomCuid} -p 9080:${publicPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${publicPort} -d ${domain} --agree-tos --non-interactive --register-unsafely-without-email`
);
const { stderr } = await asyncExecShell(
`docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem`
);
if (stderr) throw new Error(stderr);
}
}
}
return await generateSSLCerts();
} catch (error) {
console.log(error);
throw error;
}
}

View File

@@ -1,10 +1,12 @@
import { asyncExecShell } from '$lib/common';
import { reloadHaproxy } from '$lib/haproxy';
export default async function () {
try {
return await asyncExecShell(
await asyncExecShell(
`docker run --rm --name certbot-renewal -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs renew`
);
await reloadHaproxy('unix:///var/run/docker.sock');
} catch (error) {
throw error;
}

View File

@@ -38,13 +38,13 @@
import { errorNotification } from '$lib/form';
import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api';
import { browser } from '$app/env';
import { fade } from 'svelte/transition';
import { browser, dev } from '$app/env';
let isUpdateAvailable = false;
let updateStatus = {
found: false,
loading: false,
checking: false,
success: null
};
let latestVersion = 'latest';
@@ -60,16 +60,17 @@
}
if ($session.teamId === '0') {
try {
updateStatus.checking = true;
const data = await get(`/update.json`);
if (overrideVersion || data?.isUpdateAvailable) {
latestVersion = overrideVersion || data.latestVersion;
isUpdateAvailable = overrideVersion ? true : data?.isUpdateAvailable;
await post(`/update.json`, { type: 'pull', latestVersion });
if (overrideVersion) {
isUpdateAvailable = true;
} else {
isUpdateAvailable = data.isUpdateAvailable;
}
}
} catch (error) {
} finally {
updateStatus.checking = false;
}
}
}
@@ -97,26 +98,32 @@
async function update() {
updateStatus.loading = true;
try {
await post(`/update.json`, { type: 'update', latestVersion });
toast.push('Update completed.<br><br>Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
if (dev) {
console.log(`updating to ${latestVersion}`);
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
return window.location.reload();
} else {
await post(`/update.json`, { type: 'update', latestVersion });
toast.push('Update completed.<br><br>Waiting for the new version to start...');
let reachable = false;
let tries = 0;
do {
await asyncSleep(4000);
try {
await get(`/undead.json`);
reachable = true;
} catch (error) {
reachable = false;
}
if (reachable) break;
tries++;
} while (!reachable || tries < 120);
toast.push('New version reachable. Reloading...');
updateStatus.loading = false;
updateStatus.success = true;
await asyncSleep(3000);
return window.location.reload();
}
} catch ({ error }) {
updateStatus.success = false;
updateStatus.loading = false;
@@ -311,35 +318,10 @@
<div class="flex flex-col space-y-4 py-2">
{#if $session.teamId === '0'}
{#if updateStatus.checking}
<button
disabled
in:fade={{ duration: 150 }}
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
data-tooltip="Checking for updates..."
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-9 w-8 animate-spin"
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="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
<line x1="11" y1="19.94" x2="11" y2="19.95" />
</svg></button
>
{:else if isUpdateAvailable}
{#if isUpdateAvailable}
<button
disabled={updateStatus.success === false}
data-tooltip="Update available"
title="Update available"
on:click={update}
class="icons tooltip-right bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 text-white duration-75 hover:scale-105"
>

View File

@@ -76,6 +76,7 @@
import { del, post } from '$lib/api';
import { goto } from '$app/navigation';
import { gitTokens } from '$lib/store';
import { toast } from '@zerodevx/svelte-toast';
if (githubToken) $gitTokens.githubToken = githubToken;
if (gitlabToken) $gitTokens.gitlabToken = gitlabToken;
@@ -86,6 +87,7 @@
async function handleDeploySubmit() {
try {
const { buildId } = await post(`/applications/${id}/deploy.json`, { ...application });
toast.push('Deployment queued.');
return await goto(`/applications/${id}/logs/build?buildId=${buildId}`);
} catch ({ error }) {
return errorNotification(error);
@@ -111,8 +113,6 @@
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
} finally {
loading = false;
}
}
</script>

View File

@@ -25,9 +25,11 @@
let selected = {
projectId: undefined,
repository: undefined,
branch: undefined
branch: undefined,
autodeploy: application.settings.autodeploy || true
};
let showSave = false;
async function loadRepositoriesByPage(page = 0) {
return await get(`${apiUrl}/installation/repositories?per_page=100&page=${page}`, {
Authorization: `token ${$gitTokens.githubToken}`
@@ -69,7 +71,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.repository}&branch=${selected.branch}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
if (sure) {
selected.autodeploy = false;
showSave = true;
return true;
}
showSave = false;
return true;
}

View File

@@ -30,6 +30,7 @@
let projects = [];
let branches = [];
let showSave = false;
let autodeploy = application.settings.autodeploy || true;
let selected = {
group: undefined,
@@ -138,7 +139,14 @@
`/applications/${id}/configuration/repository.json?repository=${selected.project.path_with_namespace}&branch=${selected.branch.name}`
);
if (data.used) {
errorNotification('This branch is already used by another application.');
const sure = confirm(
`This branch is already used by another application. Webhooks won't work in this case for both applications. Are you sure you want to use it?`
);
if (sure) {
autodeploy = false;
showSave = true;
return true;
}
showSave = false;
return true;
}
@@ -235,10 +243,14 @@
const url = `/applications/${id}/configuration/repository.json`;
try {
const repository = `${selected.group.full_path.replace('-personal', '')}/${
selected.project.name
}`;
await post(url, {
repository: `${selected.group.full_path}/${selected.project.name}`,
repository,
branch: selected.branch.name,
projectId: selected.project.id,
autodeploy,
webhookToken
});
return await goto(from || `/applications/${id}/configuration/buildpack`);

View File

@@ -30,14 +30,21 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body };
const { id } = event.params;
let { repository, branch, projectId, webhookToken } = await event.request.json();
let { repository, branch, projectId, webhookToken, autodeploy } = await event.request.json();
repository = repository.toLowerCase();
branch = branch.toLowerCase();
projectId = Number(projectId);
try {
await db.configureGitRepository({ id, repository, branch, projectId, webhookToken });
await db.configureGitRepository({
id,
repository,
branch,
projectId,
webhookToken,
autodeploy
});
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@@ -29,6 +29,19 @@ export const post: RequestHandler = async (event) => {
.digest('hex');
await db.prisma.application.update({ where: { id }, data: { configHash } });
}
await db.prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
await db.prisma.build.create({
data: {
id: buildId,
applicationId: id,
destinationDockerId: applicationFound.destinationDocker.id,
gitSourceId: applicationFound.gitSource.id,
githubAppId: applicationFound.gitSource.githubApp?.id,
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
status: 'queued',
type: 'manual'
}
});
await buildQueue.add(buildId, { build_id: buildId, type: 'manual', ...applicationFound });
return {
status: 200,

View File

@@ -56,6 +56,7 @@
let debug = application.settings.debug;
let previews = application.settings.previews;
let dualCerts = application.settings.dualCerts;
let autodeploy = application.settings.autodeploy;
if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) {
application.fqdn = `http://${cuid()}.demo.coolify.io`;
@@ -75,10 +76,32 @@
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
try {
await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts });
await post(`/applications/${id}/settings.json`, {
previews,
debug,
dualCerts,
autodeploy,
branch: application.branch,
projectId: application.projectId
});
return toast.push('Settings saved.');
} catch ({ error }) {
if (name === 'debug') {
debug = !debug;
}
if (name === 'previews') {
previews = !previews;
}
if (name === 'dualCerts') {
dualCerts = !dualCerts;
}
if (name === 'autodeploy') {
autodeploy = !autodeploy;
}
return errorNotification(error);
}
}
@@ -383,22 +406,23 @@
<div class="flex space-x-1 pb-5 font-bold">
<div class="title">Features</div>
</div>
<!-- <ul class="mt-2 divide-y divide-stone-800">
<Setting
bind:setting={forceSSL}
on:click={() => changeSettings('forceSSL')}
title="Force https"
description="Creates a https redirect for all requests from http and also generates a https certificate for the domain through Let's Encrypt."
/>
</ul> -->
<div class="px-10 pb-10">
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={autodeploy}
on:click={() => changeSettings('autodeploy')}
title="Enable Automatic Deployment"
description="Enable automatic deployment through webhooks."
/>
</div>
<div class="grid grid-cols-2 items-center">
<Setting
isCenter={false}
bind:setting={previews}
on:click={() => changeSettings('previews')}
title="Enable MR/PR Previews"
description="Creates previews from pull and merge requests."
description="Enable preview deployments from pull or merge requests."
/>
</div>
<div class="grid grid-cols-2 items-center">

View File

@@ -43,11 +43,11 @@
logs = logs.concat(responseLogs.map((log) => ({ ...log, line: cleanAnsiCodes(log.line) })));
loading = false;
streamInterval = setInterval(async () => {
if (status !== 'running') {
if (status !== 'running' && status !== 'queued') {
clearInterval(streamInterval);
return;
}
const nextSequence = logs[logs.length - 1].time;
const nextSequence = logs[logs.length - 1]?.time || 0;
try {
const data = await get(
`/applications/${id}/logs/build/build.json?buildId=${buildId}&sequence=${nextSequence}`
@@ -83,38 +83,42 @@
{#if currentStatus === 'running'}
<LoadingLogs />
{/if}
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
{#if currentStatus === 'queued'}
<div class="text-center">Queued and waiting for execution.</div>
{:else}
<div class="flex justify-end sticky top-0 p-2">
<button
on:click={followBuild}
class="bg-transparent"
data-tooltip="Follow logs"
class:text-green-500={followingBuild}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
bind:this={logsEl}
>
{#each logs as log}
<div>{log.line + '\n'}</div>
{/each}
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-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" />
<circle cx="12" cy="12" r="9" />
<line x1="8" y1="12" x2="12" y2="16" />
<line x1="12" y1="8" x2="12" y2="16" />
<line x1="16" y1="12" x2="12" y2="16" />
</svg>
</button>
</div>
<div
class="font-mono leading-6 text-left text-md tracking-tighter rounded bg-coolgray-200 py-5 px-6 whitespace-pre-wrap break-words overflow-auto max-h-[80vh] -mt-12 overflow-y-scroll scrollbar-w-1 scrollbar-thumb-coollabs scrollbar-track-coolgray-200"
bind:this={logsEl}
>
{#each logs as log}
<div>{log.line + '\n'}</div>
{/each}
</div>
{/if}
</div>
{/if}

View File

@@ -19,7 +19,7 @@ export const get: RequestHandler = async (event) => {
return {
body: {
logs,
status: data?.status || 'running'
status: data?.status
}
};
} catch (error) {

View File

@@ -51,10 +51,10 @@
build.took = data.builds[0].took;
build.since = data.builds[0].since;
}
window.location.reload();
return build;
});
return;
return window.location.reload();
} catch ({ error }) {
return errorNotification(error);
}
@@ -121,6 +121,8 @@
<div class="w-48 text-center text-xs">
{#if build.status === 'running'}
<div class="font-bold">Running</div>
{:else if build.status === 'queued'}
<div class="font-bold">Queued</div>
{:else}
<div>{build.since}</div>
<div>Finished in <span class="font-bold">{build.took}s</span></div>

View File

@@ -43,53 +43,41 @@
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto">
<thead class=" rounded-xl border-b border-coolgray-500">
<tr>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white">Name</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Value</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
>Need during buildtime?</th
>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
/>
</tr>
</thead>
<tbody class="">
{#each applicationSecrets as secret}
{#key secret.id}
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
<Secret
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
isPRMRSecret
name={secret.name}
value={secret.value}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
</tbody>
</table>
</div>
{#if applicationSecrets.length !== 0}
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-64 text-center">Need during buildtime?</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody>
{#each applicationSecrets as secret}
{#key secret.id}
<tr>
<Secret
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
isPRMRSecret
name={secret.name}
value={secret.value}
isBuildSecret={secret.isBuildSecret}
on:refresh={refreshSecrets}
/>
</tr>
{/key}
{/each}
</tbody>
</table>
</div>
{/if}
<div class="flex justify-center py-4 text-center">
<Explainer
customClass="w-full"
text={applicationSecrets.length === 0
? "<span class='font-bold text-white text-xl'>Please add secrets to the application first.</span> <br><br>These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
? "You can add secrets to PR/MR deployments. Please add secrets to the application first. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
/>
</div>

View File

@@ -8,10 +8,17 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body };
const { id } = event.params;
const { debug, previews, dualCerts } = await event.request.json();
const { debug, previews, dualCerts, autodeploy, branch, projectId } = await event.request.json();
try {
await db.setApplicationSettings({ id, debug, previews, dualCerts });
const isDouble = await db.checkDoubleBranch(branch, projectId);
if (isDouble && autodeploy) {
throw {
message:
'Cannot activate automatic deployments until only one application is defined for this repository / branch.'
};
}
await db.setApplicationSettings({ id, debug, previews, dualCerts, autodeploy });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@@ -1,8 +1,7 @@
import { getDomain, getUserDetails } from '$lib/common';
import { asyncExecShell, getEngine, getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { removeProxyConfiguration } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -17,10 +16,12 @@ export const post: RequestHandler = async (event) => {
teamId
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
await docker.engine.getContainer(id).stop();
const { engine } = destinationDocker;
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
}
await removeProxyConfiguration(fqdn);
return {
status: 200
};

View File

@@ -117,6 +117,8 @@
setTimeout(() => {
window.location.reload();
}, 5000);
} finally {
restarting = false;
}
}
}

View File

@@ -1,30 +1,19 @@
import { getDomain, getUserDetails } from '$lib/common';
import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database';
import * as db from '$lib/database';
import {
configureCoolifyProxyOn,
forceSSLOnApplication,
setWwwRedirection,
startCoolifyProxy,
stopCoolifyProxy
} from '$lib/haproxy';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { engine, fqdn } = await event.request.json();
const { engine } = await event.request.json();
try {
const domain = getDomain(fqdn);
await stopCoolifyProxy(engine);
await startCoolifyProxy(engine);
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication(domain);
return {
status: 200
};

View File

@@ -25,7 +25,7 @@
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);`
: null}>{service.wordpress.extraConfig || 'N/A'}</textarea
: 'N/A'}>{service.wordpress.extraConfig}</textarea
>
</div>
<div class="flex space-x-1 py-5 font-bold">

View File

@@ -57,13 +57,13 @@
</script>
<script>
import { session } from '$app/stores';
import { page, session } from '$app/stores';
import { errorNotification } from '$lib/form';
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
import Loading from '$lib/components/Loading.svelte';
import { del, post } from '$lib/api';
import { goto } from '$app/navigation';
import { onMount } from 'svelte';
const { id } = $page.params;
export let service;
export let isRunning;
@@ -110,23 +110,6 @@
loading = false;
}
}
// onMount(async () => {
// if (
// service.type &&
// service.destinationDockerId &&
// service.version &&
// service.fqdn &&
// !isRunning
// ) {
// try {
// await post(`/services/${service.id}/${service.type}/stop.json`, {});
// } catch ({ error }) {
// return errorNotification(error);
// } finally {
// loading = false;
// }
// }
// });
</script>
<nav class="nav-side">
@@ -185,6 +168,76 @@
</svg>
</button>
{/if}
<div class="border border-stone-700 h-8" />
{/if}
{#if service.type && service.destinationDockerId && service.version}
<a
href="/services/{id}"
sveltekit:prefetch
class="hover:text-yellow-500 rounded"
class:text-yellow-500={$page.url.pathname === `/services/${id}`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}`}
>
<button
title="Configurations"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Configurations"
>
<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="4" y="8" width="4" height="4" />
<line x1="6" y1="4" x2="6" y2="8" />
<line x1="6" y1="12" x2="6" y2="20" />
<rect x="10" y="14" width="4" height="4" />
<line x1="12" y1="4" x2="12" y2="14" />
<line x1="12" y1="18" x2="12" y2="20" />
<rect x="16" y="5" width="4" height="4" />
<line x1="18" y1="4" x2="18" y2="5" />
<line x1="18" y1="9" x2="18" y2="20" />
</svg></button
></a
>
<a
href="/services/{id}/secrets"
sveltekit:prefetch
class="hover:text-pink-500 rounded"
class:text-pink-500={$page.url.pathname === `/services/${id}/secrets`}
class:bg-coolgray-500={$page.url.pathname === `/services/${id}/secrets`}
>
<button
title="Secrets"
class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500"
data-tooltip="Secrets"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-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" />
<path
d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"
/>
<circle cx="12" cy="11" r="1" />
<line x1="12" y1="12" x2="12" y2="14.5" />
</svg></button
></a
>
<div class="border border-stone-700 h-8" />
{/if}
<button
on:click={deleteService}

View File

@@ -37,6 +37,7 @@
import { goto } from '$app/navigation';
import { post } from '$lib/api';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@@ -74,6 +75,8 @@
<Wordpress isAbsolute />
{:else if type.name === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if type.name === 'languagetool'}
<LanguageTool isAbsolute />
{/if}{type.fancyName}
</button>
</form>

View File

@@ -35,10 +35,10 @@
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import Services from './_Services/_Services.svelte';
import { getDomain } from '$lib/components/common';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import cuid from 'cuid';
import { browser } from '$app/env';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
export let service;
export let isRunning;
@@ -105,6 +105,10 @@
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden />
</a>
{:else if service.type === 'languagetool'}
<a href="https://languagetool.org/dev" target="_blank">
<LanguageTool />
</a>
{/if}
</div>
</div>

View File

@@ -0,0 +1,21 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
let { name, fqdn } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase();
try {
await db.updateLanguageToolService({ id, fqdn, name });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,78 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
volume: `${id}-ngrams:/ngrams`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
networks: [network],
environment: config.environmentVariables,
restart: 'always',
volumes: [`${id}-ngrams:/ngrams`],
labels: makeLabelForServices('languagetool')
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[`${id}-ngrams`]: {
external: true
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}-ngrams`);
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,35 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
} catch (error) {
console.error(error);
}
}
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -3,18 +3,10 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection,
startHttpProxy
} from '$lib/haproxy';
import { startHttpProxy } from '$lib/haproxy';
import getPort, { portNumbers } from 'get-port';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -24,7 +16,6 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
@@ -32,15 +23,13 @@ export const post: RequestHandler = async (event) => {
fqdn,
destinationDockerId,
destinationDocker,
minio: { rootUser, rootUserPassword }
minio: { rootUser, rootUserPassword },
serviceSecret
} = service;
const data = await db.prisma.setting.findFirst();
const { minPort, maxPort } = data;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
@@ -50,9 +39,10 @@ export const post: RequestHandler = async (event) => {
const apiPort = 9000;
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `minio/minio:${version}`,
image: `${image}:${version}`,
volume: `${id}-minio-data:/data`,
environmentVariables: {
MINIO_ROOT_USER: rootUser,
@@ -60,12 +50,17 @@ export const post: RequestHandler = async (event) => {
MINIO_BROWSER_REDIRECT_URL: fqdn
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: `minio/minio:${version}`,
image: config.image,
command: `server /data --console-address ":${consolePort}"`,
environment: config.environmentVariables,
networks: [network],
@@ -96,16 +91,8 @@ export const post: RequestHandler = async (event) => {
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
await db.updateMinioService({ id, publicPort });
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getEngine, getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff, stopTcpHttpProxy } from '$lib/haproxy';
import { checkContainer, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -21,7 +19,6 @@ export const post: RequestHandler = async (event) => {
minio: { publicPort }
} = service;
await db.updateMinioService({ id, publicPort: null });
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -35,7 +32,6 @@ export const post: RequestHandler = async (event) => {
}
try {
await stopTcpHttpProxy(destinationDocker, publicPort);
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,25 +13,31 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const { type, version, fqdn, destinationDockerId, destinationDocker } = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: `nocodb/nocodb:${version}`,
image: config.image,
networks: [network],
environment: config.environmentVariables,
restart: 'always',
labels: makeLabelForServices('nocodb')
}
@@ -56,14 +53,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,11 +24,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,7 +13,6 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
@@ -30,6 +20,7 @@ export const post: RequestHandler = async (event) => {
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
plausibleAnalytics: {
id: plausibleDbId,
username,
@@ -41,13 +32,11 @@ export const post: RequestHandler = async (event) => {
secretKeyBase
}
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const image = getServiceImage(type);
const config = {
plausibleAnalytics: {
image: `plausible/analytics:${version}`,
image: `${image}:${version}`,
environmentVariables: {
ADMIN_USER_EMAIL: email,
ADMIN_USER_NAME: username,
@@ -81,9 +70,13 @@ export const post: RequestHandler = async (event) => {
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.plausibleAnalytics.environmentVariables[secret.name] = secret.value;
});
}
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const engine = destinationDocker.engine;
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -187,14 +180,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8000 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -36,12 +33,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -0,0 +1,87 @@
<script>
export let name = '';
export let value = '';
export let isNewSecret = false;
import { page } from '$app/stores';
import { del, post } from '$lib/api';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const { id } = $page.params;
async function removeSecret() {
try {
await del(`/services/${id}/secrets.json`, { name });
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
} catch ({ error }) {
return errorNotification(error);
}
}
async function saveSecret(isNew = false) {
if (!name) return errorNotification('Name is required.');
if (!value) return errorNotification('Value is required.');
try {
await post(`/services/${id}/secrets.json`, {
name,
value,
isNew
});
dispatch('refresh');
if (isNewSecret) {
name = '';
value = '';
}
toast.push('Secret saved.');
} catch ({ error }) {
return errorNotification(error);
}
}
</script>
<td>
<input
id={isNewSecret ? 'secretName' : 'secretNameNew'}
bind:value={name}
required
placeholder="EXAMPLE_VARIABLE"
class=" border border-dashed border-coolgray-300"
readonly={!isNewSecret}
class:bg-transparent={!isNewSecret}
class:cursor-not-allowed={!isNewSecret}
/>
</td>
<td>
<CopyPasswordField
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
isPasswordField={true}
bind:value
required
placeholder="J$#@UIO%HO#$U%H"
/>
</td>
<td>
{#if isNewSecret}
<div class="flex items-center justify-center">
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
</div>
{:else}
<div class="flex flex-row justify-center space-x-2">
<div class="flex items-center justify-center">
<button class="" on:click={() => saveSecret(false)}>Set</button>
</div>
<div class="flex justify-center items-end">
<button class="bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
</div>
</div>
{/if}
</td>

View File

@@ -0,0 +1,70 @@
import { getTeam, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const get: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const secrets = await db.listServiceSecrets(id);
return {
status: 200,
body: {
secrets: secrets.sort((a, b) => {
return ('' + a.name).localeCompare(b.name);
})
}
};
} catch (error) {
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
try {
if (isNew) {
const found = await db.isServiceSecretExists({ id, name });
if (found) {
throw {
error: `Secret ${name} already exists.`
};
} else {
await db.createServiceSecret({ id, name, value });
return {
status: 201
};
}
} else {
await db.updateServiceSecret({ id, name, value });
return {
status: 201
};
}
} catch (error) {
return ErrorHandler(error);
}
};
export const del: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
const { name } = await event.request.json();
try {
await db.removeServiceSecret({ id, name });
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@@ -0,0 +1,67 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff }) => {
let endpoint = `/services/${params.id}/secrets.json`;
const res = await fetch(endpoint);
if (res.ok) {
return {
props: {
service: stuff.service,
...(await res.json())
}
};
}
return {
status: res.status,
error: new Error(`Could not load ${endpoint}`)
};
};
</script>
<script lang="ts">
export let secrets;
export let service;
import Secret from './_Secret.svelte';
import { getDomain } from '$lib/components/common';
import { page } from '$app/stores';
import { get } from '$lib/api';
const { id } = $page.params;
async function refreshSecrets() {
const data = await get(`/services/${id}/secrets.json`);
secrets = [...data.secrets];
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">
Secrets {#if service.fqdn}
<a href={service.fqdn} target="_blank">{getDomain(service.fqdn)}</a>
{/if}
</div>
</div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
<table class="mx-auto border-separate text-left">
<thead>
<tr class="h-12">
<th scope="col">Name</th>
<th scope="col">Value</th>
<th scope="col" class="w-96 text-center">Action</th>
</tr>
</thead>
<tbody>
{#each secrets as secret}
{#key secret.id}
<tr>
<Secret name={secret.name} value={secret.value} on:refresh={refreshSecrets} />
</tr>
{/key}
{/each}
<tr>
<Secret isNewSecret on:refresh={refreshSecrets} />
</tr>
</tbody>
</table>
</div>

View File

@@ -3,15 +3,6 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { getServiceImage, ErrorHandler } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
@@ -22,30 +13,32 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const { type, version, fqdn, destinationDockerId, destinationDocker } = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const baseImage = getServiceImage(type);
const image = getServiceImage(type);
const config = {
image: `${baseImage}:${version}`,
volume: `${id}-vaultwarden-data:/data/`
image: `${image}:${version}`,
volume: `${id}-vaultwarden-data:/data/`,
environmentVariables: {}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
environment: config.environmentVariables,
networks: [network],
volumes: [config.volume],
restart: 'always',
@@ -74,14 +67,6 @@ export const post: RequestHandler = async (event) => {
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,13 +24,7 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {
status: 200
};

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,31 +13,34 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
vscodeserver: { password }
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `codercom/code-server:${version}`,
image: `${image}:${version}`,
volume: `${id}-vscodeserver-data:/home/coder`,
environmentVariables: {
PASSWORD: password
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
@@ -84,14 +78,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
@@ -27,11 +24,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {
status: 200

View File

@@ -3,16 +3,7 @@ import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt';
import {
checkHAProxy,
checkProxyConfigurations,
configureSimpleServiceProxyOn,
reloadHaproxy,
setWwwRedirection
} from '$lib/haproxy';
import { getDomain } from '$lib/components/common';
import { ErrorHandler } from '$lib/database';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
@@ -22,13 +13,13 @@ export const post: RequestHandler = async (event) => {
const { id } = event.params;
try {
await checkHAProxy();
const service = await db.getService({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
serviceSecret,
destinationDocker,
wordpress: {
mysqlDatabase,
@@ -40,16 +31,14 @@ export const post: RequestHandler = async (event) => {
}
} = service;
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const image = getServiceImage(type);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const config = {
wordpress: {
image: `wordpress:${version}`,
image: `${image}:${version}`,
volume: `${id}-wordpress-data:/var/www/html`,
environmentVariables: {
WORDPRESS_DB_HOST: `${id}-mysql`,
@@ -71,6 +60,11 @@ export const post: RequestHandler = async (event) => {
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.wordpress.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
@@ -121,14 +115,6 @@ export const post: RequestHandler = async (event) => {
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await checkProxyConfigurations();
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
if (isHttps) {
await letsEncrypt({ domain, id });
}
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine);
return {
status: 200
};

View File

@@ -1,9 +1,7 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import { getDomain } from '$lib/components/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { dockerInstance } from '$lib/docker';
import { checkContainer, configureSimpleServiceProxyOff } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
@@ -15,7 +13,6 @@ export const post: RequestHandler = async (event) => {
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const domain = getDomain(fqdn);
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
@@ -30,11 +27,6 @@ export const post: RequestHandler = async (event) => {
} catch (error) {
console.error(error);
}
try {
await configureSimpleServiceProxyOff(fqdn);
} catch (error) {
console.log(error);
}
}
return {

View File

@@ -26,6 +26,7 @@
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
export let services;
</script>
@@ -70,6 +71,8 @@
<Wordpress isAbsolute />
{:else if service.type === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if service.type === 'languagetool'}
<LanguageTool isAbsolute />
{/if}
<div class="font-bold text-xl text-center truncate">
{service.name}

View File

@@ -1,18 +1,6 @@
import { dev } from '$app/env';
import { getDomain, getUserDetails } from '$lib/common';
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { listSettings, ErrorHandler } from '$lib/database';
import {
checkProxyConfigurations,
configureCoolifyProxyOff,
configureCoolifyProxyOn,
forceSSLOnApplication,
reloadHaproxy,
removeWwwRedirection,
setWwwRedirection,
startCoolifyProxy
} from '$lib/haproxy';
import { letsEncrypt } from '$lib/letsencrypt';
import type { RequestHandler } from '@sveltejs/kit';
import { promises as dns } from 'dns';
@@ -52,10 +40,7 @@ export const del: RequestHandler = async (event) => {
// Do not care.
}
try {
const domain = getDomain(fqdn);
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
await configureCoolifyProxyOff(fqdn);
await removeWwwRedirection(domain);
return {
status: 200,
body: {
@@ -80,50 +65,19 @@ export const post: RequestHandler = async (event) => {
const { fqdn, isRegistrationEnabled, dualCerts, minPort, maxPort } = await event.request.json();
try {
await checkProxyConfigurations();
const {
id,
fqdn: oldFqdn,
isRegistrationEnabled: oldIsRegistrationEnabled,
dualCerts: oldDualCerts
} = await db.listSettings();
if (oldIsRegistrationEnabled !== isRegistrationEnabled) {
const { id } = await db.listSettings();
if (isRegistrationEnabled) {
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
}
if (oldDualCerts !== dualCerts) {
await db.prisma.setting.update({ where: { id }, data: { dualCerts } });
}
if (oldFqdn && oldFqdn !== fqdn) {
if (oldFqdn) {
const oldDomain = getDomain(oldFqdn);
await configureCoolifyProxyOff(oldFqdn);
await removeWwwRedirection(oldDomain);
}
}
if (fqdn) {
await startCoolifyProxy('/var/run/docker.sock');
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
if (domain) {
await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
if (isHttps) {
await letsEncrypt({ domain, isCoolify: true });
await forceSSLOnApplication(domain);
await reloadHaproxy('/var/run/docker.sock');
}
}
await db.prisma.setting.update({ where: { id }, data: { fqdn } });
await db.prisma.destinationDocker.updateMany({
where: { engine: '/var/run/docker.sock' },
data: { isCoolifyProxyUsed: true }
});
}
if (dualCerts) {
await db.prisma.setting.update({ where: { id }, data: { dualCerts } });
}
if (minPort && maxPort) {
await db.prisma.setting.update({ where: { id }, data: { minPort, maxPort } });
}
return {
status: 201
};

View File

@@ -78,6 +78,7 @@
if (fqdn !== settings.fqdn) {
await post(`/settings/check.json`, { fqdn });
await post(`/settings.json`, { fqdn });
return window.location.reload();
}
if (minPort !== settings.minPort || maxPort !== settings.maxPort) {
await post(`/settings.json`, { minPort, maxPort });
@@ -98,7 +99,7 @@
{#if $session.teamId === '0'}
<div class="mx-auto max-w-4xl px-6">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 py-6">
<div class="flex space-x-1 pb-6">
<div class="title font-bold">Global Settings</div>
<button
type="submit"
@@ -188,8 +189,11 @@
<Explainer
text={`Credentials for <a class="text-white font-bold" href=${
fqdn
? 'http://' + getDomain(fqdn) + ':8404'
: browser && 'http://' + window.location.hostname + ':8404'
? `http://${settings.proxyUser}:${settings.proxyPassword}@` + getDomain(fqdn) + ':8404'
: browser &&
`http://${settings.proxyUser}:${settings.proxyPassword}@` +
window.location.hostname +
':8404'
} target="_blank">stats</a> page.`}
/>
<div class="space-y-2 px-10 py-5">

View File

@@ -6,13 +6,18 @@ import type { RequestHandler } from '@sveltejs/kit';
import compare from 'compare-versions';
import got from 'got';
export const get: RequestHandler = async () => {
export const get: RequestHandler = async (request) => {
try {
const currentVersion = version;
const versions = await got
.get(`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}`)
.get(
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`
)
.json();
const latestVersion = dev ? '10.0.0' : versions['coolify'].main.version;
const latestVersion =
request.url.hostname === 'staging.coolify.io'
? versions['coolify'].next.version
: versions['coolify'].main.version;
const isUpdateAvailable = compare(latestVersion, currentVersion);
return {
body: {
@@ -21,32 +26,17 @@ export const get: RequestHandler = async () => {
}
};
} catch (error) {
console.log(error);
return ErrorHandler(error);
}
};
export const post: RequestHandler = async (event) => {
const { type, latestVersion } = await event.request.json();
if (type === 'pull') {
if (type === 'update') {
try {
if (!dev) {
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
return {
status: 200,
body: {}
};
} else {
return {
status: 200,
body: {}
};
}
} catch (error) {
return ErrorHandler(error);
}
} else if (type === 'update') {
try {
if (!dev) {
await asyncExecShell(`env | grep COOLIFY > .env`);
await asyncExecShell(
`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-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"`

View File

@@ -4,12 +4,12 @@ import type { RequestHandler } from '@sveltejs/kit';
import cuid from 'cuid';
import crypto from 'crypto';
import { buildQueue } from '$lib/queues';
import { checkContainer, removeProxyConfiguration } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import { dev } from '$app/env';
export const options: RequestHandler = async () => {
return {
status: 200,
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
@@ -47,7 +47,7 @@ export const post: RequestHandler = async (event) => {
const applicationFound = await db.getApplicationWebhook({ projectId, branch });
if (applicationFound) {
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret;
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
const hmac = crypto.createHmac('sha256', webhookSecret);
const digest = Buffer.from(
'sha256=' + hmac.update(JSON.stringify(body)).digest('hex'),
@@ -84,6 +84,22 @@ export const post: RequestHandler = async (event) => {
data: { configHash }
});
}
await db.prisma.application.update({
where: { id: applicationFound.id },
data: { updatedAt: new Date() }
});
await db.prisma.build.create({
data: {
id: buildId,
applicationId: applicationFound.id,
destinationDockerId: applicationFound.destinationDocker.id,
gitSourceId: applicationFound.gitSource.id,
githubAppId: applicationFound.gitSource.githubApp?.id,
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
status: 'queued',
type: 'webhook_commit'
}
});
await buildQueue.add(buildId, {
build_id: buildId,
type: 'webhook_commit',
@@ -128,6 +144,22 @@ export const post: RequestHandler = async (event) => {
pullmergeRequestAction === 'reopened' ||
pullmergeRequestAction === 'synchronize'
) {
await db.prisma.application.update({
where: { id: applicationFound.id },
data: { updatedAt: new Date() }
});
await db.prisma.build.create({
data: {
id: buildId,
applicationId: applicationFound.id,
destinationDockerId: applicationFound.destinationDocker.id,
gitSourceId: applicationFound.gitSource.id,
githubAppId: applicationFound.gitSource.githubApp?.id,
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
status: 'queued',
type: 'webhook_pr'
}
});
await buildQueue.add(buildId, {
build_id: buildId,
type: 'webhook_pr',
@@ -143,18 +175,9 @@ export const post: RequestHandler = async (event) => {
};
} else if (pullmergeRequestAction === 'closed') {
if (applicationFound.destinationDockerId) {
const domain = getDomain(applicationFound.fqdn);
const isHttps = applicationFound.fqdn.startsWith('https://');
const isWWW = applicationFound.fqdn.includes('www.');
const fqdn = `${isHttps ? 'https://' : 'http://'}${
isWWW ? 'www.' : ''
}${pullmergeRequestId}.${domain}`;
const id = `${applicationFound.id}-${pullmergeRequestId}`;
const engine = applicationFound.destinationDocker.engine;
await removeDestinationDocker({ id, engine });
await removeProxyConfiguration(fqdn);
}
return {
status: 200,

View File

@@ -1,5 +1,5 @@
import { getTeam, getUserDetails, getDomain, removeDestinationDocker } from '$lib/common';
import { checkContainer, removeProxyConfiguration } from '$lib/haproxy';
import { checkContainer } from '$lib/haproxy';
import * as db from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
import cuid from 'cuid';
@@ -9,7 +9,7 @@ import { dev } from '$app/env';
export const options: RequestHandler = async () => {
return {
status: 200,
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
@@ -48,6 +48,22 @@ export const post: RequestHandler = async (event) => {
data: { configHash }
});
}
await db.prisma.application.update({
where: { id: applicationFound.id },
data: { updatedAt: new Date() }
});
await db.prisma.build.create({
data: {
id: buildId,
applicationId: applicationFound.id,
destinationDockerId: applicationFound.destinationDocker.id,
gitSourceId: applicationFound.gitSource.id,
githubAppId: applicationFound.gitSource.githubApp?.id,
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
status: 'queued',
type: 'webhook_commit'
}
});
await buildQueue.add(buildId, {
build_id: buildId,
type: 'webhook_commit',
@@ -125,6 +141,22 @@ export const post: RequestHandler = async (event) => {
action === 'open' ||
action === 'update'
) {
await db.prisma.application.update({
where: { id: applicationFound.id },
data: { updatedAt: new Date() }
});
await db.prisma.build.create({
data: {
id: buildId,
applicationId: applicationFound.id,
destinationDockerId: applicationFound.destinationDocker.id,
gitSourceId: applicationFound.gitSource.id,
githubAppId: applicationFound.gitSource.githubApp?.id,
gitlabAppId: applicationFound.gitSource.gitlabApp?.id,
status: 'queued',
type: 'webhook_mr'
}
});
await buildQueue.add(buildId, {
build_id: buildId,
type: 'webhook_mr',
@@ -140,18 +172,9 @@ export const post: RequestHandler = async (event) => {
};
} else if (action === 'close') {
if (applicationFound.destinationDockerId) {
const domain = getDomain(applicationFound.fqdn);
const isHttps = applicationFound.fqdn.startsWith('https://');
const isWWW = applicationFound.fqdn.includes('www.');
const fqdn = `${isHttps ? 'https://' : 'http://'}${
isWWW ? 'www.' : ''
}${pullmergeRequestId}.${domain}`;
const id = `${applicationFound.id}-${pullmergeRequestId}`;
const engine = applicationFound.destinationDocker.engine;
await removeDestinationDocker({ id, engine });
await removeProxyConfiguration(fqdn);
}
return {

View File

@@ -7,7 +7,7 @@ import cookie from 'cookie';
export const options: RequestHandler = async () => {
return {
status: 200,
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',

View File

@@ -1,5 +1,5 @@
const defaultTheme = require('tailwindcss/defaultTheme');
const colors = require('tailwindcss/colors');
// const colors = require('tailwindcss/colors');
module.exports = {
content: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'],
important: true,
@@ -18,7 +18,6 @@ module.exports = {
sans: ['Poppins', ...defaultTheme.fontFamily.sans]
},
colors: {
...colors,
coollabs: '#6B16ED',
'coollabs-100': '#7317FF',
coolblack: '#161616',