From 1053abb9a98e8853e5be8af635f838200e0fbf7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Talha=20Zekeriya=20Durmu=C5=9F?= Date: Thu, 12 Jan 2023 01:41:35 +0100 Subject: [PATCH 01/20] Add Soketi Logo --- apps/ui/static/icons/soketi.png | Bin 0 -> 12754 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/ui/static/icons/soketi.png diff --git a/apps/ui/static/icons/soketi.png b/apps/ui/static/icons/soketi.png new file mode 100644 index 0000000000000000000000000000000000000000..aec5a79a2765f65961c58a3b8f68fc1079f4111d GIT binary patch literal 12754 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 literal 0 HcmV?d00001 From c07c742feb4f22e4a0c634c7fbb96b3fa3df857b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Talha=20Zekeriya=20Durmu=C5=9F?= Date: Thu, 12 Jan 2023 01:45:13 +0100 Subject: [PATCH 02/20] Add Repman logo --- apps/ui/static/icons/repman.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/ui/static/icons/repman.svg diff --git a/apps/ui/static/icons/repman.svg b/apps/ui/static/icons/repman.svg new file mode 100644 index 000000000..d718dddaf --- /dev/null +++ b/apps/ui/static/icons/repman.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8980598085edd9af23547ed9956114e751bb5e4d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Jan 2023 16:43:41 +0100 Subject: [PATCH 03/20] wip: trpc --- .../applications/[id]/danger/+page.svelte | 60 ++ .../applications/[id]/previews/+page.svelte | 323 +++++++ .../applications/[id]/revert/+page.svelte | 151 ++++ .../routes/applications/[id]/revert/+page.ts | 16 + .../applications/[id]/usage/+page.svelte | 116 +++ apps/client/static/icons/directus.svg | 4 + apps/client/static/icons/libretranslate.png | Bin 0 -> 2329 bytes apps/client/static/icons/openblocks.png | Bin 0 -> 6641 bytes apps/client/static/icons/whoogle.png | Bin 0 -> 10193 bytes apps/server/package.json | 1 + ...pplication.ts => applicationBuildQueue.ts} | 58 +- apps/server/src/jobs/worker.ts | 9 - apps/server/src/lib/buildPacks/common.ts | 843 ++++++++++++++++++ apps/server/src/lib/buildPacks/compose.ts | 111 +++ apps/server/src/lib/buildPacks/deno.ts | 52 ++ apps/server/src/lib/buildPacks/docker.ts | 27 + apps/server/src/lib/buildPacks/gatsby.ts | 28 + apps/server/src/lib/buildPacks/heroku.ts | 17 + apps/server/src/lib/buildPacks/index.ts | 41 + apps/server/src/lib/buildPacks/laravel.ts | 46 + apps/server/src/lib/buildPacks/nestjs.ts | 31 + apps/server/src/lib/buildPacks/nextjs.ts | 66 ++ apps/server/src/lib/buildPacks/node.ts | 49 + apps/server/src/lib/buildPacks/nuxtjs.ts | 66 ++ apps/server/src/lib/buildPacks/php.ts | 50 ++ apps/server/src/lib/buildPacks/python.ts | 67 ++ apps/server/src/lib/buildPacks/react.ts | 28 + apps/server/src/lib/buildPacks/rust.ts | 40 + apps/server/src/lib/buildPacks/static.ts | 54 ++ apps/server/src/lib/buildPacks/svelte.ts | 28 + apps/server/src/lib/buildPacks/vuejs.ts | 28 + apps/server/src/lib/common.ts | 131 +++ apps/server/src/lib/importers/github.ts | 96 ++ apps/server/src/lib/importers/gitlab.ts | 65 ++ apps/server/src/lib/importers/index.ts | 4 + apps/server/src/scheduler.ts | 14 +- apps/server/src/server.ts | 6 +- .../src/trpc/routers/applications/index.ts | 646 +++++++++++--- .../src/trpc/routers/applications/lib.ts | 12 +- pnpm-lock.yaml | 20 +- 40 files changed, 3210 insertions(+), 194 deletions(-) create mode 100644 apps/client/src/routes/applications/[id]/danger/+page.svelte create mode 100644 apps/client/src/routes/applications/[id]/previews/+page.svelte create mode 100644 apps/client/src/routes/applications/[id]/revert/+page.svelte create mode 100644 apps/client/src/routes/applications/[id]/revert/+page.ts create mode 100644 apps/client/src/routes/applications/[id]/usage/+page.svelte create mode 100644 apps/client/static/icons/directus.svg create mode 100644 apps/client/static/icons/libretranslate.png create mode 100644 apps/client/static/icons/openblocks.png create mode 100644 apps/client/static/icons/whoogle.png rename apps/server/src/jobs/{deployApplication.ts => applicationBuildQueue.ts} (94%) delete mode 100644 apps/server/src/jobs/worker.ts create mode 100644 apps/server/src/lib/buildPacks/common.ts create mode 100644 apps/server/src/lib/buildPacks/compose.ts create mode 100644 apps/server/src/lib/buildPacks/deno.ts create mode 100644 apps/server/src/lib/buildPacks/docker.ts create mode 100644 apps/server/src/lib/buildPacks/gatsby.ts create mode 100644 apps/server/src/lib/buildPacks/heroku.ts create mode 100644 apps/server/src/lib/buildPacks/index.ts create mode 100644 apps/server/src/lib/buildPacks/laravel.ts create mode 100644 apps/server/src/lib/buildPacks/nestjs.ts create mode 100644 apps/server/src/lib/buildPacks/nextjs.ts create mode 100644 apps/server/src/lib/buildPacks/node.ts create mode 100644 apps/server/src/lib/buildPacks/nuxtjs.ts create mode 100644 apps/server/src/lib/buildPacks/php.ts create mode 100644 apps/server/src/lib/buildPacks/python.ts create mode 100644 apps/server/src/lib/buildPacks/react.ts create mode 100644 apps/server/src/lib/buildPacks/rust.ts create mode 100644 apps/server/src/lib/buildPacks/static.ts create mode 100644 apps/server/src/lib/buildPacks/svelte.ts create mode 100644 apps/server/src/lib/buildPacks/vuejs.ts create mode 100644 apps/server/src/lib/importers/github.ts create mode 100644 apps/server/src/lib/importers/gitlab.ts create mode 100644 apps/server/src/lib/importers/index.ts diff --git a/apps/client/src/routes/applications/[id]/danger/+page.svelte b/apps/client/src/routes/applications/[id]/danger/+page.svelte new file mode 100644 index 000000000..4fede0195 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/danger/+page.svelte @@ -0,0 +1,60 @@ + + +
+
+
Danger Zone
+
+ + {#if forceDelete} + + {:else} + + {/if} +
diff --git a/apps/client/src/routes/applications/[id]/previews/+page.svelte b/apps/client/src/routes/applications/[id]/previews/+page.svelte new file mode 100644 index 000000000..a4095f8b1 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/previews/+page.svelte @@ -0,0 +1,323 @@ + + +
+
+
+
Preview Deployments
+
+ +
+
+
+
+ +{#if loading.init} +
+
Loading...
+
+{:else if application.previewApplication.length > 0} +
+ {#each application.previewApplication as preview} +
+
+ {#await getStatus(preview)} + + {:then} + {#if status[preview.id] === 'running'} + + {:else} + + {/if} + {/await} +
+
+

+ PR #{preview.pullmergeRequestId} + {#if status[preview.id] === 'building'} + + BUILDING + + {/if} +

+
+

{preview.customDomain.replace('https://', '').replace('http://', '')}

+
+ +
+ {#if preview.customDomain} + + + + + + + + + {/if} + Open Preview + {#if loading.restart} + + {:else} + + {/if} + + Restart (useful to change secrets) + + Force redeploy (without cache) + + Delete Preview +
+
+
+
+
+ {/each} +
+{:else} + No previews found. +{/if} diff --git a/apps/client/src/routes/applications/[id]/revert/+page.svelte b/apps/client/src/routes/applications/[id]/revert/+page.svelte new file mode 100644 index 000000000..80013e0f0 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/revert/+page.svelte @@ -0,0 +1,151 @@ + + +
+
+
+
+ Revert +
+
+
+ If you do not want the next commit to overwrite the reverted application, temporary disable Automatic Deployment + feature here. +
+ {#if imagesAvailables.length > 0} +
Local Images
+
+ {#each imagesAvailables as image} +
+
+
+ {image.tag} +
+
+ + + {#if image.repository + ':' + image.tag !== runningImage} + + {:else} + + {/if} +
+
+
+ {/each} +
+ {:else} +
+
No Local images available
+
+ {/if} +
+ Remote Images (Docker Registry) +
+
+ + +
+
+
diff --git a/apps/client/src/routes/applications/[id]/revert/+page.ts b/apps/client/src/routes/applications/[id]/revert/+page.ts new file mode 100644 index 000000000..2fc882718 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/revert/+page.ts @@ -0,0 +1,16 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { PageLoad } from './$types'; +export const ssr = false; + +export const load: PageLoad = async ({ params }) => { + try { + const { id } = params; + const { data } = await trpc.applications.getLocalImages.query({ id }); + return data; + } catch (err) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/applications/[id]/usage/+page.svelte b/apps/client/src/routes/applications/[id]/usage/+page.svelte new file mode 100644 index 000000000..656c697e3 --- /dev/null +++ b/apps/client/src/routes/applications/[id]/usage/+page.svelte @@ -0,0 +1,116 @@ + + +
+
+
Monitoring
+
+
+
+ {#each services as service} + + {/each} +
+{#if selectedService} +
+ {#if usageLoading} +
+{/if} diff --git a/apps/client/static/icons/directus.svg b/apps/client/static/icons/directus.svg new file mode 100644 index 000000000..e530a836b --- /dev/null +++ b/apps/client/static/icons/directus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/client/static/icons/libretranslate.png b/apps/client/static/icons/libretranslate.png new file mode 100644 index 0000000000000000000000000000000000000000..b22316b68597295cb1ae8dcd04c4608b6b1f62d0 GIT binary patch literal 2329 zcmZ{mc{~%0AIE1TjkUz&CdZt=4AH8s9COSy2`zJGO;L`BWx1DJsX1e`+{zUqSDCr* zEa!xY5IHJ6tdytckKgmx@At>&`+0pn|9-!(*Y_Q7YhwlmNrC_X02pJAw)^9_e zGk@=tcmeDZz=t))iq-c4x?B$H*joF=b z&Ar9hFhKJQa)?)GYXto4&d|nY&hRhP(1OthVx7+zKza#|gG$Ih5;|heFQCNBm%;<= z1w;`d4Rj3T|E+b{{CsZ(8`BC63S7KDW#&fP#B@o=am7AoNhdG>kylI*bFh|6We1ZU z<0YqWc8DK%Xr+X}na~3~1^KKQA`+`g=}<((C6?F9B=CzpEGzqI_9#mI?EC!)T?Lfz z&o&Duva&MI`G#1Q*EWtv*o;XXv_h_v!?0rCbA?dB+m8W;?ps}AFh#XUZ3O>$GGz#x z*pNGNQ2Tyt0e3sY{>q1hnIik(=VT>gh7`>j>qSL~8S2JumqduP(djmVnz#|qV!dw; zAYb9bnYH^3ip4N@%kq`eR0Ogq?x}ynxQ1Cz%C(`mAdSDKWd?LMp#jBFelqra5;o&o zw^6QM%fF$cmOg6gIxsVEN0-Si`62C?GD*UAL@kV^nH&)uzcqffvbVBE9zIF8e(l0Q z5KH;nhr>b7x>SM!si@e)xKT0+Ckm)hR8~?-kWW@oT2yypZ-b zzXxod6tbZe+a}2q5%oL+G{?>Oe9-xEIQk;my3`ubC21>LpdXxVzVCEG_1 z;vmNPL&ux)~6rJF=Mid#c({MMY!m-f(kjY|Ruh4kUOBZ}+z4H_c+v_j^n zVCDv9&dEZ0cJZ=0h#_CrayNOMHnj3|>0-Jm_Z6VWCD^YjD$w^y`a|#r&XKaWPmLci0I%F`1FkBj2x$-AxL( zC@l^l9Cz@VI@qo^_3={wWcRyxdX*O}KPCK>sKc$wBWq+`o@%)4`IkI*RC(X;T7csA z7hu|6veb0+-g^Dk9_{bIj z4W6nsG4ey9179SthB>gQnr2`#`-82Mw60KP{-dA~gi`mjZp<2E8v>`ceK^^vaRPW? zMm5qrxxF01GBb=GZ~J7Z8P|R7K~bVeyI-)VLAhJ$li;w*rzt(38(o8!iue<-#1hrW z9_+1iaX8yRy zXDi6EO`b*^w3kGDy3{#TMDUOmfg5`YkOstdM>hM5HRoxfP0^F7CP!0+8uZdt2RsMD z1zQ=WIK8#}N<8@Z65fBm#kY$E>F6UMv4O68-|9(?@5)MFp1!yHUCcBU&vUQ6_Vn|z z!>3wYdT)cvx(8-&sLx0~Suj>PqICGYQ$@WJPWYVf(PW3Mecx|as+H3x=@m?Nu1WE= z)n8B-CLB`qz#H0U#moa+;m*jMi6Wi2KU!ZqR0l%Ym^g9>;E3W2k51-w1U3dmJrJ+j zmhHK);B7-`O|C=sO&2AutG|Kv?pEn@zb6}K%F4f~rjSe*QZ8;0Ru<^W2P{_avG~Ik znnbUPFQuiC%KbGw$3Fu~q_{)X;`WZ5kV{;fLEzLIil#tN4W%$H2It-BdM9nU$>KUxwD z2lR~R9ZjXUqdOK}v@pR<5NW*U*lSk2_eNjXr$J;@Y-#F!k+B@?=CWg}vt3n2_<|}T z+s`Qr85p7nzi7qT9lMQ6VJHIQ05|zV{g2e$o%x%gboi73ZAw z!lT!T9(rwOr>)nH4ZJN7h(#Ku#MrLuS%7e~2?txO1zcXwWWwX@FEj6H{^V12DuSxwws`V)B7j;nBq z6y?ut`z?TP`8VW@jhADjp9~j0TzVIzI%ERi;|~-co;#HODpAgv{(SK5vYm4_uu`>M zuIiiIj8MIXXeVNB;x>#>56)Y2+ULH**bETmS$7 literal 0 HcmV?d00001 diff --git a/apps/client/static/icons/openblocks.png b/apps/client/static/icons/openblocks.png new file mode 100644 index 0000000000000000000000000000000000000000..b64d1bab30aba85a707b99db4f932e6d321ee04f GIT binary patch literal 6641 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 Y7JR_`w5g2#U7!Q0D!za=$Xi7IFV|i#_5c6? literal 0 HcmV?d00001 diff --git a/apps/server/package.json b/apps/server/package.json index a4ee12018..45177559a 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -24,6 +24,7 @@ "@fastify/jwt": "6.5.0", "@fastify/static": "6.6.0", "@fastify/websocket": "7.1.1", + "@iarna/toml": "2.2.5", "@ladjs/graceful": "3.0.2", "@prisma/client": "4.6.1", "@trpc/client": "10.1.0", diff --git a/apps/server/src/jobs/deployApplication.ts b/apps/server/src/jobs/applicationBuildQueue.ts similarity index 94% rename from apps/server/src/jobs/deployApplication.ts rename to apps/server/src/jobs/applicationBuildQueue.ts index 6f79b94e8..fd7bf076a 100644 --- a/apps/server/src/jobs/deployApplication.ts +++ b/apps/server/src/jobs/applicationBuildQueue.ts @@ -14,17 +14,16 @@ import { import { createDirectories, decrypt, - defaultComposeConfiguration, getDomain, - prisma, + generateSecrets, decryptApplication, - isDev, - pushToRegistry, - executeCommand, - generateSecrets + pushToRegistry } from '../lib/common'; import * as importers from '../lib/importers'; import * as buildpacks from '../lib/buildPacks'; +import { prisma } from '../prisma'; +import { executeCommand } from '../lib/executeCommand'; +import { defaultComposeConfiguration } from '../lib/docker'; (async () => { if (parentPort) { @@ -532,6 +531,48 @@ import * as buildpacks from '../lib/buildPacks'; }); if (forceRebuild) deployNeeded = true; if ((!imageFoundLocally && !imageFoundRemotely) || deployNeeded) { + if (buildPack === 'static') { + await buildpacks.staticApp({ + dockerId: destinationDocker.id, + network: destinationDocker.network, + buildId, + applicationId, + domain, + name, + type, + volumes, + labels, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + publishDirectory, + debug, + commit, + tag, + workdir, + port: exposePort ? `${exposePort}:${port}` : port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + phpModules, + pythonWSGI, + pythonModule, + pythonVariable, + dockerFileLocation, + dockerComposeConfiguration, + dockerComposeFileLocation, + denoMainFile, + denoOptions, + baseImage, + baseBuildImage, + deploymentType, + forceRebuild + }); + } if (buildpacks[buildPack]) await buildpacks[buildPack]({ dockerId: destinationDocker.id, @@ -803,5 +844,8 @@ import * as buildpacks from '../lib/buildPacks'; while (true) { await th(); } - } else process.exit(0); + } else { + console.log('hello'); + process.exit(0); + } })(); diff --git a/apps/server/src/jobs/worker.ts b/apps/server/src/jobs/worker.ts deleted file mode 100644 index 981171c92..000000000 --- a/apps/server/src/jobs/worker.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { parentPort } from 'node:worker_threads'; -import process from 'node:process'; - -console.log('Hello TypeScript!'); - -// signal to parent that the job is done -if (parentPort) parentPort.postMessage('done'); -// eslint-disable-next-line unicorn/no-process-exit -else process.exit(0); diff --git a/apps/server/src/lib/buildPacks/common.ts b/apps/server/src/lib/buildPacks/common.ts new file mode 100644 index 000000000..ebbd83583 --- /dev/null +++ b/apps/server/src/lib/buildPacks/common.ts @@ -0,0 +1,843 @@ +import { + base64Encode, + decrypt, + encrypt, + generateSecrets, + generateTimestamp, + getDomain, + isARM, + isDev, + version +} from '../common'; +import { promises as fs } from 'fs'; +import { day } from '../dayjs'; +import { prisma } from '../../prisma'; +import { executeCommand } from '../executeCommand'; + +const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy']; +const nodeBased = [ + 'react', + 'preact', + 'vuejs', + 'svelte', + 'gatsby', + 'astro', + 'eleventy', + 'node', + 'nestjs', + 'nuxtjs', + 'nextjs' +]; + +export function setDefaultBaseImage( + buildPack: string | null, + deploymentType: string | null = null +) { + const nodeVersions = [ + { + value: 'node:lts', + label: 'node:lts' + }, + { + value: 'node:18', + label: 'node:18' + }, + { + value: 'node:17', + label: 'node:17' + }, + { + value: 'node:16', + label: 'node:16' + }, + { + value: 'node:14', + label: 'node:14' + }, + { + value: 'node:12', + label: 'node:12' + } + ]; + const staticVersions = [ + { + value: 'webdevops/nginx:alpine', + label: 'webdevops/nginx:alpine' + }, + { + value: 'webdevops/apache:alpine', + label: 'webdevops/apache:alpine' + }, + { + value: 'nginx:alpine', + label: 'nginx:alpine' + }, + { + value: 'httpd:alpine', + label: 'httpd:alpine (Apache)' + } + ]; + const rustVersions = [ + { + value: 'rust:latest', + label: 'rust:latest' + }, + { + value: 'rust:1.60', + label: 'rust:1.60' + }, + { + value: 'rust:1.60-buster', + label: 'rust:1.60-buster' + }, + { + value: 'rust:1.60-bullseye', + label: 'rust:1.60-bullseye' + }, + { + value: 'rust:1.60-slim-buster', + label: 'rust:1.60-slim-buster' + }, + { + value: 'rust:1.60-slim-bullseye', + label: 'rust:1.60-slim-bullseye' + }, + { + value: 'rust:1.60-alpine3.14', + label: 'rust:1.60-alpine3.14' + }, + { + value: 'rust:1.60-alpine3.15', + label: 'rust:1.60-alpine3.15' + } + ]; + const phpVersions = [ + { + value: 'webdevops/php-apache:8.2', + label: 'webdevops/php-apache:8.2' + }, + { + value: 'webdevops/php-nginx:8.2', + label: 'webdevops/php-nginx:8.2' + }, + { + value: 'webdevops/php-apache:8.1', + label: 'webdevops/php-apache:8.1' + }, + { + value: 'webdevops/php-nginx:8.1', + label: 'webdevops/php-nginx:8.1' + }, + { + value: 'webdevops/php-apache:8.0', + label: 'webdevops/php-apache:8.0' + }, + { + value: 'webdevops/php-nginx:8.0', + label: 'webdevops/php-nginx:8.0' + }, + { + value: 'webdevops/php-apache:7.4', + label: 'webdevops/php-apache:7.4' + }, + { + value: 'webdevops/php-nginx:7.4', + label: 'webdevops/php-nginx:7.4' + }, + { + value: 'webdevops/php-apache:7.3', + label: 'webdevops/php-apache:7.3' + }, + { + value: 'webdevops/php-nginx:7.3', + label: 'webdevops/php-nginx:7.3' + }, + { + value: 'webdevops/php-apache:7.2', + label: 'webdevops/php-apache:7.2' + }, + { + value: 'webdevops/php-nginx:7.2', + label: 'webdevops/php-nginx:7.2' + }, + { + value: 'webdevops/php-apache:7.1', + label: 'webdevops/php-apache:7.1' + }, + { + value: 'webdevops/php-nginx:7.1', + label: 'webdevops/php-nginx:7.1' + }, + { + value: 'webdevops/php-apache:7.0', + label: 'webdevops/php-apache:7.0' + }, + { + value: 'webdevops/php-nginx:7.0', + label: 'webdevops/php-nginx:7.0' + }, + { + value: 'webdevops/php-apache:5.6', + label: 'webdevops/php-apache:5.6' + }, + { + value: 'webdevops/php-nginx:5.6', + label: 'webdevops/php-nginx:5.6' + }, + { + value: 'webdevops/php-apache:8.2-alpine', + label: 'webdevops/php-apache:8.2-alpine' + }, + { + value: 'webdevops/php-nginx:8.2-alpine', + label: 'webdevops/php-nginx:8.2-alpine' + }, + { + value: 'webdevops/php-apache:8.1-alpine', + label: 'webdevops/php-apache:8.1-alpine' + }, + { + value: 'webdevops/php-nginx:8.1-alpine', + label: 'webdevops/php-nginx:8.1-alpine' + }, + { + value: 'webdevops/php-apache:8.0-alpine', + label: 'webdevops/php-apache:8.0-alpine' + }, + { + value: 'webdevops/php-nginx:8.0-alpine', + label: 'webdevops/php-nginx:8.0-alpine' + }, + { + value: 'webdevops/php-apache:7.4-alpine', + label: 'webdevops/php-apache:7.4-alpine' + }, + { + value: 'webdevops/php-nginx:7.4-alpine', + label: 'webdevops/php-nginx:7.4-alpine' + }, + { + value: 'webdevops/php-apache:7.3-alpine', + label: 'webdevops/php-apache:7.3-alpine' + }, + { + value: 'webdevops/php-nginx:7.3-alpine', + label: 'webdevops/php-nginx:7.3-alpine' + }, + { + value: 'webdevops/php-apache:7.2-alpine', + label: 'webdevops/php-apache:7.2-alpine' + }, + { + value: 'webdevops/php-nginx:7.2-alpine', + label: 'webdevops/php-nginx:7.2-alpine' + }, + { + value: 'webdevops/php-apache:7.1-alpine', + label: 'webdevops/php-apache:7.1-alpine' + }, + { + value: 'php:8.1-fpm', + label: 'php:8.1-fpm' + }, + { + value: 'php:8.0-fpm', + label: 'php:8.0-fpm' + }, + { + value: 'php:8.1-fpm-alpine', + label: 'php:8.1-fpm-alpine' + }, + { + value: 'php:8.0-fpm-alpine', + label: 'php:8.0-fpm-alpine' + } + ]; + const pythonVersions = [ + { + value: 'python:3.10-alpine', + label: 'python:3.10-alpine' + }, + { + value: 'python:3.10-buster', + label: 'python:3.10-buster' + }, + { + value: 'python:3.10-bullseye', + label: 'python:3.10-bullseye' + }, + { + value: 'python:3.10-slim-bullseye', + label: 'python:3.10-slim-bullseye' + }, + { + value: 'python:3.9-alpine', + label: 'python:3.9-alpine' + }, + { + value: 'python:3.9-buster', + label: 'python:3.9-buster' + }, + { + value: 'python:3.9-bullseye', + label: 'python:3.9-bullseye' + }, + { + value: 'python:3.9-slim-bullseye', + label: 'python:3.9-slim-bullseye' + }, + { + value: 'python:3.8-alpine', + label: 'python:3.8-alpine' + }, + { + value: 'python:3.8-buster', + label: 'python:3.8-buster' + }, + { + value: 'python:3.8-bullseye', + label: 'python:3.8-bullseye' + }, + { + value: 'python:3.8-slim-bullseye', + label: 'python:3.8-slim-bullseye' + }, + { + value: 'python:3.7-alpine', + label: 'python:3.7-alpine' + }, + { + value: 'python:3.7-buster', + label: 'python:3.7-buster' + }, + { + value: 'python:3.7-bullseye', + label: 'python:3.7-bullseye' + }, + { + value: 'python:3.7-slim-bullseye', + label: 'python:3.7-slim-bullseye' + } + ]; + const herokuVersions = [ + { + value: 'heroku/builder:22', + label: 'heroku/builder:22' + }, + { + value: 'heroku/buildpacks:20', + label: 'heroku/buildpacks:20' + }, + { + value: 'heroku/builder-classic:22', + label: 'heroku/builder-classic:22' + } + ]; + let payload: any = { + baseImage: null, + baseBuildImage: null, + baseImages: [], + baseBuildImages: [] + }; + if (nodeBased.includes(buildPack)) { + if (deploymentType === 'static') { + payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine'; + payload.baseImages = isARM(process.arch) + ? staticVersions.filter((version) => !version.value.includes('webdevops')) + : staticVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } else { + payload.baseImage = 'node:lts'; + payload.baseImages = nodeVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } + } + if (staticApps.includes(buildPack)) { + payload.baseImage = isARM(process.arch) ? 'nginx:alpine' : 'webdevops/nginx:alpine'; + payload.baseImages = isARM(process.arch) + ? staticVersions.filter((version) => !version.value.includes('webdevops')) + : staticVersions; + payload.baseBuildImage = 'node:lts'; + payload.baseBuildImages = nodeVersions; + } + if (buildPack === 'python') { + payload.baseImage = 'python:3.10-alpine'; + payload.baseImages = pythonVersions; + } + if (buildPack === 'rust') { + payload.baseImage = 'rust:latest'; + payload.baseBuildImage = 'rust:latest'; + payload.baseImages = rustVersions; + payload.baseBuildImages = rustVersions; + } + if (buildPack === 'deno') { + payload.baseImage = 'denoland/deno:latest'; + } + if (buildPack === 'php') { + payload.baseImage = isARM(process.arch) + ? 'php:8.1-fpm-alpine' + : 'webdevops/php-apache:8.2-alpine'; + payload.baseImages = isARM(process.arch) + ? phpVersions.filter((version) => !version.value.includes('webdevops')) + : phpVersions; + } + if (buildPack === 'laravel') { + payload.baseImage = isARM(process.arch) + ? 'php:8.1-fpm-alpine' + : 'webdevops/php-apache:8.2-alpine'; + payload.baseImages = isARM(process.arch) + ? phpVersions.filter((version) => !version.value.includes('webdevops')) + : phpVersions; + payload.baseBuildImage = 'node:18'; + payload.baseBuildImages = nodeVersions; + } + if (buildPack === 'heroku') { + payload.baseImage = 'heroku/buildpacks:20'; + payload.baseImages = herokuVersions; + } + return payload; +} + +export const setDefaultConfiguration = async (data: any) => { + let { + buildPack, + port, + installCommand, + startCommand, + buildCommand, + publishDirectory, + baseDirectory, + dockerFileLocation, + dockerComposeFileLocation, + denoMainFile + } = data; + //@ts-ignore + const template = scanningTemplates[buildPack]; + if (!port) { + port = template?.port || 3000; + + if (buildPack === 'static') port = 80; + else if (buildPack === 'node') port = 3000; + else if (buildPack === 'php') port = 80; + else if (buildPack === 'python') port = 8000; + } + if (!installCommand && buildPack !== 'static' && buildPack !== 'laravel') + installCommand = template?.installCommand || 'yarn install'; + if (!startCommand && buildPack !== 'static' && buildPack !== 'laravel') + startCommand = template?.startCommand || 'yarn start'; + if (!buildCommand && buildPack !== 'static' && buildPack !== 'laravel') + buildCommand = template?.buildCommand || null; + if (!publishDirectory) publishDirectory = template?.publishDirectory || null; + if (baseDirectory) { + if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`; + if (baseDirectory.endsWith('/') && baseDirectory !== '/') + baseDirectory = baseDirectory.slice(0, -1); + } + if (dockerFileLocation) { + if (!dockerFileLocation.startsWith('/')) dockerFileLocation = `/${dockerFileLocation}`; + if (dockerFileLocation.endsWith('/')) dockerFileLocation = dockerFileLocation.slice(0, -1); + } else { + dockerFileLocation = '/Dockerfile'; + } + if (dockerComposeFileLocation) { + if (!dockerComposeFileLocation.startsWith('/')) + dockerComposeFileLocation = `/${dockerComposeFileLocation}`; + if (dockerComposeFileLocation.endsWith('/')) + dockerComposeFileLocation = dockerComposeFileLocation.slice(0, -1); + } else { + dockerComposeFileLocation = '/Dockerfile'; + } + if (!denoMainFile) { + denoMainFile = 'main.ts'; + } + + return { + buildPack, + port, + installCommand, + startCommand, + buildCommand, + publishDirectory, + baseDirectory, + dockerFileLocation, + dockerComposeFileLocation, + denoMainFile + }; +}; + +export const scanningTemplates = { + '@sveltejs/kit': { + buildPack: 'nodejs' + }, + astro: { + buildPack: 'astro' + }, + '@11ty/eleventy': { + buildPack: 'eleventy' + }, + svelte: { + buildPack: 'svelte' + }, + '@nestjs/core': { + buildPack: 'nestjs' + }, + next: { + buildPack: 'nextjs' + }, + nuxt: { + buildPack: 'nuxtjs' + }, + 'react-scripts': { + buildPack: 'react' + }, + 'parcel-bundler': { + buildPack: 'static' + }, + '@vue/cli-service': { + buildPack: 'vuejs' + }, + vuejs: { + buildPack: 'vuejs' + }, + gatsby: { + buildPack: 'gatsby' + }, + 'preact-cli': { + buildPack: 'react' + } +}; + +export const saveBuildLog = async ({ + line, + buildId, + applicationId +}: { + line: string; + buildId: string; + applicationId: string; +}): Promise => { + if (buildId === 'undefined' || buildId === 'null' || !buildId) return; + if (applicationId === 'undefined' || applicationId === 'null' || !applicationId) return; + const { default: got } = await import('got'); + if (typeof line === 'object' && line) { + if (line.shortMessage) { + line = line.shortMessage + '\n' + line.stderr; + } else { + line = JSON.stringify(line); + } + } + if (line && typeof line === 'string' && line.includes('ghs_')) { + const regex = /ghs_.*@/g; + line = line.replace(regex, '@'); + } + const addTimestamp = `[${generateTimestamp()}] ${line}`; + const fluentBitUrl = isDev + ? process.env.COOLIFY_CONTAINER_DEV === 'true' + ? 'http://coolify-fluentbit:24224' + : 'http://localhost:24224' + : 'http://coolify-fluentbit:24224'; + + if (isDev && !process.env.COOLIFY_CONTAINER_DEV) { + console.debug(`[${applicationId}] ${addTimestamp}`); + } + try { + return await got.post(`${fluentBitUrl}/${applicationId}_buildlog_${buildId}.csv`, { + json: { + line: encrypt(line) + } + }); + } catch (error) { + return await prisma.buildLog.create({ + data: { + line: addTimestamp, + buildId, + time: Number(day().valueOf()), + applicationId + } + }); + } +}; + +export async function copyBaseConfigurationFiles( + buildPack, + workdir, + buildId, + applicationId, + baseImage +) { + try { + if (buildPack === 'php') { + await fs.writeFile(`${workdir}/entrypoint.sh`, `chown -R 1000 /app`); + await saveBuildLog({ + line: 'Copied default configuration file for PHP.', + buildId, + applicationId + }); + } else if (baseImage?.includes('nginx')) { + await fs.writeFile( + `${workdir}/nginx.conf`, + `user nginx; + worker_processes auto; + + error_log /docker.stdout; + pid /run/nginx.pid; + + events { + worker_connections 1024; + } + + http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /docker.stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + server_name localhost; + + location / { + root /app; + index index.html; + try_files $uri $uri/index.html $uri/ /index.html =404; + } + + error_page 404 /50x.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /app; + } + + } + + } + ` + ); + } + // TODO: Add more configuration files for other buildpacks, like apache2, etc. + } catch (error) { + throw new Error(error); + } +} + +export function checkPnpm(installCommand = null, buildCommand = null, startCommand = null) { + return ( + installCommand?.includes('pnpm') || + buildCommand?.includes('pnpm') || + startCommand?.includes('pnpm') + ); +} + +export async function saveDockerRegistryCredentials({ url, username, password, workdir }) { + if (!username || !password) { + return null; + } + + let decryptedPassword = decrypt(password); + const location = `${workdir}/.docker`; + + try { + await fs.mkdir(`${workdir}/.docker`); + } catch (error) { + // console.log(error); + } + const payload = JSON.stringify({ + auths: { + [url]: { + auth: Buffer.from(`${username}:${decryptedPassword}`).toString('base64') + } + } + }); + await fs.writeFile(`${location}/config.json`, payload); + return location; +} +export async function buildImage({ + applicationId, + tag, + workdir, + buildId, + dockerId, + isCache = false, + debug = false, + dockerFileLocation = '/Dockerfile', + commit, + forceRebuild = false +}) { + if (isCache) { + await saveBuildLog({ line: `Building cache image...`, buildId, applicationId }); + } else { + await saveBuildLog({ line: `Building production image...`, buildId, applicationId }); + } + const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`; + const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`; + let location = null; + + const { dockerRegistry } = await prisma.application.findUnique({ + where: { id: applicationId }, + select: { dockerRegistry: true } + }); + if (dockerRegistry) { + const { url, username, password } = dockerRegistry; + location = await saveDockerRegistryCredentials({ url, username, password, workdir }); + } + + await executeCommand({ + stream: true, + debug, + buildId, + applicationId, + dockerId, + command: `docker ${location ? `--config ${location}` : ''} build ${ + forceRebuild ? '--no-cache' : '' + } --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` + }); + + const { status } = await prisma.build.findUnique({ where: { id: buildId } }); + if (status === 'canceled') { + throw new Error('Canceled.'); + } +} +export function makeLabelForSimpleDockerfile({ applicationId, port, type }) { + return [ + 'coolify.managed=true', + `coolify.version=${version}`, + `coolify.applicationId=${applicationId}`, + `coolify.type=standalone-application` + ]; +} +export function makeLabelForStandaloneApplication({ + applicationId, + fqdn, + name, + type, + pullmergeRequestId = null, + buildPack, + repository, + branch, + projectId, + port, + commit, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory +}) { + if (pullmergeRequestId) { + const protocol = fqdn.startsWith('https://') ? 'https' : 'http'; + const domain = getDomain(fqdn); + fqdn = `${protocol}://${pullmergeRequestId}.${domain}`; + } + return [ + 'coolify.managed=true', + `coolify.version=${version}`, + `coolify.applicationId=${applicationId}`, + `coolify.type=standalone-application`, + `coolify.name=${name}`, + `coolify.configuration=${base64Encode( + JSON.stringify({ + applicationId, + fqdn, + name, + type, + pullmergeRequestId, + buildPack, + repository, + branch, + projectId, + port, + commit, + installCommand, + buildCommand, + startCommand, + baseDirectory, + publishDirectory + }) + )}` + ]; +} + +export async function buildCacheImageWithNode(data, imageForBuild) { + const { + workdir, + buildId, + baseDirectory, + installCommand, + buildCommand, + secrets, + pullmergeRequestId + } = data; + const isPnpm = checkPnpm(installCommand, buildCommand); + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); + } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + if (installCommand) { + Dockerfile.push(`RUN ${installCommand}`); + } + Dockerfile.push(`RUN ${buildCommand}`); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ ...data, isCache: true }); +} + +export async function buildCacheImageForLaravel(data, imageForBuild) { + const { workdir, buildId, secrets, pullmergeRequestId } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + Dockerfile.push(`COPY *.json *.mix.js /app/`); + Dockerfile.push(`COPY resources /app/resources`); + Dockerfile.push(`RUN yarn install && yarn production`); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ ...data, isCache: true }); +} + +export async function buildCacheImageWithCargo(data, imageForBuild) { + const { applicationId, workdir, buildId } = data; + + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push('RUN cargo install cargo-chef'); + Dockerfile.push('COPY . .'); + Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); + Dockerfile.push(`FROM ${imageForBuild}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push('RUN cargo install cargo-chef'); + Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); + Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); + await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); + await buildImage({ ...data, isCache: true }); +} diff --git a/apps/server/src/lib/buildPacks/compose.ts b/apps/server/src/lib/buildPacks/compose.ts new file mode 100644 index 000000000..695e205c9 --- /dev/null +++ b/apps/server/src/lib/buildPacks/compose.ts @@ -0,0 +1,111 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { saveBuildLog } from './common'; +import yaml from 'js-yaml'; +import { defaultComposeConfiguration } from '../docker'; +import { executeCommand } from '../executeCommand'; + +export default async function (data) { + let { + applicationId, + debug, + buildId, + dockerId, + network, + volumes, + labels, + workdir, + baseDirectory, + secrets, + pullmergeRequestId, + dockerComposeConfiguration, + dockerComposeFileLocation + } = data; + const fileYaml = `${workdir}${baseDirectory}${dockerComposeFileLocation}`; + const dockerComposeRaw = await fs.readFile(fileYaml, 'utf8'); + const dockerComposeYaml = yaml.load(dockerComposeRaw); + if (!dockerComposeYaml.services) { + throw 'No Services found in docker-compose file.'; + } + let envs = []; + if (secrets.length > 0) { + envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, null)]; + } + + const composeVolumes = []; + if (volumes.length > 0) { + for (const volume of volumes) { + let [v, path] = volume.split(':'); + composeVolumes[v] = { + name: v + }; + } + } + + let networks = {}; + for (let [key, value] of Object.entries(dockerComposeYaml.services)) { + value['container_name'] = `${applicationId}-${key}`; + let environment = typeof value['environment'] === 'undefined' ? [] : value['environment'] + value['environment'] = [...environment, ...envs]; + value['labels'] = labels; + // TODO: If we support separated volume for each service, we need to add it here + if (value['volumes']?.length > 0) { + value['volumes'] = value['volumes'].map((volume) => { + let [v, path, permission] = volume.split(':'); + if (!path) { + path = v; + v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`; + } else { + v = `${applicationId}${v.replace(/\//gi, '-').replace(/\./gi, '')}`; + } + composeVolumes[v] = { + name: v + }; + return `${v}:${path}${permission ? ':' + permission : ''}`; + }); + } + if (volumes.length > 0) { + for (const volume of volumes) { + value['volumes'].push(volume); + } + } + if (dockerComposeConfiguration[key].port) { + value['expose'] = [dockerComposeConfiguration[key].port]; + } + if (value['networks']?.length > 0) { + value['networks'].forEach((network) => { + networks[network] = { + name: network + }; + }); + } + value['networks'] = [...(value['networks'] || ''), network]; + dockerComposeYaml.services[key] = { + ...dockerComposeYaml.services[key], + restart: defaultComposeConfiguration(network).restart, + deploy: defaultComposeConfiguration(network).deploy + }; + } + if (Object.keys(composeVolumes).length > 0) { + dockerComposeYaml['volumes'] = { ...composeVolumes }; + } + dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } }); + + await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml)); + await executeCommand({ + debug, + buildId, + applicationId, + dockerId, + command: `docker compose --project-directory ${workdir} pull` + }); + await saveBuildLog({ line: 'Pulling images from Compose file...', buildId, applicationId }); + await executeCommand({ + debug, + buildId, + applicationId, + dockerId, + command: `docker compose --project-directory ${workdir} build --progress plain` + }); + await saveBuildLog({ line: 'Building images from Compose file...', buildId, applicationId }); +} diff --git a/apps/server/src/lib/buildPacks/deno.ts b/apps/server/src/lib/buildPacks/deno.ts new file mode 100644 index 000000000..2649e3d0a --- /dev/null +++ b/apps/server/src/lib/buildPacks/deno.ts @@ -0,0 +1,52 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + workdir, + port, + baseDirectory, + secrets, + pullmergeRequestId, + denoMainFile, + denoOptions, + buildId + } = data; + const Dockerfile: Array = []; + + let depsFound = false; + try { + await fs.readFile(`${workdir}${baseDirectory || ''}/deps.ts`); + depsFound = true; + } catch (error) {} + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (depsFound) { + Dockerfile.push(`COPY .${baseDirectory || ''}/deps.ts /app`); + Dockerfile.push(`RUN deno cache deps.ts`); + } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN deno cache ${denoMainFile}`); + Dockerfile.push(`ENV NO_COLOR true`); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD deno run ${denoOptions || ''} ${denoMainFile}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/docker.ts b/apps/server/src/lib/buildPacks/docker.ts new file mode 100644 index 000000000..e02103f88 --- /dev/null +++ b/apps/server/src/lib/buildPacks/docker.ts @@ -0,0 +1,27 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildImage } from './common'; + +export default async function (data) { + let { workdir, buildId, baseDirectory, secrets, pullmergeRequestId, dockerFileLocation } = data; + const file = `${workdir}${baseDirectory}${dockerFileLocation}`; + data.workdir = `${workdir}${baseDirectory}`; + const DockerfileRaw = await fs.readFile(`${file}`, 'utf8'); + const Dockerfile: Array = DockerfileRaw.toString().trim().split('\n'); + Dockerfile.forEach((line, index) => { + if (line.startsWith('FROM')) { + Dockerfile.splice(index + 1, 0, `LABEL coolify.buildId=${buildId}`); + } + }); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.forEach((line, index) => { + if (line.startsWith('FROM')) { + Dockerfile.splice(index + 1, 0, env); + } + }); + }); + } + await fs.writeFile(`${data.workdir}${dockerFileLocation}`, Dockerfile.join('\n')); + await buildImage(data); +} diff --git a/apps/server/src/lib/buildPacks/gatsby.ts b/apps/server/src/lib/buildPacks/gatsby.ts new file mode 100644 index 000000000..fbb0a933f --- /dev/null +++ b/apps/server/src/lib/buildPacks/gatsby.ts @@ -0,0 +1,28 @@ +import { promises as fs } from 'fs'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, imageforBuild): Promise => { + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${imageforBuild}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/heroku.ts b/apps/server/src/lib/buildPacks/heroku.ts new file mode 100644 index 000000000..a58fd8fcf --- /dev/null +++ b/apps/server/src/lib/buildPacks/heroku.ts @@ -0,0 +1,17 @@ +import { executeCommand } from "../executeCommand"; +import { saveBuildLog } from "./common"; + +export default async function (data: any): Promise { + const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data + try { + await saveBuildLog({ line: `Building production image...`, buildId, applicationId }); + await executeCommand({ + buildId, + debug, + dockerId, + command: `pack build -p ${workdir}${baseDirectory} ${applicationId}:${tag} --builder ${baseImage}` + }) + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/index.ts b/apps/server/src/lib/buildPacks/index.ts new file mode 100644 index 000000000..fa10e4bcd --- /dev/null +++ b/apps/server/src/lib/buildPacks/index.ts @@ -0,0 +1,41 @@ +import node from './node'; +import staticApp from './static'; +import docker from './docker'; +import gatsby from './gatsby'; +import svelte from './svelte'; +import react from './react'; +import nestjs from './nestjs'; +import nextjs from './nextjs'; +import nuxtjs from './nuxtjs'; +import vuejs from './vuejs'; +import php from './php'; +import rust from './rust'; +import astro from './static'; +import eleventy from './static'; +import python from './python'; +import deno from './deno'; +import laravel from './laravel'; +import heroku from './heroku'; +import compose from './compose'; + +export { + node, + staticApp, + docker, + gatsby, + svelte, + react, + nestjs, + nextjs, + nuxtjs, + vuejs, + php, + rust, + astro, + eleventy, + python, + deno, + laravel, + heroku, + compose +}; diff --git a/apps/server/src/lib/buildPacks/laravel.ts b/apps/server/src/lib/buildPacks/laravel.ts new file mode 100644 index 000000000..159e8d9ca --- /dev/null +++ b/apps/server/src/lib/buildPacks/laravel.ts @@ -0,0 +1,46 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildCacheImageForLaravel, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { workdir, applicationId, tag, buildId, port, secrets, pullmergeRequestId } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`); + Dockerfile.push(`COPY --chown=application:application composer.* ./`); + Dockerfile.push(`COPY --chown=application:application database/ database/`); + Dockerfile.push( + `RUN composer install --ignore-platform-reqs --no-interaction --no-plugins --no-scripts --prefer-dist` + ); + Dockerfile.push( + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/public/js/ /app/public/js/` + ); + Dockerfile.push( + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/public/css/ /app/public/css/` + ); + Dockerfile.push( + `COPY --chown=application:application --from=${applicationId}:${tag}-cache /app/mix-manifest.json /app/public/mix-manifest.json` + ); + Dockerfile.push(`COPY --chown=application:application . ./`); + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + const { baseImage, baseBuildImage } = data; + try { + await buildCacheImageForLaravel(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/nestjs.ts b/apps/server/src/lib/buildPacks/nestjs.ts new file mode 100644 index 000000000..90c99301b --- /dev/null +++ b/apps/server/src/lib/buildPacks/nestjs.ts @@ -0,0 +1,31 @@ +import { promises as fs } from 'fs'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { buildId, applicationId, tag, port, startCommand, workdir, baseDirectory } = data; + const Dockerfile: Array = []; + const isPnpm = startCommand.includes('pnpm'); + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); + } + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`); + + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/nextjs.ts b/apps/server/src/lib/buildPacks/nextjs.ts new file mode 100644 index 000000000..957dc5bce --- /dev/null +++ b/apps/server/src/lib/buildPacks/nextjs.ts @@ -0,0 +1,66 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildCacheImageWithNode, buildImage, checkPnpm } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + applicationId, + buildId, + tag, + workdir, + publishDirectory, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId, + deploymentType, + baseImage + } = data; + const Dockerfile: Array = []; + const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); + } + if (deploymentType === 'node') { + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN ${installCommand}`); + Dockerfile.push(`RUN ${buildCommand}`); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + } else if (deploymentType === 'static') { + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + } + + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage, deploymentType, buildCommand } = data; + if (deploymentType === 'node') { + await createDockerfile(data, baseImage); + await buildImage(data); + } else if (deploymentType === 'static') { + if (buildCommand) await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/node.ts b/apps/server/src/lib/buildPacks/node.ts new file mode 100644 index 000000000..8ccfcc68e --- /dev/null +++ b/apps/server/src/lib/buildPacks/node.ts @@ -0,0 +1,49 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildImage, checkPnpm } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + workdir, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId, + buildId + } = data; + const Dockerfile: Array = []; + const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); + } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN ${installCommand}`); + if (buildCommand) { + Dockerfile.push(`RUN ${buildCommand}`); + } + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage } = data; + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/nuxtjs.ts b/apps/server/src/lib/buildPacks/nuxtjs.ts new file mode 100644 index 000000000..957dc5bce --- /dev/null +++ b/apps/server/src/lib/buildPacks/nuxtjs.ts @@ -0,0 +1,66 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildCacheImageWithNode, buildImage, checkPnpm } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + applicationId, + buildId, + tag, + workdir, + publishDirectory, + port, + installCommand, + buildCommand, + startCommand, + baseDirectory, + secrets, + pullmergeRequestId, + deploymentType, + baseImage + } = data; + const Dockerfile: Array = []; + const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (isPnpm) { + Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@7'); + } + if (deploymentType === 'node') { + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`RUN ${installCommand}`); + Dockerfile.push(`RUN ${buildCommand}`); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ${startCommand}`); + } else if (deploymentType === 'static') { + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + Dockerfile.push(`EXPOSE 80`); + } + + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage, deploymentType, buildCommand } = data; + if (deploymentType === 'node') { + await createDockerfile(data, baseImage); + await buildImage(data); + } else if (deploymentType === 'static') { + if (buildCommand) await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/php.ts b/apps/server/src/lib/buildPacks/php.ts new file mode 100644 index 000000000..abfd7af4f --- /dev/null +++ b/apps/server/src/lib/buildPacks/php.ts @@ -0,0 +1,50 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildImage } from './common'; + +const createDockerfile = async (data, image, htaccessFound): Promise => { + const { workdir, baseDirectory, buildId, port, secrets, pullmergeRequestId } = data; + const Dockerfile: Array = []; + let composerFound = false; + try { + await fs.readFile(`${workdir}${baseDirectory || ''}/composer.json`); + composerFound = true; + } catch (error) {} + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`COPY .${baseDirectory || ''} /app`); + if (htaccessFound) { + Dockerfile.push(`COPY .${baseDirectory || ''}/.htaccess ./`); + } + if (composerFound) { + Dockerfile.push(`RUN composer install`); + } + + Dockerfile.push(`COPY /entrypoint.sh /opt/docker/provision/entrypoint.d/30-entrypoint.sh`); + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + const { workdir, baseDirectory, baseImage } = data; + try { + let htaccessFound = false; + try { + await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`); + htaccessFound = true; + } catch (e) { + // + } + await createDockerfile(data, baseImage, htaccessFound); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/python.ts b/apps/server/src/lib/buildPacks/python.ts new file mode 100644 index 000000000..56294660f --- /dev/null +++ b/apps/server/src/lib/buildPacks/python.ts @@ -0,0 +1,67 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + workdir, + port, + baseDirectory, + secrets, + pullmergeRequestId, + pythonWSGI, + pythonModule, + pythonVariable, + buildId + } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (pythonWSGI?.toLowerCase() === 'gunicorn') { + Dockerfile.push(`RUN pip install gunicorn`); + } else if (pythonWSGI?.toLowerCase() === 'uvicorn') { + Dockerfile.push(`RUN pip install uvicorn`); + } else if (pythonWSGI?.toLowerCase() === 'uwsgi') { + Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`); + // Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`) + } + + try { + await fs.stat(`${workdir}${baseDirectory || ''}/requirements.txt`); + Dockerfile.push(`COPY .${baseDirectory || ''}/requirements.txt ./`); + Dockerfile.push(`RUN pip install --no-cache-dir -r .${baseDirectory || ''}/requirements.txt`); + } catch (e) { + // + } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`EXPOSE ${port}`); + if (pythonWSGI?.toLowerCase() === 'gunicorn') { + Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`); + } else if (pythonWSGI?.toLowerCase() === 'uvicorn') { + Dockerfile.push(`CMD uvicorn ${pythonModule}:${pythonVariable} --port ${port} --host 0.0.0.0`); + } else if (pythonWSGI?.toLowerCase() === 'uwsgi') { + Dockerfile.push( + `CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}` + ); + } else { + Dockerfile.push(`CMD python ${pythonModule}`); + } + + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/react.ts b/apps/server/src/lib/buildPacks/react.ts new file mode 100644 index 000000000..e85704d3f --- /dev/null +++ b/apps/server/src/lib/buildPacks/react.ts @@ -0,0 +1,28 @@ +import { promises as fs } from 'fs'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/rust.ts b/apps/server/src/lib/buildPacks/rust.ts new file mode 100644 index 000000000..f3c9ef918 --- /dev/null +++ b/apps/server/src/lib/buildPacks/rust.ts @@ -0,0 +1,40 @@ +import { promises as fs } from 'fs'; +import TOML from '@iarna/toml'; +import { buildCacheImageWithCargo, buildImage } from './common'; +import { executeCommand } from '../executeCommand'; + +const createDockerfile = async (data, image, name): Promise => { + const { workdir, port, applicationId, tag, buildId } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); + Dockerfile.push(`COPY . .`); + Dockerfile.push(`RUN cargo build --release --bin ${name}`); + Dockerfile.push('FROM debian:buster-slim'); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push( + `RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*` + ); + Dockerfile.push(`RUN update-ca-certificates`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`); + Dockerfile.push(`EXPOSE ${port}`); + Dockerfile.push(`CMD ["/app/${name}"]`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { workdir, baseImage, baseBuildImage } = data; + const { stdout: cargoToml } = await executeCommand({ command: `cat ${workdir}/Cargo.toml` }); + const parsedToml: any = TOML.parse(cargoToml); + const name = parsedToml.package.name; + await buildCacheImageWithCargo(data, baseBuildImage); + await createDockerfile(data, baseImage, name); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/static.ts b/apps/server/src/lib/buildPacks/static.ts new file mode 100644 index 000000000..19b13cef3 --- /dev/null +++ b/apps/server/src/lib/buildPacks/static.ts @@ -0,0 +1,54 @@ +import { promises as fs } from 'fs'; +import { generateSecrets } from '../common'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { + applicationId, + tag, + workdir, + buildCommand, + baseDirectory, + publishDirectory, + secrets, + pullmergeRequestId, + baseImage, + buildId, + port + } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + if (baseImage?.includes('httpd')) { + Dockerfile.push('WORKDIR /usr/local/apache2/htdocs/'); + } else { + Dockerfile.push('WORKDIR /app'); + } + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + if (secrets.length > 0) { + generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => { + Dockerfile.push(env); + }); + } + if (buildCommand) { + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + } else { + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + } + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + if (data.buildCommand) await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/svelte.ts b/apps/server/src/lib/buildPacks/svelte.ts new file mode 100644 index 000000000..56fc12d7a --- /dev/null +++ b/apps/server/src/lib/buildPacks/svelte.ts @@ -0,0 +1,28 @@ +import { promises as fs } from 'fs'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/buildPacks/vuejs.ts b/apps/server/src/lib/buildPacks/vuejs.ts new file mode 100644 index 000000000..56fc12d7a --- /dev/null +++ b/apps/server/src/lib/buildPacks/vuejs.ts @@ -0,0 +1,28 @@ +import { promises as fs } from 'fs'; +import { buildCacheImageWithNode, buildImage } from './common'; + +const createDockerfile = async (data, image): Promise => { + const { applicationId, tag, workdir, publishDirectory, baseImage, buildId, port } = data; + const Dockerfile: Array = []; + + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.buildId=${buildId}`); + Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); + if (baseImage?.includes('nginx')) { + Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); + } + Dockerfile.push(`EXPOSE ${port}`); + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const { baseImage, baseBuildImage } = data; + await buildCacheImageWithNode(data, baseBuildImage); + await createDockerfile(data, baseImage); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/apps/server/src/lib/common.ts b/apps/server/src/lib/common.ts index 5b200a5b2..b415790aa 100644 --- a/apps/server/src/lib/common.ts +++ b/apps/server/src/lib/common.ts @@ -535,3 +535,134 @@ export async function cleanupDB(buildId: string, applicationId: string) { } await saveBuildLog({ line: 'Canceled.', buildId, applicationId }); } + +export const base64Encode = (text: string): string => { + return Buffer.from(text).toString('base64'); +}; +export const base64Decode = (text: string): string => { + return Buffer.from(text, 'base64').toString('ascii'); +}; +function parseSecret(secret, isBuild) { + if (secret.value.includes('$')) { + secret.value = secret.value.replaceAll('$', '$$$$'); + } + if (secret.value.includes('\\n')) { + if (isBuild) { + return `ARG ${secret.name}=${secret.value}`; + } else { + return `${secret.name}=${secret.value}`; + } + } else if (secret.value.includes(' ')) { + if (isBuild) { + return `ARG ${secret.name}='${secret.value}'`; + } else { + return `${secret.name}='${secret.value}'`; + } + } else { + if (isBuild) { + return `ARG ${secret.name}=${secret.value}`; + } else { + return `${secret.name}=${secret.value}`; + } + } +} +export function generateSecrets( + secrets: Array, + pullmergeRequestId: string, + isBuild = false, + port = null +): Array { + const envs = []; + const isPRMRSecret = secrets.filter((s) => s.isPRMRSecret); + const normalSecrets = secrets.filter((s) => !s.isPRMRSecret); + if (pullmergeRequestId && isPRMRSecret.length > 0) { + isPRMRSecret.forEach((secret) => { + if (isBuild && !secret.isBuildSecret) { + return; + } + const build = isBuild && secret.isBuildSecret; + envs.push(parseSecret(secret, build)); + }); + } + if (!pullmergeRequestId && normalSecrets.length > 0) { + normalSecrets.forEach((secret) => { + if (isBuild && !secret.isBuildSecret) { + return; + } + const build = isBuild && secret.isBuildSecret; + envs.push(parseSecret(secret, build)); + }); + } + const portFound = envs.filter((env) => env.startsWith('PORT')); + if (portFound.length === 0 && port && !isBuild) { + envs.push(`PORT=${port}`); + } + const nodeEnv = envs.filter((env) => env.startsWith('NODE_ENV')); + if (nodeEnv.length === 0 && !isBuild) { + envs.push(`NODE_ENV=production`); + } + return envs; +} +export function decryptApplication(application: any) { + if (application) { + if (application?.gitSource?.githubApp?.clientSecret) { + application.gitSource.githubApp.clientSecret = + decrypt(application.gitSource.githubApp.clientSecret) || null; + } + if (application?.gitSource?.githubApp?.webhookSecret) { + application.gitSource.githubApp.webhookSecret = + decrypt(application.gitSource.githubApp.webhookSecret) || null; + } + if (application?.gitSource?.githubApp?.privateKey) { + application.gitSource.githubApp.privateKey = + decrypt(application.gitSource.githubApp.privateKey) || null; + } + if (application?.gitSource?.gitlabApp?.appSecret) { + application.gitSource.gitlabApp.appSecret = + decrypt(application.gitSource.gitlabApp.appSecret) || null; + } + if (application?.secrets.length > 0) { + application.secrets = application.secrets.map((s: any) => { + s.value = decrypt(s.value) || null; + return s; + }); + } + + return application; + } +} +export async function pushToRegistry( + application: any, + workdir: string, + tag: string, + imageName: string, + customTag: string +) { + const location = `${workdir}/.docker`; + const tagCommand = `docker tag ${application.id}:${tag} ${imageName}:${customTag}`; + const pushCommand = `docker --config ${location} push ${imageName}:${customTag}`; + await executeCommand({ + dockerId: application.destinationDockerId, + command: tagCommand + }); + await executeCommand({ + dockerId: application.destinationDockerId, + command: pushCommand + }); +} + +export async function getContainerUsage(dockerId: string, container: string): Promise { + try { + const { stdout } = await executeCommand({ + dockerId, + command: `docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"` + }); + return JSON.parse(stdout); + } catch (err) { + return { + MemUsage: 0, + CPUPerc: 0, + NetIO: 0 + }; + } +} \ No newline at end of file diff --git a/apps/server/src/lib/importers/github.ts b/apps/server/src/lib/importers/github.ts new file mode 100644 index 000000000..dfaa4c724 --- /dev/null +++ b/apps/server/src/lib/importers/github.ts @@ -0,0 +1,96 @@ + +import jsonwebtoken from 'jsonwebtoken'; +import { prisma } from '../../prisma'; +import { saveBuildLog } from '../buildPacks/common'; +import { decrypt } from '../common'; +import { executeCommand } from '../executeCommand'; + +export default async function ({ + applicationId, + workdir, + githubAppId, + repository, + apiUrl, + gitCommitHash, + htmlUrl, + branch, + buildId, + customPort, + forPublic +}: { + applicationId: string; + workdir: string; + githubAppId: string; + repository: string; + apiUrl: string; + gitCommitHash?: string; + htmlUrl: string; + branch: string; + buildId: string; + customPort: number; + forPublic?: boolean; +}): Promise { + const { default: got } = await import('got') + const url = htmlUrl.replace('https://', '').replace('http://', ''); + if (forPublic) { + await saveBuildLog({ + line: `Cloning ${repository}:${branch}...`, + buildId, + applicationId + }); + if (gitCommitHash) { + await saveBuildLog({ + line: `Checking out ${gitCommitHash} commit...`, + buildId, + applicationId + }); + } + await executeCommand({ + command: + `git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, + shell: true + }); + + } else { + const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } }); + if (body.privateKey) body.privateKey = decrypt(body.privateKey); + const { privateKey, appId, installationId } = body + const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, ''); + + const payload = { + iat: Math.round(new Date().getTime() / 1000), + exp: Math.round(new Date().getTime() / 1000 + 60), + iss: appId + }; + const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, { + algorithm: 'RS256' + }); + const { token } = await got + .post(`${apiUrl}/app/installations/${installationId}/access_tokens`, { + headers: { + Authorization: `Bearer ${jwtToken}`, + Accept: 'application/vnd.github.machine-man-preview+json' + } + }) + .json(); + await saveBuildLog({ + line: `Cloning ${repository}:${branch}...`, + buildId, + applicationId + }); + if (gitCommitHash) { + await saveBuildLog({ + line: `Checking out ${gitCommitHash} commit...`, + buildId, + applicationId + }); + } + await executeCommand({ + command: + `git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, + shell: true + }); + } + const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true }); + return commit.replace('\n', ''); +} diff --git a/apps/server/src/lib/importers/gitlab.ts b/apps/server/src/lib/importers/gitlab.ts new file mode 100644 index 000000000..4f49fbd0e --- /dev/null +++ b/apps/server/src/lib/importers/gitlab.ts @@ -0,0 +1,65 @@ +import { saveBuildLog } from "../buildPacks/common"; +import { executeCommand } from "../executeCommand"; + +export default async function ({ + applicationId, + workdir, + repodir, + htmlUrl, + gitCommitHash, + repository, + branch, + buildId, + privateSshKey, + customPort, + forPublic, + customUser, +}: { + applicationId: string; + workdir: string; + repository: string; + htmlUrl: string; + branch: string; + buildId: string; + repodir: string; + gitCommitHash: string; + privateSshKey: string; + customPort: number; + forPublic: boolean; + customUser: string; +}): Promise { + const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, ''); + if (!forPublic) { + await executeCommand({ command: `echo '${privateSshKey}' > ${repodir}/id.rsa`, shell: true }); + await executeCommand({ command: `chmod 600 ${repodir}/id.rsa` }); + } + + await saveBuildLog({ + line: `Cloning ${repository}:${branch}...`, + buildId, + applicationId + }); + if (gitCommitHash) { + await saveBuildLog({ + line: `Checking out ${gitCommitHash} commit...`, + buildId, + applicationId + }); + } + if (forPublic) { + await executeCommand({ + command: + `git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true + } + ); + } else { + await executeCommand({ + command: + `git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true + } + ); + } + + const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true }); + return commit.replace('\n', ''); +} diff --git a/apps/server/src/lib/importers/index.ts b/apps/server/src/lib/importers/index.ts new file mode 100644 index 000000000..193443890 --- /dev/null +++ b/apps/server/src/lib/importers/index.ts @@ -0,0 +1,4 @@ +import github from './github'; +import gitlab from './gitlab'; + +export { github, gitlab }; diff --git a/apps/server/src/scheduler.ts b/apps/server/src/scheduler.ts index 38067e196..3ec5c7990 100644 --- a/apps/server/src/scheduler.ts +++ b/apps/server/src/scheduler.ts @@ -1,6 +1,6 @@ import Bree from 'bree'; import path from 'path'; -// import Cabin from 'cabin'; +import Cabin from 'cabin'; import TSBree from '@breejs/ts-worker'; export const isDev = process.env['NODE_ENV'] === 'development'; @@ -9,16 +9,8 @@ Bree.extend(TSBree); const options: any = { defaultExtension: 'js', - logger: false, - // logger: false, - // workerMessageHandler: async ({ name, message }) => { - // if (name === 'deployApplication' && message?.deploying) { - // if (scheduler.workers.has('autoUpdater') || scheduler.workers.has('cleanupStorage')) { - // scheduler.workers.get('deployApplication').postMessage('cancel') - // } - // } - // }, - jobs: [{ name: 'deployApplication' }, { name: 'worker' }] + logger: new Cabin({}), + jobs: [{ name: 'applicationBuildQueue' }] }; if (isDev) options.root = path.join(__dirname, './jobs'); diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index e9dd64876..6b2d9d87d 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -64,7 +64,11 @@ export function createServer(opts: ServerOptions) { console.log('Coolify server is listening on port', port, 'at 0.0.0.0 🚀'); const graceful = new Graceful({ brees: [scheduler] }); graceful.listen(); - scheduler.run('worker'); + setInterval(async () => { + if (!scheduler.workers.has('applicationBuildQueue')) { + scheduler.run('applicationBuildQueue'); + } + }, 2000); } catch (err) { server.log.error(err); process.exit(1); diff --git a/apps/server/src/trpc/routers/applications/index.ts b/apps/server/src/trpc/routers/applications/index.ts index 0b51690f5..bfa71d848 100644 --- a/apps/server/src/trpc/routers/applications/index.ts +++ b/apps/server/src/trpc/routers/applications/index.ts @@ -24,6 +24,8 @@ import { createDirectories, decrypt, encrypt, + generateSecrets, + getContainerUsage, getDomain, isDev, isDomainConfigured, @@ -32,8 +34,345 @@ import { } from '../../../lib/common'; import { day } from '../../../lib/dayjs'; import csv from 'csvtojson'; +import { scheduler } from '../../../scheduler'; export const applicationsRouter = router({ + deleteApplication: privateProcedure + .input( + z.object({ + id: z.string(), + force: z.boolean().default(false) + }) + ) + .mutation(async ({ input, ctx }) => { + const { id, force } = input; + const teamId = ctx.user.teamId; + const application = await prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (!force && application?.destinationDockerId && application.destinationDocker?.network) { + const { stdout: containers } = await executeCommand({ + dockerId: application.destinationDocker.id, + command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'` + }); + if (containers) { + const containersArray = containers.trim().split('\n'); + for (const container of containersArray) { + const containerObj = JSON.parse(container); + const id = containerObj.ID; + await removeContainer({ id, dockerId: application.destinationDocker.id }); + } + } + } + await prisma.applicationSettings.deleteMany({ where: { application: { id } } }); + await prisma.buildLog.deleteMany({ where: { applicationId: id } }); + await prisma.build.deleteMany({ where: { applicationId: id } }); + await prisma.secret.deleteMany({ where: { applicationId: id } }); + await prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id } }); + await prisma.applicationConnectedDatabase.deleteMany({ where: { applicationId: id } }); + if (teamId === '0') { + await prisma.application.deleteMany({ where: { id } }); + } else { + await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } }); + } + return {} + }), + restartPreview: privateProcedure + .input( + z.object({ + id: z.string(), + pullmergeRequestId: z.string() + }) + ) + .mutation(async ({ input, ctx }) => { + const { id, pullmergeRequestId } = input; + const teamId = ctx.user.teamId; + let application: any = await getApplicationFromDB(id, teamId); + if (application?.destinationDockerId) { + const buildId = cuid(); + const { id: dockerId, network } = application.destinationDocker; + const { + secrets, + port, + repository, + persistentStorage, + id: applicationId, + buildPack, + exposePort + } = application; + + let envs = []; + if (secrets.length > 0) { + envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, port)]; + } + const { workdir } = await createDirectories({ repository, buildId }); + const labels = []; + let image = null; + const { stdout: container } = await executeCommand({ + dockerId, + command: `docker container ls --filter 'label=com.docker.compose.service=${id}-${pullmergeRequestId}' --format '{{json .}}'` + }); + const containersArray = container.trim().split('\n'); + for (const container of containersArray) { + const containerObj = formatLabelsOnDocker(container); + image = containerObj[0].Image; + Object.keys(containerObj[0].Labels).forEach(function (key) { + if (key.startsWith('coolify')) { + labels.push(`${key}=${containerObj[0].Labels[key]}`); + } + }); + } + let imageFound = false; + try { + await executeCommand({ + dockerId, + command: `docker image inspect ${image}` + }); + imageFound = true; + } catch (error) { + // + } + if (!imageFound) { + throw { status: 500, message: 'Image not found, cannot restart application.' }; + } + + const volumes = + persistentStorage?.map((storage) => { + return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ + buildPack !== 'docker' ? '/app' : '' + }${storage.path}`; + }) || []; + const composeVolumes = volumes.map((volume) => { + return { + [`${volume.split(':')[0]}`]: { + name: volume.split(':')[0] + } + }; + }); + const composeFile = { + version: '3.8', + services: { + [`${applicationId}-${pullmergeRequestId}`]: { + image, + container_name: `${applicationId}-${pullmergeRequestId}`, + volumes, + environment: envs, + labels, + depends_on: [], + expose: [port], + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + ...defaultComposeConfiguration(network) + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: Object.assign({}, ...composeVolumes) + }; + await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); + await executeCommand({ dockerId, command: `docker stop -t 0 ${id}-${pullmergeRequestId}` }); + await executeCommand({ dockerId, command: `docker rm ${id}-${pullmergeRequestId}` }); + await executeCommand({ + dockerId, + command: `docker compose --project-directory ${workdir} up -d` + }); + } + }), + getPreviewStatus: privateProcedure + .input( + z.object({ + id: z.string(), + pullmergeRequestId: z.string() + }) + ) + .query(async ({ input, ctx }) => { + const { id, pullmergeRequestId } = input; + const teamId = ctx.user.teamId; + let isRunning = false; + let isExited = false; + let isRestarting = false; + let isBuilding = false; + const application: any = await getApplicationFromDB(id, teamId); + if (application?.destinationDockerId) { + const status = await checkContainer({ + dockerId: application.destinationDocker.id, + container: `${id}-${pullmergeRequestId}` + }); + if (status?.found) { + isRunning = status.status.isRunning; + isExited = status.status.isExited; + isRestarting = status.status.isRestarting; + } + const building = await prisma.build.findMany({ + where: { applicationId: id, pullmergeRequestId, status: { in: ['queued', 'running'] } } + }); + isBuilding = building.length > 0; + } + return { + success: true, + data: { + isBuilding, + isRunning, + isRestarting, + isExited + } + }; + }), + loadPreviews: privateProcedure + .input( + z.object({ + id: z.string() + }) + ) + .mutation(async ({ input, ctx }) => { + const { id } = input; + const application = await prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + const { stdout } = await executeCommand({ + dockerId: application.destinationDocker.id, + command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` + }); + if (stdout === '') { + throw { status: 500, message: 'No previews found.' }; + } + const containers = formatLabelsOnDocker(stdout).filter( + (container) => + container.Labels['coolify.configuration'] && + container.Labels['coolify.type'] === 'standalone-application' + ); + + const jsonContainers = containers + .map((container) => + JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString()) + ) + .filter((container) => { + return container.pullmergeRequestId && container.applicationId === id; + }); + for (const container of jsonContainers) { + const found = await prisma.previewApplication.findMany({ + where: { + applicationId: container.applicationId, + pullmergeRequestId: container.pullmergeRequestId + } + }); + if (found.length === 0) { + await prisma.previewApplication.create({ + data: { + pullmergeRequestId: container.pullmergeRequestId, + sourceBranch: container.branch, + customDomain: container.fqdn, + application: { connect: { id: container.applicationId } } + } + }); + } + } + return { + success: true, + data: { + previews: await prisma.previewApplication.findMany({ where: { applicationId: id } }) + } + }; + }), + stopPreview: privateProcedure + .input( + z.object({ + id: z.string(), + pullmergeRequestId: z.string() + }) + ) + .mutation(async ({ input, ctx }) => { + const { id, pullmergeRequestId } = input; + const teamId = ctx.user.teamId; + const application: any = await getApplicationFromDB(id, teamId); + if (application?.destinationDockerId) { + const container = `${id}-${pullmergeRequestId}`; + const { id: dockerId } = application.destinationDocker; + const { found } = await checkContainer({ dockerId, container }); + if (found) { + await removeContainer({ id: container, dockerId: application.destinationDocker.id }); + } + await prisma.previewApplication.deleteMany({ + where: { applicationId: application.id, pullmergeRequestId } + }); + } + return {}; + }), + getUsage: privateProcedure + .input( + z.object({ + id: z.string(), + containerId: z.string() + }) + ) + .query(async ({ input, ctx }) => { + const { id, containerId } = input; + const teamId = ctx.user.teamId; + let usage = {}; + + const application: any = await getApplicationFromDB(id, teamId); + if (application.destinationDockerId) { + [usage] = await Promise.all([ + getContainerUsage(application.destinationDocker.id, containerId) + ]); + } + return { + success: true, + data: { + usage + } + }; + }), + getLocalImages: privateProcedure + .input( + z.object({ + id: z.string() + }) + ) + .query(async ({ input, ctx }) => { + const { id } = input; + const teamId = ctx.user.teamId; + const application: any = await getApplicationFromDB(id, teamId); + let imagesAvailables = []; + const { stdout } = await executeCommand({ + dockerId: application.destinationDocker.id, + command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'` + }); + const { stdout: runningImage } = await executeCommand({ + dockerId: application.destinationDocker.id, + command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` + }); + const images = stdout + .trim() + .split('\n') + .filter((image) => image.includes(id) && !image.includes('-cache')); + for (const image of images) { + const [repository, tag, createdAt] = image.split('#'); + if (tag.includes('-')) { + continue; + } + const [year, time] = createdAt.split(' '); + imagesAvailables.push({ + repository, + tag, + createdAt: day(year + time).unix() + }); + } + + imagesAvailables = imagesAvailables.sort((a, b) => b.tag - a.tag); + + return { + success: true, + data: { + imagesAvailables, + runningImage + } + }; + }), resetQueue: privateProcedure.mutation(async ({ ctx }) => { const teamId = ctx.user.teamId; if (teamId === '0') { @@ -41,7 +380,7 @@ export const applicationsRouter = router({ where: { status: { in: ['queued', 'running'] } }, data: { status: 'canceled' } }); - // scheduler.workers.get("deployApplication").postMessage("cancel"); + // scheduler.workers.get("").postMessage("cancel"); } }), cancelBuild: privateProcedure @@ -813,170 +1152,183 @@ export const applicationsRouter = router({ } return {}; }), - restart: privateProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => { - const { id } = input; - const teamId = ctx.user?.teamId; - let application = await getApplicationFromDB(id, teamId); - if (application?.destinationDockerId) { - const buildId = cuid(); - const { id: dockerId, network } = application.destinationDocker; - const { - dockerRegistry, - secrets, - pullmergeRequestId, - port, - repository, - persistentStorage, - id: applicationId, - buildPack, - exposePort - } = application; - let location = null; - const labels = []; - let image = null; - const envs = [`PORT=${port}`, 'NODE_ENV=production']; + restart: privateProcedure + .input(z.object({ id: z.string(), imageId: z.string().nullable() })) + .mutation(async ({ ctx, input }) => { + const { id, imageId } = input; + const teamId = ctx.user?.teamId; + let application = await getApplicationFromDB(id, teamId); + if (application?.destinationDockerId) { + const buildId = cuid(); + const { id: dockerId, network } = application.destinationDocker; + const { + dockerRegistry, + secrets, + pullmergeRequestId, + port, + repository, + persistentStorage, + id: applicationId, + buildPack, + exposePort + } = application; + let location = null; + const labels = []; + let image = null; + const envs = [`PORT=${port}`, 'NODE_ENV=production']; - if (secrets.length > 0) { - secrets.forEach((secret) => { - if (pullmergeRequestId) { - const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret); - if (isSecretFound.length > 0) { - if (isSecretFound[0].value.includes('\\n') || isSecretFound[0].value.includes("'")) { - envs.push(`${secret.name}=${isSecretFound[0].value}`); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (pullmergeRequestId) { + const isSecretFound = secrets.filter((s) => s.name === secret.name && s.isPRMRSecret); + if (isSecretFound.length > 0) { + if ( + isSecretFound[0].value.includes('\\n') || + isSecretFound[0].value.includes("'") + ) { + envs.push(`${secret.name}=${isSecretFound[0].value}`); + } else { + envs.push(`${secret.name}='${isSecretFound[0].value}'`); + } } else { - envs.push(`${secret.name}='${isSecretFound[0].value}'`); + if (secret.value.includes('\\n') || secret.value.includes("'")) { + envs.push(`${secret.name}=${secret.value}`); + } else { + envs.push(`${secret.name}='${secret.value}'`); + } } } else { - if (secret.value.includes('\\n') || secret.value.includes("'")) { - envs.push(`${secret.name}=${secret.value}`); - } else { - envs.push(`${secret.name}='${secret.value}'`); + if (!secret.isPRMRSecret) { + if (secret.value.includes('\\n') || secret.value.includes("'")) { + envs.push(`${secret.name}=${secret.value}`); + } else { + envs.push(`${secret.name}='${secret.value}'`); + } } } - } else { - if (!secret.isPRMRSecret) { - if (secret.value.includes('\\n') || secret.value.includes("'")) { - envs.push(`${secret.name}=${secret.value}`); - } else { - envs.push(`${secret.name}='${secret.value}'`); + }); + } + const { workdir } = await createDirectories({ repository, buildId }); + + if (imageId) { + image = imageId; + } else { + const { stdout: container } = await executeCommand({ + dockerId, + command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` + }); + const containersArray = container.trim().split('\n'); + for (const container of containersArray) { + const containerObj = formatLabelsOnDocker(container); + image = containerObj[0].Image; + Object.keys(containerObj[0].Labels).forEach(function (key) { + if (key.startsWith('coolify')) { + labels.push(`${key}=${containerObj[0].Labels[key]}`); } + }); + } + } + + if (dockerRegistry) { + const { url, username, password } = dockerRegistry; + location = await saveDockerRegistryCredentials({ url, username, password, workdir }); + } + + let imageFoundLocally = false; + try { + await executeCommand({ + dockerId, + command: `docker image inspect ${image}` + }); + imageFoundLocally = true; + } catch (error) { + // + } + let imageFoundRemotely = false; + try { + await executeCommand({ + dockerId, + command: `docker ${location ? `--config ${location}` : ''} pull ${image}` + }); + imageFoundRemotely = true; + } catch (error) { + // + } + + if (!imageFoundLocally && !imageFoundRemotely) { + throw { status: 500, message: 'Image not found, cannot restart application.' }; + } + await fs.writeFile(`${workdir}/.env`, envs.join('\n')); + + let envFound = false; + try { + envFound = !!(await fs.stat(`${workdir}/.env`)); + } catch (error) { + // + } + const volumes = + persistentStorage?.map((storage) => { + return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ + buildPack !== 'docker' ? '/app' : '' + }${storage.path}`; + }) || []; + const composeVolumes = volumes.map((volume) => { + return { + [`${volume.split(':')[0]}`]: { + name: volume.split(':')[0] } - } + }; }); - } - const { workdir } = await createDirectories({ repository, buildId }); - - const { stdout: container } = await executeCommand({ - dockerId, - command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` - }); - const containersArray = container.trim().split('\n'); - for (const container of containersArray) { - const containerObj = formatLabelsOnDocker(container); - image = containerObj[0].Image; - Object.keys(containerObj[0].Labels).forEach(function (key) { - if (key.startsWith('coolify')) { - labels.push(`${key}=${containerObj[0].Labels[key]}`); - } - }); - } - if (dockerRegistry) { - const { url, username, password } = dockerRegistry; - location = await saveDockerRegistryCredentials({ url, username, password, workdir }); - } - - let imageFoundLocally = false; - try { - await executeCommand({ - dockerId, - command: `docker image inspect ${image}` - }); - imageFoundLocally = true; - } catch (error) { - // - } - let imageFoundRemotely = false; - try { - await executeCommand({ - dockerId, - command: `docker ${location ? `--config ${location}` : ''} pull ${image}` - }); - imageFoundRemotely = true; - } catch (error) { - // - } - - if (!imageFoundLocally && !imageFoundRemotely) { - throw { status: 500, message: 'Image not found, cannot restart application.' }; - } - await fs.writeFile(`${workdir}/.env`, envs.join('\n')); - - let envFound = false; - try { - envFound = !!(await fs.stat(`${workdir}/.env`)); - } catch (error) { - // - } - const volumes = - persistentStorage?.map((storage) => { - return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ - buildPack !== 'docker' ? '/app' : '' - }${storage.path}`; - }) || []; - const composeVolumes = volumes.map((volume) => { - return { - [`${volume.split(':')[0]}`]: { - name: volume.split(':')[0] - } + const composeFile = { + version: '3.8', + services: { + [applicationId]: { + image, + container_name: applicationId, + volumes, + env_file: envFound ? [`${workdir}/.env`] : [], + labels, + depends_on: [], + expose: [port], + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + ...defaultComposeConfiguration(network) + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: Object.assign({}, ...composeVolumes) }; - }); - const composeFile = { - version: '3.8', - services: { - [applicationId]: { - image, - container_name: applicationId, - volumes, - env_file: envFound ? [`${workdir}/.env`] : [], - labels, - depends_on: [], - expose: [port], - ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), - ...defaultComposeConfiguration(network) - } - }, - networks: { - [network]: { - external: true - } - }, - volumes: Object.assign({}, ...composeVolumes) - }; - await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); - try { - await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` }); - await executeCommand({ dockerId, command: `docker rm ${id}` }); - } catch (error) { - // - } + await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); + try { + await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` }); + await executeCommand({ dockerId, command: `docker rm ${id}` }); + } catch (error) { + // + } - await executeCommand({ - dockerId, - command: `docker compose --project-directory ${workdir} up -d` - }); - } - return {}; - }), + await executeCommand({ + dockerId, + command: `docker compose --project-directory ${workdir} up -d` + }); + } + return {}; + }), deploy: privateProcedure .input( z.object({ - id: z.string() + id: z.string(), + forceRebuild: z.boolean().default(false), + pullmergeRequestId: z.string().nullable(), + branch: z.string().nullable() }) ) .mutation(async ({ ctx, input }) => { - const { id } = input; + const { id, pullmergeRequestId, branch, forceRebuild } = input; const teamId = ctx.user?.teamId; - const buildId = await deployApplication(id, teamId); + const buildId = await deployApplication(id, teamId, forceRebuild, pullmergeRequestId, branch); return { buildId }; diff --git a/apps/server/src/trpc/routers/applications/lib.ts b/apps/server/src/trpc/routers/applications/lib.ts index dfedc1bc1..ee4b9a809 100644 --- a/apps/server/src/trpc/routers/applications/lib.ts +++ b/apps/server/src/trpc/routers/applications/lib.ts @@ -7,7 +7,9 @@ import { prisma } from '../../../prisma'; export async function deployApplication( id: string, teamId: string, - forceRebuild: boolean = false + forceRebuild: boolean, + pullmergeRequestId: string | null = null, + branch: string | null = null ): Promise { const buildId = cuid(); const application = await getApplicationFromDB(id, teamId); @@ -29,14 +31,20 @@ export async function deployApplication( data: { id: buildId, applicationId: id, + sourceBranch: branch, branch: application.branch, + pullmergeRequestId: pullmergeRequestId?.toString(), forceRebuild, destinationDockerId: application.destinationDocker?.id, gitSourceId: application.gitSource?.id, githubAppId: application.gitSource?.githubApp?.id, gitlabAppId: application.gitSource?.gitlabApp?.id, status: 'queued', - type: 'manual' + type: pullmergeRequestId + ? application.gitSource?.githubApp?.id + ? 'manual_pr' + : 'manual_mr' + : 'manual' } }); } else { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af24eef4f..e19fab50f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,6 +245,7 @@ importers: '@fastify/jwt': 6.5.0 '@fastify/static': 6.6.0 '@fastify/websocket': 7.1.1 + '@iarna/toml': 2.2.5 '@ladjs/graceful': 3.0.2 '@prisma/client': 4.6.1 '@trpc/client': 10.1.0 @@ -301,6 +302,7 @@ importers: '@fastify/jwt': 6.5.0 '@fastify/static': 6.6.0 '@fastify/websocket': 7.1.1 + '@iarna/toml': 2.2.5 '@ladjs/graceful': 3.0.2 '@prisma/client': 4.6.1_prisma@4.6.1 '@trpc/client': 10.1.0_@trpc+server@10.1.0 @@ -1453,11 +1455,10 @@ packages: engines: {node: '>= 12.11'} peerDependencies: bree: '>=9.0.0' - tsconfig-paths: '>= 4' dependencies: bree: 9.1.2 ts-node: 10.8.2_wup25etrarvlqkprac7h35hj7u - tsconfig-paths: 4.1.0 + tsconfig-paths: 4.1.2 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -6305,12 +6306,6 @@ packages: hasBin: true dev: true - /json5/2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} - engines: {node: '>=6'} - hasBin: true - dev: false - /json5/2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -8771,15 +8766,6 @@ packages: yn: 3.1.1 dev: false - /tsconfig-paths/4.1.0: - resolution: {integrity: sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==} - engines: {node: '>=6'} - dependencies: - json5: 2.2.1 - minimist: 1.2.7 - strip-bom: 3.0.0 - dev: false - /tsconfig-paths/4.1.2: resolution: {integrity: sha512-uhxiMgnXQp1IR622dUXI+9Ehnws7i/y6xvpZB9IbUVOPy0muvdvgXeZOn88UcGPiT98Vp3rJPTa8bFoalZ3Qhw==} engines: {node: '>=6'} From c651570e6216873789f34abfe434a808d52268a8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 12 Jan 2023 16:50:17 +0100 Subject: [PATCH 04/20] wip: trpc --- apps/server/src/jobs/applicationBuildQueue.ts | 3 +-- apps/server/src/trpc/routers/applications/index.ts | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/server/src/jobs/applicationBuildQueue.ts b/apps/server/src/jobs/applicationBuildQueue.ts index fd7bf076a..92ddc3b84 100644 --- a/apps/server/src/jobs/applicationBuildQueue.ts +++ b/apps/server/src/jobs/applicationBuildQueue.ts @@ -572,8 +572,7 @@ import { defaultComposeConfiguration } from '../lib/docker'; deploymentType, forceRebuild }); - } - if (buildpacks[buildPack]) + } else if (buildpacks[buildPack]) await buildpacks[buildPack]({ dockerId: destinationDocker.id, network: destinationDocker.network, diff --git a/apps/server/src/trpc/routers/applications/index.ts b/apps/server/src/trpc/routers/applications/index.ts index bfa71d848..939fceefe 100644 --- a/apps/server/src/trpc/routers/applications/index.ts +++ b/apps/server/src/trpc/routers/applications/index.ts @@ -76,7 +76,7 @@ export const applicationsRouter = router({ } else { await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } }); } - return {} + return {}; }), restartPreview: privateProcedure .input( @@ -101,7 +101,7 @@ export const applicationsRouter = router({ buildPack, exposePort } = application; - + let envs = []; if (secrets.length > 0) { envs = [...envs, ...generateSecrets(secrets, pullmergeRequestId, false, port)]; @@ -136,7 +136,7 @@ export const applicationsRouter = router({ if (!imageFound) { throw { status: 500, message: 'Image not found, cannot restart application.' }; } - + const volumes = persistentStorage?.map((storage) => { return `${applicationId}${storage.path.replace(/\//gi, '-')}:${ @@ -1321,8 +1321,8 @@ export const applicationsRouter = router({ z.object({ id: z.string(), forceRebuild: z.boolean().default(false), - pullmergeRequestId: z.string().nullable(), - branch: z.string().nullable() + pullmergeRequestId: z.string().nullable().optional(), + branch: z.string().nullable().optional() }) ) .mutation(async ({ ctx, input }) => { From 5a745efcd3b32af1720157d3ba5d3df7d2cf20cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Talha=20Zekeriya=20Durmu=C5=9F?= Date: Fri, 13 Jan 2023 02:38:33 +0100 Subject: [PATCH 05/20] Add Matermost Logo --- apps/ui/static/icons/mattermost.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/ui/static/icons/mattermost.svg diff --git a/apps/ui/static/icons/mattermost.svg b/apps/ui/static/icons/mattermost.svg new file mode 100644 index 000000000..b6c526ac6 --- /dev/null +++ b/apps/ui/static/icons/mattermost.svg @@ -0,0 +1 @@ + \ No newline at end of file From 568ab24fd9a4909af788bbddadb1752dff64a391 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 13 Jan 2023 14:17:36 +0100 Subject: [PATCH 06/20] wip: trpc --- apps/client/src/lib/components/DocLink.svelte | 44 + .../src/lib/components/ExternalLink.svelte | 10 + .../icons/services/ServiceIcons.svelte | 1 + apps/client/src/lib/store.ts | 8 + .../src/routes/services/[id]/+layout.svelte | 366 ++ .../src/routes/services/[id]/+layout.ts | 46 + .../src/routes/services/[id]/+page.svelte | 562 +++ .../services/[id]/components/Menu.svelte | 138 + .../[id]/components/ServiceLinks.svelte | 14 + .../[id]/components/ServiceStatus.svelte | 37 + .../services/[id]/components/Wordpress.svelte | 85 + .../routes/services/[id]/danger/+page.svelte | 46 + .../routes/services/[id]/logs/+page.svelte | 173 + .../routes/services/[id]/secrets/+page.svelte | 98 + .../src/routes/services/[id]/secrets/+page.ts | 16 + .../[id]/secrets/components/Secret.svelte | 101 + .../src/routes/services/[id]/secrets/utils.ts | 78 + .../services/[id]/storages/+page.svelte | 73 + .../routes/services/[id]/storages/+page.ts | 16 + .../[id]/storages/components/Storage.svelte | 167 + apps/client/src/routes/services/[id]/utils.ts | 79 + apps/server/devTags.json | 1013 +++++ apps/server/devTemplates.yaml | 3582 +++++++++++++++++ apps/server/src/lib/common.ts | 44 +- apps/server/src/scheduler.ts | 2 +- apps/server/src/trpc/routers/services.ts | 171 - .../server/src/trpc/routers/services/index.ts | 895 ++++ apps/server/src/trpc/routers/services/lib.ts | 376 ++ apps/server/tags.json | 1013 +++++ apps/server/templates.json | 1 + 30 files changed, 9082 insertions(+), 173 deletions(-) create mode 100644 apps/client/src/lib/components/DocLink.svelte create mode 100644 apps/client/src/lib/components/ExternalLink.svelte create mode 100644 apps/client/src/routes/services/[id]/+layout.svelte create mode 100644 apps/client/src/routes/services/[id]/+layout.ts create mode 100644 apps/client/src/routes/services/[id]/+page.svelte create mode 100644 apps/client/src/routes/services/[id]/components/Menu.svelte create mode 100644 apps/client/src/routes/services/[id]/components/ServiceLinks.svelte create mode 100644 apps/client/src/routes/services/[id]/components/ServiceStatus.svelte create mode 100644 apps/client/src/routes/services/[id]/components/Wordpress.svelte create mode 100644 apps/client/src/routes/services/[id]/danger/+page.svelte create mode 100644 apps/client/src/routes/services/[id]/logs/+page.svelte create mode 100644 apps/client/src/routes/services/[id]/secrets/+page.svelte create mode 100644 apps/client/src/routes/services/[id]/secrets/+page.ts create mode 100644 apps/client/src/routes/services/[id]/secrets/components/Secret.svelte create mode 100644 apps/client/src/routes/services/[id]/secrets/utils.ts create mode 100644 apps/client/src/routes/services/[id]/storages/+page.svelte create mode 100644 apps/client/src/routes/services/[id]/storages/+page.ts create mode 100644 apps/client/src/routes/services/[id]/storages/components/Storage.svelte create mode 100644 apps/client/src/routes/services/[id]/utils.ts create mode 100644 apps/server/devTags.json create mode 100644 apps/server/devTemplates.yaml delete mode 100644 apps/server/src/trpc/routers/services.ts create mode 100644 apps/server/src/trpc/routers/services/index.ts create mode 100644 apps/server/src/trpc/routers/services/lib.ts create mode 100644 apps/server/tags.json create mode 100644 apps/server/templates.json diff --git a/apps/client/src/lib/components/DocLink.svelte b/apps/client/src/lib/components/DocLink.svelte new file mode 100644 index 000000000..803b48583 --- /dev/null +++ b/apps/client/src/lib/components/DocLink.svelte @@ -0,0 +1,44 @@ + + + + + + + {text} + {#if isExternal} + + {/if} + +{#if !text} + See details in the documentation +{/if} diff --git a/apps/client/src/lib/components/ExternalLink.svelte b/apps/client/src/lib/components/ExternalLink.svelte new file mode 100644 index 000000000..62f2e312a --- /dev/null +++ b/apps/client/src/lib/components/ExternalLink.svelte @@ -0,0 +1,10 @@ + + + diff --git a/apps/client/src/lib/components/icons/services/ServiceIcons.svelte b/apps/client/src/lib/components/icons/services/ServiceIcons.svelte index 7f146e3fb..7f3ead42e 100644 --- a/apps/client/src/lib/components/icons/services/ServiceIcons.svelte +++ b/apps/client/src/lib/components/icons/services/ServiceIcons.svelte @@ -5,6 +5,7 @@ const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback); let extension = 'png'; let svgs = [ + 'directus', 'pocketbase', 'gitea', 'languagetool', diff --git a/apps/client/src/lib/store.ts b/apps/client/src/lib/store.ts index 87afa0034..dc45778b2 100644 --- a/apps/client/src/lib/store.ts +++ b/apps/client/src/lib/store.ts @@ -171,3 +171,11 @@ export const setLocation = (resource: any, settings?: any) => { } }; export const selectedBuildId: any = writable(null) +export function checkIfDeploymentEnabledServices( service: any) { + return ( + service.fqdn && + service.destinationDocker && + service.version && + service.type + ); +} \ No newline at end of file diff --git a/apps/client/src/routes/services/[id]/+layout.svelte b/apps/client/src/routes/services/[id]/+layout.svelte new file mode 100644 index 000000000..f218a664c --- /dev/null +++ b/apps/client/src/routes/services/[id]/+layout.svelte @@ -0,0 +1,366 @@ + + +
+ +
+ {#if $status.service.initialLoading} + + {:else if $status.service.overallStatus === 'healthy'} + + + {:else if $status.service.overallStatus === 'degraded'} + + {:else if $status.service.overallStatus === 'stopped'} + {#if $status.service.overallStatus === 'degraded'} + + {:else if $status.service.overallStatus === 'stopped'} + + {/if} + {/if} +
+
+ +
+ {#if !$page.url.pathname.startsWith(`/services/${id}/configuration/`)} + + {/if} +
+ +
+
diff --git a/apps/client/src/routes/services/[id]/+layout.ts b/apps/client/src/routes/services/[id]/+layout.ts new file mode 100644 index 000000000..9027326c5 --- /dev/null +++ b/apps/client/src/routes/services/[id]/+layout.ts @@ -0,0 +1,46 @@ +import { error } from '@sveltejs/kit'; +import { trpc } from '$lib/store'; +import type { LayoutLoad } from './$types'; +import { redirect } from '@sveltejs/kit'; + +function checkConfiguration(service: any): string | null { + let configurationPhase = null; + if (!service.type) { + configurationPhase = 'type'; + } else if (!service.destinationDockerId) { + configurationPhase = 'destination'; + } + return configurationPhase; +} + +export const load: LayoutLoad = async ({ params, url }) => { + const { pathname } = new URL(url); + const { id } = params; + try { + const service = await trpc.services.getServices.query({ id }); + if (!service) { + throw redirect(307, '/services'); + } + const configurationPhase = checkConfiguration(service); + console.log({ configurationPhase }); + // if ( + // configurationPhase && + // pathname !== `/applications/${params.id}/configuration/${configurationPhase}` + // ) { + // throw redirect(302, `/applications/${params.id}/configuration/${configurationPhase}`); + // } + return { + ...service.data + }; + } catch (err) { + if (err instanceof Error) { + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + '

' + err.message + }); + } + + throw error(500, { + message: 'An unexpected error occurred, please try again later.' + }); + } +}; diff --git a/apps/client/src/routes/services/[id]/+page.svelte b/apps/client/src/routes/services/[id]/+page.svelte new file mode 100644 index 000000000..2dd38eadb --- /dev/null +++ b/apps/client/src/routes/services/[id]/+page.svelte @@ -0,0 +1,562 @@ + + +
+
+
+
+
General
+ {#if $appSession.isAdmin} + + {/if} + {#if service.type === 'plausibleanalytics' && $status.service.overallStatus === 'healthy'} + + + {/if} + {#if service.type === 'appwrite' && $status.service.overallStatus === 'healthy'} + +
+ +
+ {/if} +
+
+ +
+
+ + +
+
+ + {#if tags.tags?.length > 0} +
+ + {/if} +
+ +
+ +
+ {#if service.destinationDockerId} +
+ +
+ {/if} +
+
+ +
+ + +
+ {#each Object.keys(template) as oneService} + {#each template[oneService].fqdns as fqdn} +
+ + +
+ {/each} + {/each} +
+ {#if forceSave} +
+ {#if isNonWWWDomainOK} + + {:else} + + {/if} + {#if dualCerts} + {#if isWWWDomainOK} + + {:else} + + {/if} + {/if} +
+ {/if} + +
+
+ You need to have both DNS entries set in advance.

Service needs to be restarted."} + on:click={() => !$status.service.isRunning && changeSettings('dualCerts')} + /> +
+ {#if hostPorts.length === 0} +
+ + +
+ {/if} +
+
+ {#each Object.keys(template) as oneService} +
0 && + template[oneService].environment.find((env) => env.main === oneService)} + class:border-b={template[oneService].environment.length > 0 && + template[oneService].environment.find((env) => env.main === oneService)} + class:border-coolgray-500={template[oneService].environment.length > 0 && + template[oneService].environment.find((env) => env.main === oneService)} + > +
+ {template[oneService].name || + oneService.replace(`${id}-`, '').replace(id, service.type)} +
+ +
+
+ {#if template[oneService].environment.length > 0} + {#each template[oneService].environment as variable} + {#if variable.main === oneService} +
+ + {#if variable.defaultValue === '$$generate_fqdn'} + + {:else if variable.defaultValue === '$$generate_fqdn_slash'} + + {:else if variable.defaultValue === '$$generate_domain'} + + {:else if variable.defaultValue === '$$generate_network'} + + {:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'} + {#if variable.value === 'true' || variable.value === 'false' || variable.value === 'invite_only'} + + {:else} + + {/if} + {:else if variable.defaultValue === '$$generate_password'} + + {:else if variable.type === 'textarea'} +