From f1313b646837bc5549792dac28065b89e853580f Mon Sep 17 00:00:00 2001 From: dominicbachmann Date: Tue, 5 Apr 2022 01:13:25 +0200 Subject: [PATCH 01/24] Implemented typing for dockerfile configuration --- src/lib/types/composeFile.ts | 49 +++++++++++++++++++ src/routes/databases/[id]/start.json.ts | 3 +- src/routes/services/[id]/ghost/start.json.ts | 3 +- .../services/[id]/languagetool/start.json.ts | 3 +- .../services/[id]/meilisearch/start.json.ts | 3 +- src/routes/services/[id]/minio/start.json.ts | 3 +- src/routes/services/[id]/n8n/start.json.ts | 3 +- src/routes/services/[id]/nocodb/start.json.ts | 3 +- .../[id]/plausibleanalytics/start.json.ts | 3 +- .../services/[id]/uptimekuma/start.json.ts | 3 +- .../services/[id]/vaultwarden/start.json.ts | 3 +- .../services/[id]/vscodeserver/start.json.ts | 3 +- .../services/[id]/wordpress/start.json.ts | 3 +- 13 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 src/lib/types/composeFile.ts diff --git a/src/lib/types/composeFile.ts b/src/lib/types/composeFile.ts new file mode 100644 index 000000000..b7e88210f --- /dev/null +++ b/src/lib/types/composeFile.ts @@ -0,0 +1,49 @@ +import { makeLabelForServices } from '../buildPacks/common'; + +export type ComposeFile = { + version: ComposerFileVersion; + services: Record; + networks: Record; + volumes?: Record; +}; + +export type ComposeFileService = { + container_name: string; + image?: string; + networks: string[]; + environment: Record; + volumes?: string[]; + ulimits?: unknown; + labels?: string[]; + restart: ComposeFileRestartOption; + depends_on?: string[]; + command?: string; + build?: string; +}; + +export type ComposerFileVersion = + | '3.8' + | '3.7' + | '3.6' + | '3.5' + | '3.4' + | '3.3' + | '3.2' + | '3.1' + | '3.0' + | '2.4' + | '2.3' + | '2.2' + | '2.1' + | '2.0'; + +export type ComposeFileRestartOption = 'no' | 'always' | 'on-failure' | 'unless-stopped'; + +export type ComposeFileNetwork = { + external: boolean; +}; + +export type ComposeFileVolume = { + external?: boolean; + name?: string; +}; diff --git a/src/routes/databases/[id]/start.json.ts b/src/routes/databases/[id]/start.json.ts index bca2995d4..59fd7c1ea 100644 --- a/src/routes/databases/[id]/start.json.ts +++ b/src/routes/databases/[id]/start.json.ts @@ -6,6 +6,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { makeLabelForStandaloneDatabase } from '$lib/buildPacks/common'; import { startTcpProxy } from '$lib/haproxy'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -33,7 +34,7 @@ export const post: RequestHandler = async (event) => { const { workdir } = await createDirectories({ repository: type, buildId: id }); - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/ghost/start.json.ts b/src/routes/services/[id]/ghost/start.json.ts index 4fd9e7fa4..9e0b4ba11 100644 --- a/src/routes/services/[id]/ghost/start.json.ts +++ b/src/routes/services/[id]/ghost/start.json.ts @@ -11,6 +11,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -75,7 +76,7 @@ export const post: RequestHandler = async (event) => { config.ghost.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/languagetool/start.json.ts b/src/routes/services/[id]/languagetool/start.json.ts index a6a81d475..ac0f6a5bb 100644 --- a/src/routes/services/[id]/languagetool/start.json.ts +++ b/src/routes/services/[id]/languagetool/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -32,7 +33,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/meilisearch/start.json.ts b/src/routes/services/[id]/meilisearch/start.json.ts index 646a8b1ba..5a5a9d5c0 100644 --- a/src/routes/services/[id]/meilisearch/start.json.ts +++ b/src/routes/services/[id]/meilisearch/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -37,7 +38,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/minio/start.json.ts b/src/routes/services/[id]/minio/start.json.ts index bcffd9e8f..6e14a42ca 100644 --- a/src/routes/services/[id]/minio/start.json.ts +++ b/src/routes/services/[id]/minio/start.json.ts @@ -8,6 +8,7 @@ import getPort, { portNumbers } from 'get-port'; import { getDomain } from '$lib/components/common'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -55,7 +56,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/n8n/start.json.ts b/src/routes/services/[id]/n8n/start.json.ts index e5ad7e930..3df1855d0 100644 --- a/src/routes/services/[id]/n8n/start.json.ts +++ b/src/routes/services/[id]/n8n/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -33,7 +34,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/nocodb/start.json.ts b/src/routes/services/[id]/nocodb/start.json.ts index ebc59ce97..1642038b3 100644 --- a/src/routes/services/[id]/nocodb/start.json.ts +++ b/src/routes/services/[id]/nocodb/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -30,7 +31,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/plausibleanalytics/start.json.ts b/src/routes/services/[id]/plausibleanalytics/start.json.ts index 836bd0178..6ca37c5e8 100644 --- a/src/routes/services/[id]/plausibleanalytics/start.json.ts +++ b/src/routes/services/[id]/plausibleanalytics/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -120,7 +121,7 @@ COPY ./init.query /docker-entrypoint-initdb.d/init.query COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`; await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile); - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/uptimekuma/start.json.ts b/src/routes/services/[id]/uptimekuma/start.json.ts index 845873bb0..7a72326e1 100644 --- a/src/routes/services/[id]/uptimekuma/start.json.ts +++ b/src/routes/services/[id]/uptimekuma/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -31,7 +32,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/vaultwarden/start.json.ts b/src/routes/services/[id]/vaultwarden/start.json.ts index f977adda3..ae13d9015 100644 --- a/src/routes/services/[id]/vaultwarden/start.json.ts +++ b/src/routes/services/[id]/vaultwarden/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { getServiceImage, ErrorHandler } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -32,7 +33,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/vscodeserver/start.json.ts b/src/routes/services/[id]/vscodeserver/start.json.ts index 6f51fcf2a..dbe0c120c 100644 --- a/src/routes/services/[id]/vscodeserver/start.json.ts +++ b/src/routes/services/[id]/vscodeserver/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -41,7 +42,7 @@ export const post: RequestHandler = async (event) => { config.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { diff --git a/src/routes/services/[id]/wordpress/start.json.ts b/src/routes/services/[id]/wordpress/start.json.ts index 98e7d6aab..c0a28e0bd 100644 --- a/src/routes/services/[id]/wordpress/start.json.ts +++ b/src/routes/services/[id]/wordpress/start.json.ts @@ -5,6 +5,7 @@ import yaml from 'js-yaml'; import type { RequestHandler } from '@sveltejs/kit'; import { ErrorHandler, getServiceImage } from '$lib/database'; import { makeLabelForServices } from '$lib/buildPacks/common'; +import type { ComposeFile } from '$lib/types/composeFile'; export const post: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -65,7 +66,7 @@ export const post: RequestHandler = async (event) => { config.wordpress.environmentVariables[secret.name] = secret.value; }); } - const composeFile = { + const composeFile: ComposeFile = { version: '3.8', services: { [id]: { From e1efd9355ff051b840a0d1eebe3b2e39740fb780 Mon Sep 17 00:00:00 2001 From: Dhaval Soneji Date: Tue, 5 Apr 2022 14:03:36 +0100 Subject: [PATCH 02/24] Add plausible/analytics:stable docker tag --- src/lib/database/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 3e473f094..75366dbc9 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -110,7 +110,7 @@ export const supportedServiceTypesAndVersions = [ fancyName: 'Plausible Analytics', baseImage: 'plausible/analytics', images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'], - versions: ['latest'], + versions: ['latest', 'stable'], ports: { main: 8000 } From 8e9e6607e5c67c4785fa18a473c1ad8606990e23 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 15:56:25 +0200 Subject: [PATCH 03/24] feat: Wordpress on-demand SFTP --- prisma/schema.prisma | 6 + src/lib/components/Setting.svelte | 5 +- src/lib/database/services.ts | 6 +- src/lib/haproxy/index.ts | 6 +- .../services/[id]/_Services/_Services.svelte | 16 +- .../services/[id]/_Services/_Wordpress.svelte | 69 +++++++++ src/routes/services/[id]/__layout.svelte | 5 +- src/routes/services/[id]/index.json.ts | 5 +- src/routes/services/[id]/index.svelte | 6 +- .../services/[id]/wordpress/settings.json.ts | 138 ++++++++++++++++++ 10 files changed, 238 insertions(+), 24 deletions(-) create mode 100644 src/routes/services/[id]/wordpress/settings.json.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 986a773bf..d5e322337 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -332,6 +332,12 @@ model Wordpress { mysqlRootUserPassword String mysqlDatabase String? mysqlPublicPort Int? + ftpEnabled Boolean @default(false) + ftpUser String? + ftpPassword String? + ftpPublicPort Int? + ftpHostKey String? + ftpHostKeyPrivate String? serviceId String @unique service Service @relation(fields: [serviceId], references: [id]) createdAt DateTime @default(now()) diff --git a/src/lib/components/Setting.svelte b/src/lib/components/Setting.svelte index c8764bed7..7d87dd6fb 100644 --- a/src/lib/components/Setting.svelte +++ b/src/lib/components/Setting.svelte @@ -7,6 +7,7 @@ export let isCenter = true; export let disabled = false; export let dataTooltip = null; + export let loading = false;
@@ -26,7 +27,7 @@ on:click aria-pressed="false" class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out" - class:opacity-50={disabled} + class:opacity-50={disabled || loading} class:bg-green-600={setting} class:bg-stone-700={!setting} > @@ -40,6 +41,7 @@ class=" absolute inset-0 flex h-full w-full items-center justify-center transition-opacity duration-200 ease-in" class:opacity-0={setting} class:opacity-100={!setting} + class:animate-spin={loading} aria-hidden="true" > @@ -57,6 +59,7 @@ aria-hidden="true" class:opacity-100={setting} class:opacity-0={!setting} + class:animate-spin={loading} > {:else if service.type === 'wordpress'} - + {:else if service.type === 'ghost'} {:else if service.type === 'meilisearch'} @@ -151,17 +152,4 @@ {/if}
- diff --git a/src/routes/services/[id]/_Services/_Wordpress.svelte b/src/routes/services/[id]/_Services/_Wordpress.svelte index 883178a22..dc7eb4ebb 100644 --- a/src/routes/services/[id]/_Services/_Wordpress.svelte +++ b/src/routes/services/[id]/_Services/_Wordpress.svelte @@ -1,9 +1,55 @@
@@ -28,6 +74,29 @@ define('SUBDOMAIN_INSTALL', false);` : 'N/A'}>{service.wordpress.extraConfig}
+
+ changeSettings('ftpEnabled')} + title="Enable sFTP connection to WordPress data" + description="Enables an on-demand sFTP connection to the WordPress data directory. This is useful if you want to use sFTP to upload files." + /> +
+{#if service.wordpress.ftpEnabled} +
+ + +
+
+ + +
+
+ + +
+{/if}
MySQL
diff --git a/src/routes/services/[id]/__layout.svelte b/src/routes/services/[id]/__layout.svelte index 362f89786..37158d711 100644 --- a/src/routes/services/[id]/__layout.svelte +++ b/src/routes/services/[id]/__layout.svelte @@ -16,7 +16,7 @@ const endpoint = `/services/${params.id}.json`; const res = await fetch(endpoint); if (res.ok) { - const { service, isRunning } = await res.json(); + const { service, isRunning, settings } = await res.json(); if (!service || Object.entries(service).length === 0) { return { status: 302, @@ -45,7 +45,8 @@ stuff: { service, isRunning, - readOnly + readOnly, + settings } }; } diff --git a/src/routes/services/[id]/index.json.ts b/src/routes/services/[id]/index.json.ts index 676ff6405..f75ff87f6 100644 --- a/src/routes/services/[id]/index.json.ts +++ b/src/routes/services/[id]/index.json.ts @@ -17,7 +17,7 @@ export const get: RequestHandler = async (event) => { const { id } = event.params; try { const service = await db.getService({ id, teamId }); - const { destinationDockerId, destinationDocker, type, version } = service; + const { destinationDockerId, destinationDocker, type, version, settings } = service; let isRunning = false; if (destinationDockerId) { @@ -46,7 +46,8 @@ export const get: RequestHandler = async (event) => { return { body: { isRunning, - service + service, + settings } }; } catch (error) { diff --git a/src/routes/services/[id]/index.svelte b/src/routes/services/[id]/index.svelte index 9f64e039b..a16591266 100644 --- a/src/routes/services/[id]/index.svelte +++ b/src/routes/services/[id]/index.svelte @@ -6,7 +6,8 @@ props: { service: stuff.service, isRunning: stuff.isRunning, - readOnly: stuff.readOnly + readOnly: stuff.readOnly, + settings: stuff.settings } }; } @@ -37,6 +38,7 @@ export let service; export let isRunning; export let readOnly; + export let settings; if (browser && window.location.hostname === 'demo.coolify.io' && !service.fqdn) { service.fqdn = `http://${cuid()}.demo.coolify.io`; @@ -76,4 +78,4 @@ - + diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts new file mode 100644 index 000000000..728d4919f --- /dev/null +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -0,0 +1,138 @@ +import { dev } from '$app/env'; +import { asyncExecShell, getEngine, getUserDetails } from '$lib/common'; +import { decrypt, encrypt } from '$lib/crypto'; +import * as db from '$lib/database'; +import { generateDatabaseConfiguration, ErrorHandler, generatePassword } from '$lib/database'; +import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; +import type { RequestHandler } from '@sveltejs/kit'; +import cuid from 'cuid'; +import fs from 'fs/promises'; +import getPort, { portNumbers } from 'get-port'; + +export const post: RequestHandler = async (event) => { + const { status, body, teamId } = await getUserDetails(event); + if (status === 401) return { status, body }; + + const { id } = event.params; + const data = await db.prisma.setting.findFirst(); + const { minPort, maxPort } = data; + + const { ftpEnabled } = await event.request.json(); + const publicPort = await getPort({ port: portNumbers(minPort, maxPort) }); + let ftpUser = cuid(); + const ftpPassword = generatePassword(); + + const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; + try { + const { stdout: password } = await asyncExecShell( + `echo ${ftpPassword} | openssl passwd -1 -stdin` + ); + const data = await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpEnabled }, + include: { service: { include: { destinationDocker: true } } } + }); + const { + service: { destinationDockerId, destinationDocker }, + ftpPublicPort: oldPublicPort, + ftpUser: user, + ftpHostKey, + ftpHostKeyPrivate + } = data; + if (user) ftpUser = user; + try { + await fs.stat(hostkeyDir); + } catch (error) { + await asyncExecShell(`mkdir -p ${hostkeyDir}`); + } + if (!ftpHostKey) { + await asyncExecShell( + `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f /tmp/${id} < /dev/null` + ); + const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey.replace('\n', '')) } + }); + } else { + await asyncExecShell(`echo ${decrypt(ftpHostKey)} > ${hostkeyDir}/${id}.ed25519`); + } + if (!ftpHostKeyPrivate) { + await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f /tmp/${id}.rsa < /dev/null`); + const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat /tmp/${id}.rsa`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate.replace('\n', '')) } + }); + } else { + await asyncExecShell(`echo ${decrypt(ftpHostKeyPrivate)} > ${hostkeyDir}/${id}.rsa`); + } + if (destinationDockerId) { + const { network, engine } = destinationDocker; + const host = getEngine(engine); + if (ftpEnabled) { + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: publicPort, ftpUser, ftpPassword: encrypt(ftpPassword) } + }); + + try { + const isRunning = await checkContainer(engine, `${id}-ftp`); + if (isRunning) { + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); + } + } catch (error) { + console.log(error); + // + } + + await asyncExecShell( + `DOCKER_HOST=${host} docker run --restart always --add-host 'host.docker.internal:host-gateway' --network ${network} --name ${id}-ftp -v ${id}-wordpress-data:/home/${ftpUser} -v ${hostkeyDir}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key -v ${hostkeyDir}/${id}.rsa:/etc/ssh/ssh_host_rsa_key -d atmoz/sftp '${ftpUser}:${password.replace( + '\n', + '' + )}:e:1001'` + ); + + await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); + } else { + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: null } + }); + try { + const isRunning = await checkContainer(engine, `${id}-ftp`); + if (isRunning) { + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); + } + } catch (error) { + console.log(error); + // + } + + await stopTcpHttpProxy(destinationDocker, oldPublicPort); + } + } + if (ftpEnabled) { + return { + status: 201, + body: { + publicPort, + ftpUser, + ftpPassword + } + }; + } else { + return { + status: 200, + body: {} + }; + } + } catch (error) { + console.log(error); + return ErrorHandler(error); + } +}; From fe9d0503fb328aa3cbf359f75b6794d7eba85d36 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 17:15:06 +0200 Subject: [PATCH 04/24] feat: Finalize on-demand sftp for wp --- .../migration.sql | 29 ++++++++++ .../services/[id]/_Services/_Wordpress.svelte | 46 ++++++++-------- .../services/[id]/wordpress/settings.json.ts | 54 +++++++++---------- 3 files changed, 81 insertions(+), 48 deletions(-) create mode 100644 prisma/migrations/20220405151428_wordpress_sftp/migration.sql diff --git a/prisma/migrations/20220405151428_wordpress_sftp/migration.sql b/prisma/migrations/20220405151428_wordpress_sftp/migration.sql new file mode 100644 index 000000000..6c3c4b907 --- /dev/null +++ b/prisma/migrations/20220405151428_wordpress_sftp/migration.sql @@ -0,0 +1,29 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Wordpress" ( + "id" TEXT NOT NULL PRIMARY KEY, + "extraConfig" TEXT, + "tablePrefix" TEXT, + "mysqlUser" TEXT NOT NULL, + "mysqlPassword" TEXT NOT NULL, + "mysqlRootUser" TEXT NOT NULL, + "mysqlRootUserPassword" TEXT NOT NULL, + "mysqlDatabase" TEXT, + "mysqlPublicPort" INTEGER, + "ftpEnabled" BOOLEAN NOT NULL DEFAULT false, + "ftpUser" TEXT, + "ftpPassword" TEXT, + "ftpPublicPort" INTEGER, + "ftpHostKey" TEXT, + "ftpHostKeyPrivate" TEXT, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Wordpress_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_Wordpress" ("createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt") SELECT "createdAt", "extraConfig", "id", "mysqlDatabase", "mysqlPassword", "mysqlPublicPort", "mysqlRootUser", "mysqlRootUserPassword", "mysqlUser", "serviceId", "tablePrefix", "updatedAt" FROM "Wordpress"; +DROP TABLE "Wordpress"; +ALTER TABLE "new_Wordpress" RENAME TO "Wordpress"; +CREATE UNIQUE INDEX "Wordpress_serviceId_key" ON "Wordpress"("serviceId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/src/routes/services/[id]/_Services/_Wordpress.svelte b/src/routes/services/[id]/_Services/_Wordpress.svelte index dc7eb4ebb..3ea53edd3 100644 --- a/src/routes/services/[id]/_Services/_Wordpress.svelte +++ b/src/routes/services/[id]/_Services/_Wordpress.svelte @@ -26,28 +26,31 @@ : 'Loading...'; } async function changeSettings(name) { - ftpLoading = true; - let ftpEnabled = service.wordpress.ftpEnabled; + if (ftpLoading) return; + if (isRunning) { + ftpLoading = true; + let ftpEnabled = service.wordpress.ftpEnabled; - if (name === 'ftpEnabled') { - ftpEnabled = !ftpEnabled; - } - try { - const { - publicPort, - ftpUser: user, - ftpPassword: password - } = await post(`/services/${id}/wordpress/settings.json`, { - ftpEnabled - }); - ftpUrl = generateUrl(publicPort); - ftpUser = user; - ftpPassword = password; - service.wordpress.ftpEnabled = ftpEnabled; - } catch ({ error }) { - return errorNotification(error); - } finally { - ftpLoading = false; + if (name === 'ftpEnabled') { + ftpEnabled = !ftpEnabled; + } + try { + const { + publicPort, + ftpUser: user, + ftpPassword: password + } = await post(`/services/${id}/wordpress/settings.json`, { + ftpEnabled + }); + ftpUrl = generateUrl(publicPort); + ftpUser = user; + ftpPassword = password; + service.wordpress.ftpEnabled = ftpEnabled; + } catch ({ error }) { + return errorNotification(error); + } finally { + ftpLoading = false; + } } } @@ -78,6 +81,7 @@ define('SUBDOMAIN_INSTALL', false);` changeSettings('ftpEnabled')} title="Enable sFTP connection to WordPress data" description="Enables an on-demand sFTP connection to the WordPress data directory. This is useful if you want to use sFTP to upload files." diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 728d4919f..6d6d730ae 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -40,34 +40,34 @@ export const post: RequestHandler = async (event) => { ftpHostKeyPrivate } = data; if (user) ftpUser = user; - try { - await fs.stat(hostkeyDir); - } catch (error) { - await asyncExecShell(`mkdir -p ${hostkeyDir}`); - } - if (!ftpHostKey) { - await asyncExecShell( - `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f /tmp/${id} < /dev/null` - ); - const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); - await db.prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKey: encrypt(ftpHostKey.replace('\n', '')) } - }); - } else { - await asyncExecShell(`echo ${decrypt(ftpHostKey)} > ${hostkeyDir}/${id}.ed25519`); - } - if (!ftpHostKeyPrivate) { - await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f /tmp/${id}.rsa < /dev/null`); - const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat /tmp/${id}.rsa`); - await db.prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate.replace('\n', '')) } - }); - } else { - await asyncExecShell(`echo ${decrypt(ftpHostKeyPrivate)} > ${hostkeyDir}/${id}.rsa`); - } if (destinationDockerId) { + try { + await fs.stat(hostkeyDir); + } catch (error) { + await asyncExecShell(`mkdir -p ${hostkeyDir}`); + } + if (!ftpHostKey) { + await asyncExecShell( + `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` + ); + const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`); + } + if (!ftpHostKeyPrivate) { + await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`); + const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`); + await db.prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } + }); + } else { + await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); + } const { network, engine } = destinationDocker; const host = getEngine(engine); if (ftpEnabled) { From d2dcd0abc8400cc0d75546039caa2f38963f48e8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 17:15:21 +0200 Subject: [PATCH 05/24] chore:version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2aa11b114..ac2396dab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.3.3", + "version": "2.4.0", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && cross-env NODE_ENV=development & svelte-kit dev", From 09841ad4cb3e909a10c8ad15b4204fd0a3f3005e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 17:21:40 +0200 Subject: [PATCH 06/24] fix: Add openssl to image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9494ff702..df4614a13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /app LABEL coolify.managed true -RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite +RUN apk add --no-cache git git-lfs openssh-client curl jq cmake sqlite openssl RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 RUN pnpm add -g pnpm From 4ba2205af4dbfac0dfb6ab215a44b897ae75a120 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 17:37:03 +0200 Subject: [PATCH 07/24] fix: Permission issues --- src/lib/components/Setting.svelte | 5 +++-- src/routes/services/[id]/wordpress/settings.json.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/components/Setting.svelte b/src/lib/components/Setting.svelte index 7d87dd6fb..d7b028861 100644 --- a/src/lib/components/Setting.svelte +++ b/src/lib/components/Setting.svelte @@ -28,8 +28,9 @@ aria-pressed="false" class="relative mx-20 inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out" class:opacity-50={disabled || loading} - class:bg-green-600={setting} - class:bg-stone-700={!setting} + class:bg-green-600={!loading && setting} + class:bg-stone-700={!loading && !setting} + class:bg-yellow-500={loading} > Use setting { } else { await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); } + await asyncExecShell(`chmod -R 600 ${hostkeyDir}`); const { network, engine } = destinationDocker; const host = getEngine(engine); if (ftpEnabled) { From 3a835b420ed96e686150872d1e1b009c7f31af73 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 5 Apr 2022 23:44:18 +0200 Subject: [PATCH 08/24] WIP --- .../services/[id]/wordpress/settings.json.ts | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 13d6685bf..86810ca25 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -8,6 +8,7 @@ import type { RequestHandler } from '@sveltejs/kit'; import cuid from 'cuid'; import fs from 'fs/promises'; import getPort, { portNumbers } from 'get-port'; +import yaml from 'js-yaml'; export const post: RequestHandler = async (event) => { const { status, body, teamId } = await getUserDetails(event); @@ -68,7 +69,6 @@ export const post: RequestHandler = async (event) => { } else { await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); } - await asyncExecShell(`chmod -R 600 ${hostkeyDir}`); const { network, engine } = destinationDocker; const host = getEngine(engine); if (ftpEnabled) { @@ -88,12 +88,40 @@ export const post: RequestHandler = async (event) => { console.log(error); // } - + const volumes = [ + `${id}-wordpress-data:/home/${ftpUser}`, + `${hostkeyDir}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, + `${hostkeyDir}/${id}.rsa:/etc/ssh/ssh_host_rsa_key` + ]; + const compose = { + version: '3.8', + services: { + [`${id}-ftp`]: { + image: `atmoz/sftp:alpine`, + command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:1001'`, + extra_hosts: ['host.docker.internal:host-gateway'], + container_name: `${id}-ftp`, + volumes, + networks: [network], + depends_on: [], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [`${id}-wordpress-data`]: { + external: true, + name: `${id}-wordpress-data` + } + } + }; + await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); await asyncExecShell( - `DOCKER_HOST=${host} docker run --restart always --add-host 'host.docker.internal:host-gateway' --network ${network} --name ${id}-ftp -v ${id}-wordpress-data:/home/${ftpUser} -v ${hostkeyDir}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key -v ${hostkeyDir}/${id}.rsa:/etc/ssh/ssh_host_rsa_key -d atmoz/sftp '${ftpUser}:${password.replace( - '\n', - '' - )}:e:1001'` + `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` ); await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); From 39fa64e20d1d9b9ec13f4acbad053b3b646a532f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 10:29:42 +0200 Subject: [PATCH 09/24] fix: On-demand sFTP for wp --- .../services/[id]/wordpress/settings.json.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 86810ca25..3356be86b 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -21,13 +21,10 @@ export const post: RequestHandler = async (event) => { const { ftpEnabled } = await event.request.json(); const publicPort = await getPort({ port: portNumbers(minPort, maxPort) }); let ftpUser = cuid(); - const ftpPassword = generatePassword(); + let ftpPassword = generatePassword(); const hostkeyDir = dev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; try { - const { stdout: password } = await asyncExecShell( - `echo ${ftpPassword} | openssl passwd -1 -stdin` - ); const data = await db.prisma.wordpress.update({ where: { serviceId: id }, data: { ftpEnabled }, @@ -37,10 +34,16 @@ export const post: RequestHandler = async (event) => { service: { destinationDockerId, destinationDocker }, ftpPublicPort: oldPublicPort, ftpUser: user, + ftpPassword: savedPassword, ftpHostKey, ftpHostKeyPrivate } = data; if (user) ftpUser = user; + if (savedPassword) ftpPassword = decrypt(savedPassword); + + const { stdout: password } = await asyncExecShell( + `echo ${ftpPassword} | openssl passwd -1 -stdin` + ); if (destinationDockerId) { try { await fs.stat(hostkeyDir); @@ -74,7 +77,11 @@ export const post: RequestHandler = async (event) => { if (ftpEnabled) { await db.prisma.wordpress.update({ where: { serviceId: id }, - data: { ftpPublicPort: publicPort, ftpUser, ftpPassword: encrypt(ftpPassword) } + data: { + ftpPublicPort: publicPort, + ftpUser: user ? undefined : ftpUser, + ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) + } }); try { @@ -125,6 +132,9 @@ export const post: RequestHandler = async (event) => { ); await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); + await asyncExecShell( + `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub` + ); } else { await db.prisma.wordpress.update({ where: { serviceId: id }, @@ -141,8 +151,10 @@ export const post: RequestHandler = async (event) => { console.log(error); // } - await stopTcpHttpProxy(destinationDocker, oldPublicPort); + await asyncExecShell( + `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ` + ); } } if (ftpEnabled) { From 9a05bfa8999b514d46c92eec3e453efd1fd53993 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 10:30:53 +0200 Subject: [PATCH 10/24] fix: Fix for fix haha --- src/routes/services/[id]/wordpress/settings.json.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 3356be86b..796510989 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -132,9 +132,6 @@ export const post: RequestHandler = async (event) => { ); await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); - await asyncExecShell( - `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub` - ); } else { await db.prisma.wordpress.update({ where: { serviceId: id }, @@ -152,9 +149,6 @@ export const post: RequestHandler = async (event) => { // } await stopTcpHttpProxy(destinationDocker, oldPublicPort); - await asyncExecShell( - `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ` - ); } } if (ftpEnabled) { @@ -175,5 +169,9 @@ export const post: RequestHandler = async (event) => { } catch (error) { console.log(error); return ErrorHandler(error); + } finally { + await asyncExecShell( + `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ` + ); } }; From 7a2f29f6a39bf64839509b782afbb70ed7e464c3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 13:35:53 +0200 Subject: [PATCH 11/24] feat: PHP Composer support --- src/lib/buildPacks/php.ts | 16 ++++++++++++++-- .../[id]/configuration/buildpack.svelte | 10 ++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lib/buildPacks/php.ts b/src/lib/buildPacks/php.ts index 14ee5d0cb..cfb39d20a 100644 --- a/src/lib/buildPacks/php.ts +++ b/src/lib/buildPacks/php.ts @@ -4,6 +4,12 @@ import { promises as fs } from 'fs'; const createDockerfile = async (data, image, htaccessFound): Promise => { const { workdir, baseDirectory } = 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.image=true`); Dockerfile.push('WORKDIR /app'); @@ -11,6 +17,10 @@ const createDockerfile = async (data, image, htaccessFound): Promise => { 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 80`); await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); @@ -21,12 +31,14 @@ export default async function (data) { try { let htaccessFound = false; try { - const d = await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`); + await fs.readFile(`${workdir}${baseDirectory || ''}/.htaccess`); htaccessFound = true; } catch (e) { // } - const image = htaccessFound ? 'webdevops/php-apache' : 'webdevops/php-nginx'; + const image = htaccessFound + ? 'webdevops/php-apache:8.0-alpine' + : 'webdevops/php-nginx:8.0-alpine'; await createDockerfile(data, image, htaccessFound); await buildImage(data); } catch (error) { diff --git a/src/routes/applications/[id]/configuration/buildpack.svelte b/src/routes/applications/[id]/configuration/buildpack.svelte index cd005768c..c885f3bf5 100644 --- a/src/routes/applications/[id]/configuration/buildpack.svelte +++ b/src/routes/applications/[id]/configuration/buildpack.svelte @@ -81,6 +81,9 @@ ); const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'blob'); const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'blob'); + const composerPHP = files.find( + (file) => file.name === 'composer.json' && file.type === 'blob' + ); if (yarnLock) packageManager = 'yarn'; if (pnpmLock) packageManager = 'pnpm'; @@ -103,7 +106,7 @@ foundConfig = findBuildPack('python'); } else if (indexHtml) { foundConfig = findBuildPack('static', packageManager); - } else if (indexPHP) { + } else if (indexPHP || composerPHP) { foundConfig = findBuildPack('php'); } else { foundConfig = findBuildPack('node', packageManager); @@ -127,6 +130,9 @@ ); const indexHtml = files.find((file) => file.name === 'index.html' && file.type === 'file'); const indexPHP = files.find((file) => file.name === 'index.php' && file.type === 'file'); + const composerPHP = files.find( + (file) => file.name === 'composer.json' && file.type === 'file' + ); if (yarnLock) packageManager = 'yarn'; if (pnpmLock) packageManager = 'pnpm'; @@ -146,7 +152,7 @@ foundConfig = findBuildPack('python'); } else if (indexHtml) { foundConfig = findBuildPack('static', packageManager); - } else if (indexPHP) { + } else if (indexPHP || composerPHP) { foundConfig = findBuildPack('php'); } else { foundConfig = findBuildPack('node', packageManager); From fe2cc5a99aa5dc71a00cf6fbc5527bafa30aad3b Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 14:27:51 +0200 Subject: [PATCH 12/24] feat: Working on-demand sftp to wp data --- .../services/[id]/wordpress/settings.json.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 796510989..080326cfc 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -97,9 +97,17 @@ export const post: RequestHandler = async (event) => { } const volumes = [ `${id}-wordpress-data:/home/${ftpUser}`, - `${hostkeyDir}/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, - `${hostkeyDir}/${id}.rsa:/etc/ssh/ssh_host_rsa_key` + `${ + dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, + `${ + dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, + `${ + dev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.sh:/etc/sftp.d/chmod.sh` ]; + const compose = { version: '3.8', services: { @@ -126,6 +134,11 @@ export const post: RequestHandler = async (event) => { } } }; + await fs.writeFile( + `${hostkeyDir}/${id}.sh`, + `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key` + ); + await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`); await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); await asyncExecShell( `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` @@ -138,14 +151,10 @@ export const post: RequestHandler = async (event) => { data: { ftpPublicPort: null } }); try { - const isRunning = await checkContainer(engine, `${id}-ftp`); - if (isRunning) { - await asyncExecShell( - `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` - ); - } + await asyncExecShell( + `DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` + ); } catch (error) { - console.log(error); // } await stopTcpHttpProxy(destinationDocker, oldPublicPort); @@ -171,7 +180,7 @@ export const post: RequestHandler = async (event) => { return ErrorHandler(error); } finally { await asyncExecShell( - `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ` + `rm -f ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` ); } }; From 352bb651257e185e9108b9c0e579e052589b065f Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 14:57:41 +0200 Subject: [PATCH 13/24] ui: fix --- src/routes/applications/[id]/configuration/source.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/applications/[id]/configuration/source.svelte b/src/routes/applications/[id]/configuration/source.svelte index b1df17aa3..953c23d69 100644 --- a/src/routes/applications/[id]/configuration/source.svelte +++ b/src/routes/applications/[id]/configuration/source.svelte @@ -62,7 +62,7 @@
{#if !filteredSources || filteredSources.length === 0}
-
No configurable Git Source found
+
No configurable Git Source found
Date: Wed, 6 Apr 2022 15:55:17 +0200 Subject: [PATCH 14/24] feat: Admin team sees everything --- src/lib/database/applications.ts | 42 +++-- src/lib/database/databases.ts | 25 ++- src/lib/database/destinations.ts | 23 ++- src/lib/database/gitSources.ts | 23 ++- src/lib/database/services.ts | 44 ++++-- src/routes/applications/_Application.svelte | 1 + src/routes/databases/index.svelte | 1 + .../destinations/[id]/_LocalDocker.svelte | 52 ++----- src/routes/destinations/[id]/index.json.ts | 2 +- src/routes/destinations/index.svelte | 1 + src/routes/services/index.svelte | 1 + src/routes/sources/[id]/_Gitlab.svelte | 144 +++++++++--------- src/routes/sources/index.svelte | 1 + 13 files changed, 210 insertions(+), 150 deletions(-) diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index 1d8140144..1c6e172c2 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -5,7 +5,13 @@ import { getDomain, removeDestinationDocker } from '$lib/common'; import { prisma } from './common'; export async function listApplications(teamId) { - return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } }); + if (teamId === '0') { + return await prisma.application.findMany({ include: { teams: true } }); + } + return await prisma.application.findMany({ + where: { teams: { some: { id: teamId } } }, + include: { teams: true } + }); } export async function newApplication({ name, teamId }) { @@ -130,16 +136,30 @@ export async function getApplicationById({ id }) { return { ...body }; } export async function getApplication({ id, teamId }) { - let body = await prisma.application.findFirst({ - where: { id, teams: { some: { id: teamId } } }, - include: { - destinationDocker: true, - settings: true, - gitSource: { include: { githubApp: true, gitlabApp: true } }, - secrets: true, - persistentStorage: true - } - }); + let body = {}; + if (teamId === '0') { + body = await prisma.application.findFirst({ + where: { id }, + include: { + destinationDocker: true, + settings: true, + gitSource: { include: { githubApp: true, gitlabApp: true } }, + secrets: true, + persistentStorage: true + } + }); + } else { + body = await prisma.application.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { + destinationDocker: true, + settings: true, + gitSource: { include: { githubApp: true, gitlabApp: true } }, + secrets: true, + persistentStorage: true + } + }); + } if (body?.gitSource?.githubApp?.clientSecret) { body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret); diff --git a/src/lib/database/databases.ts b/src/lib/database/databases.ts index be5ddacd3..7fc237944 100644 --- a/src/lib/database/databases.ts +++ b/src/lib/database/databases.ts @@ -7,7 +7,14 @@ import getPort, { portNumbers } from 'get-port'; import { asyncExecShell, getEngine, removeContainer } from '$lib/common'; export async function listDatabases(teamId) { - return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } }); + if (teamId === '0') { + return await prisma.database.findMany({ include: { teams: true } }); + } else { + return await prisma.database.findMany({ + where: { teams: { some: { id: teamId } } }, + include: { teams: true } + }); + } } export async function newDatabase({ name, teamId }) { const dbUser = cuid(); @@ -31,10 +38,18 @@ export async function newDatabase({ name, teamId }) { } export async function getDatabase({ id, teamId }) { - const body = await prisma.database.findFirst({ - where: { id, teams: { some: { id: teamId } } }, - include: { destinationDocker: true, settings: true } - }); + let body = {}; + if (teamId === '0') { + body = await prisma.database.findFirst({ + where: { id }, + include: { destinationDocker: true, settings: true } + }); + } else { + body = await prisma.database.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { destinationDocker: true, settings: true } + }); + } if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword); if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword); diff --git a/src/lib/database/destinations.ts b/src/lib/database/destinations.ts index 3f3aadec5..5bd63c81b 100644 --- a/src/lib/database/destinations.ts +++ b/src/lib/database/destinations.ts @@ -6,7 +6,13 @@ import { getDatabaseImage } from '.'; import { prisma } from './common'; export async function listDestinations(teamId) { - return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } }); + if (teamId === '0') { + return await prisma.destinationDocker.findMany({ include: { teams: true } }); + } + return await prisma.destinationDocker.findMany({ + where: { teams: { some: { id: teamId } } }, + include: { teams: true } + }); } export async function configureDestinationForService({ id, destinationId }) { @@ -124,12 +130,17 @@ export async function removeDestination({ id }) { } export async function getDestination({ id, teamId }) { - let destination = await prisma.destinationDocker.findFirst({ - where: { id, teams: { some: { id: teamId } } } - }); - if (destination.remoteEngine) { - destination.sshPrivateKey = decrypt(destination.sshPrivateKey); + let destination = {}; + if (teamId === '0') { + destination = await prisma.destinationDocker.findFirst({ + where: { id } + }); + } else { + destination = await prisma.destinationDocker.findFirst({ + where: { id, teams: { some: { id: teamId } } } + }); } + return destination; } export async function getDestinationByApplicationId({ id, teamId }) { diff --git a/src/lib/database/gitSources.ts b/src/lib/database/gitSources.ts index 927907964..7e4e90633 100644 --- a/src/lib/database/gitSources.ts +++ b/src/lib/database/gitSources.ts @@ -2,9 +2,14 @@ import { decrypt, encrypt } from '$lib/crypto'; import { prisma } from './common'; export async function listSources(teamId) { + if (teamId === '0') { + return await prisma.gitSource.findMany({ + include: { githubApp: true, gitlabApp: true, teams: true } + }); + } return await prisma.gitSource.findMany({ where: { teams: { some: { id: teamId } } }, - include: { githubApp: true, gitlabApp: true } + include: { githubApp: true, gitlabApp: true, teams: true } }); } @@ -31,10 +36,18 @@ export async function removeSource({ id }) { } export async function getSource({ id, teamId }) { - let body = await prisma.gitSource.findFirst({ - where: { id, teams: { some: { id: teamId } } }, - include: { githubApp: true, gitlabApp: true } - }); + let body = {}; + if (teamId === '0') { + body = await prisma.gitSource.findFirst({ + where: { id }, + include: { githubApp: true, gitlabApp: true } + }); + } else { + body = await prisma.gitSource.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include: { githubApp: true, gitlabApp: true } + }); + } if (body?.githubApp?.clientSecret) body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret); if (body?.githubApp?.webhookSecret) diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index bc25b9327..d578a500c 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -5,7 +5,14 @@ import { generatePassword } from '.'; import { prisma } from './common'; export async function listServices(teamId) { - return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } }); + if (teamId === '0') { + return await prisma.service.findMany({ include: { teams: true } }); + } else { + return await prisma.service.findMany({ + where: { teams: { some: { id: teamId } } }, + include: { teams: true } + }); + } } export async function newService({ name, teamId }) { @@ -13,19 +20,28 @@ export async function newService({ name, teamId }) { } export async function getService({ id, teamId }) { - const body = await prisma.service.findFirst({ - where: { id, teams: { some: { id: teamId } } }, - include: { - destinationDocker: true, - plausibleAnalytics: true, - minio: true, - vscodeserver: true, - wordpress: true, - ghost: true, - serviceSecret: true, - meiliSearch: true - } - }); + let body = {}; + const include = { + destinationDocker: true, + plausibleAnalytics: true, + minio: true, + vscodeserver: true, + wordpress: true, + ghost: true, + serviceSecret: true, + meiliSearch: true + }; + if (teamId === '0') { + body = await prisma.service.findFirst({ + where: { id }, + include + }); + } else { + body = await prisma.service.findFirst({ + where: { id, teams: { some: { id: teamId } } }, + include + }); + } if (body.plausibleAnalytics?.postgresqlPassword) body.plausibleAnalytics.postgresqlPassword = decrypt( diff --git a/src/routes/applications/_Application.svelte b/src/routes/applications/_Application.svelte index 16d19a42f..fb6a157c7 100644 --- a/src/routes/applications/_Application.svelte +++ b/src/routes/applications/_Application.svelte @@ -54,6 +54,7 @@ {/if}
{application.name}
+
Team {application.teams[0].name}
{#if application.fqdn}
{application.fqdn}
{/if} diff --git a/src/routes/databases/index.svelte b/src/routes/databases/index.svelte index f6834cd0b..6caa4d90e 100644 --- a/src/routes/databases/index.svelte +++ b/src/routes/databases/index.svelte @@ -59,6 +59,7 @@
{database.name}
+
Team {database.teams[0].name}
{#if !database.type}
Configuration missing diff --git a/src/routes/destinations/[id]/_LocalDocker.svelte b/src/routes/destinations/[id]/_LocalDocker.svelte index 329d23c2f..1ed6d3b99 100644 --- a/src/routes/destinations/[id]/_LocalDocker.svelte +++ b/src/routes/destinations/[id]/_LocalDocker.svelte @@ -184,41 +184,19 @@ value={destination.network} />
-
-
${ - cannotDisable - ? 'You cannot disable this proxy as FQDN is configured for Coolify.' - : '' - }`} - /> -
- - - - + diff --git a/src/routes/destinations/[id]/index.json.ts b/src/routes/destinations/[id]/index.json.ts index e88c29825..d64df8c08 100644 --- a/src/routes/destinations/[id]/index.json.ts +++ b/src/routes/destinations/[id]/index.json.ts @@ -8,7 +8,7 @@ import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); if (status === 401) return { status, body }; - + console.log(teamId); const { id } = event.params; try { const destination = await db.getDestination({ id, teamId }); diff --git a/src/routes/destinations/index.svelte b/src/routes/destinations/index.svelte index 01f860b21..a7b6b9a7e 100644 --- a/src/routes/destinations/index.svelte +++ b/src/routes/destinations/index.svelte @@ -57,6 +57,7 @@
{destination.name}
+
Team {destination.teams[0].name}
{destination.network}
diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte index 4cba9a702..f2a6e5fb0 100644 --- a/src/routes/services/index.svelte +++ b/src/routes/services/index.svelte @@ -74,6 +74,7 @@
{service.name}
+
Team {service.teams[0].name}
{#if !service.type || !service.fqdn}
Configuration missing diff --git a/src/routes/sources/[id]/_Gitlab.svelte b/src/routes/sources/[id]/_Gitlab.svelte index 56bb0288d..7823aebe9 100644 --- a/src/routes/sources/[id]/_Gitlab.svelte +++ b/src/routes/sources/[id]/_Gitlab.svelte @@ -91,93 +91,95 @@ {#if !source.gitlabApp?.appId} -
-
- - -
- {#if payload.applicationType === 'group'} +
+
- - + +
- {/if} + {#if payload.applicationType === 'group'} +
+ + +
+ {/if} -
- -
+
+ +
- - -
-
-
Configuration
- -
+ /> +
+
+
+
Configuration
+ +
-
-
- - +
+ + +
+
- -
- {#if payload.applicationType === 'group'} + {#if payload.applicationType === 'group'} +
+ + +
+ {/if}
- - + +
- {/if} -
- - -
-
- - -
-
+
+ + +
+ +
{:else}
diff --git a/src/routes/sources/index.svelte b/src/routes/sources/index.svelte index bd9039514..d80579272 100644 --- a/src/routes/sources/index.svelte +++ b/src/routes/sources/index.svelte @@ -60,6 +60,7 @@ class:border-l-4={source.gitlabApp && !source.gitlabAppId} >
{source.name}
+
Team {source.teams[0].name}
{#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && !source.githubAppId && !source.githubApp?.installationId)}
Configuration missing From d18bb9cc748f2ecfa1f3bae87557469550a75c67 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 17:18:25 +0200 Subject: [PATCH 15/24] Extend typings --- src/lib/queues/builder.ts | 11 ++++------- src/lib/types/composeFile.ts | 6 +++--- src/routes/services/[id]/wordpress/settings.json.ts | 3 ++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts index f6d57862e..a09e2c3bf 100644 --- a/src/lib/queues/builder.ts +++ b/src/lib/queues/builder.ts @@ -20,12 +20,9 @@ import { setDefaultConfiguration } from '$lib/buildPacks/common'; import yaml from 'js-yaml'; +import type { ComposeFile } from '$lib/types/composeFile'; export default async function (job) { - /* - Edge cases: - 1 - Change build pack and redeploy, what should happen? - */ let { id: applicationId, repository, @@ -274,7 +271,7 @@ export default async function (job) { } }; }); - const compose = { + const composeFile: ComposeFile = { version: '3.8', services: { [imageId]: { @@ -283,7 +280,7 @@ export default async function (job) { volumes, env_file: envFound ? [`${workdir}/.env`] : [], networks: [docker.network], - labels: labels, + labels, depends_on: [], restart: 'always' } @@ -295,7 +292,7 @@ export default async function (job) { }, volumes: Object.assign({}, ...composeVolumes) }; - await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose)); + await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); await asyncExecShell( `DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d` ); diff --git a/src/lib/types/composeFile.ts b/src/lib/types/composeFile.ts index b7e88210f..43b8d2e7b 100644 --- a/src/lib/types/composeFile.ts +++ b/src/lib/types/composeFile.ts @@ -1,5 +1,3 @@ -import { makeLabelForServices } from '../buildPacks/common'; - export type ComposeFile = { version: ComposerFileVersion; services: Record; @@ -11,10 +9,12 @@ export type ComposeFileService = { container_name: string; image?: string; networks: string[]; - environment: Record; + environment?: Record; volumes?: string[]; ulimits?: unknown; labels?: string[]; + env_file?: string[]; + extra_hosts?: string[]; restart: ComposeFileRestartOption; depends_on?: string[]; command?: string; diff --git a/src/routes/services/[id]/wordpress/settings.json.ts b/src/routes/services/[id]/wordpress/settings.json.ts index 080326cfc..e82b6baba 100644 --- a/src/routes/services/[id]/wordpress/settings.json.ts +++ b/src/routes/services/[id]/wordpress/settings.json.ts @@ -4,6 +4,7 @@ import { decrypt, encrypt } from '$lib/crypto'; import * as db from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler, generatePassword } from '$lib/database'; import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; +import type { ComposeFile } from '$lib/types/composeFile'; import type { RequestHandler } from '@sveltejs/kit'; import cuid from 'cuid'; import fs from 'fs/promises'; @@ -108,7 +109,7 @@ export const post: RequestHandler = async (event) => { }/${id}.sh:/etc/sftp.d/chmod.sh` ]; - const compose = { + const compose: ComposeFile = { version: '3.8', services: { [`${id}-ftp`]: { From ae4942ba29877f286d68245f3ca0a177f1b8a472 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 19:17:28 +0200 Subject: [PATCH 16/24] feat: Able to change service version/tag --- src/lib/components/common.ts | 139 ++++++++++++++++++ src/lib/database/common.ts | 132 +---------------- src/lib/haproxy/configuration.ts | 3 +- src/lib/letsencrypt/index.ts | 3 +- .../databases/[id]/configuration/type.json.ts | 3 +- .../[id]/configuration/version.json.ts | 3 +- .../services/[id]/_Services/_Services.svelte | 17 ++- .../services/[id]/configuration/type.json.ts | 3 +- .../[id]/configuration/version.json.ts | 4 +- .../[id]/configuration/version.svelte | 24 ++- 10 files changed, 193 insertions(+), 138 deletions(-) diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index f6d0f232c..f8c28cdb2 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -43,3 +43,142 @@ export function changeQueryParams(buildId) { queryParams.set('buildId', buildId); return history.pushState(null, null, '?' + queryParams.toString()); } + +export const supportedDatabaseTypesAndVersions = [ + { + name: 'mongodb', + fancyName: 'MongoDB', + baseImage: 'bitnami/mongodb', + versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27'] + }, + { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] }, + { + name: 'postgresql', + fancyName: 'PostgreSQL', + baseImage: 'bitnami/postgresql', + versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24'] + }, + { + name: 'redis', + fancyName: 'Redis', + baseImage: 'bitnami/redis', + versions: ['6.2.6', '6.0.16', '5.0.14'] + }, + { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] } +]; +export const supportedServiceTypesAndVersions = [ + { + name: 'plausibleanalytics', + fancyName: 'Plausible Analytics', + baseImage: 'plausible/analytics', + images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'], + versions: ['latest', 'stable'], + recommendedVersion: 'stable', + ports: { + main: 8000 + } + }, + { + name: 'nocodb', + fancyName: 'NocoDB', + baseImage: 'nocodb/nocodb', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8080 + } + }, + { + name: 'minio', + fancyName: 'MinIO', + baseImage: 'minio/minio', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 9001 + } + }, + { + name: 'vscodeserver', + fancyName: 'VSCode Server', + baseImage: 'codercom/code-server', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8080 + } + }, + { + name: 'wordpress', + fancyName: 'Wordpress', + baseImage: 'wordpress', + images: ['bitnami/mysql:5.7'], + versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'], + recommendedVersion: 'latest', + ports: { + main: 80 + } + }, + { + name: 'vaultwarden', + fancyName: 'Vaultwarden', + baseImage: 'vaultwarden/server', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 80 + } + }, + { + name: 'languagetool', + fancyName: 'LanguageTool', + baseImage: 'silviof/docker-languagetool', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8010 + } + }, + { + name: 'n8n', + fancyName: 'n8n', + baseImage: 'n8nio/n8n', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 5678 + } + }, + { + name: 'uptimekuma', + fancyName: 'Uptime Kuma', + baseImage: 'louislam/uptime-kuma', + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 3001 + } + }, + { + name: 'ghost', + fancyName: 'Ghost', + baseImage: 'bitnami/ghost', + images: ['bitnami/mariadb'], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 2368 + } + }, + { + name: 'meilisearch', + fancyName: 'Meilisearch', + baseImage: 'getmeili/meilisearch', + images: [], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 7700 + } + } +]; diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 75366dbc9..287dcd834 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -1,5 +1,9 @@ import { dev } from '$app/env'; import { sentry } from '$lib/common'; +import { + supportedDatabaseTypesAndVersions, + supportedServiceTypesAndVersions +} from '$lib/components/common'; import * as Prisma from '@prisma/client'; import { default as ProdPrisma } from '@prisma/client'; import type { PrismaClientOptions } from '@prisma/client/runtime'; @@ -82,134 +86,6 @@ export async function generateSshKeyPair(): Promise<{ publicKey: string; private }); } -export const supportedDatabaseTypesAndVersions = [ - { - name: 'mongodb', - fancyName: 'MongoDB', - baseImage: 'bitnami/mongodb', - versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27'] - }, - { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] }, - { - name: 'postgresql', - fancyName: 'PostgreSQL', - baseImage: 'bitnami/postgresql', - versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24'] - }, - { - name: 'redis', - fancyName: 'Redis', - baseImage: 'bitnami/redis', - versions: ['6.2.6', '6.0.16', '5.0.14'] - }, - { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] } -]; -export const supportedServiceTypesAndVersions = [ - { - name: 'plausibleanalytics', - fancyName: 'Plausible Analytics', - baseImage: 'plausible/analytics', - images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'], - versions: ['latest', 'stable'], - ports: { - main: 8000 - } - }, - { - name: 'nocodb', - fancyName: 'NocoDB', - baseImage: 'nocodb/nocodb', - versions: ['latest'], - ports: { - main: 8080 - } - }, - { - name: 'minio', - fancyName: 'MinIO', - baseImage: 'minio/minio', - versions: ['latest'], - ports: { - main: 9001 - } - }, - { - name: 'vscodeserver', - fancyName: 'VSCode Server', - baseImage: 'codercom/code-server', - versions: ['latest'], - ports: { - main: 8080 - } - }, - { - name: 'wordpress', - fancyName: 'Wordpress', - baseImage: 'wordpress', - images: ['bitnami/mysql:5.7'], - versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'], - ports: { - main: 80 - } - }, - { - name: 'vaultwarden', - fancyName: 'Vaultwarden', - baseImage: 'vaultwarden/server', - versions: ['latest'], - ports: { - main: 80 - } - }, - { - name: 'languagetool', - fancyName: 'LanguageTool', - baseImage: 'silviof/docker-languagetool', - versions: ['latest'], - ports: { - main: 8010 - } - }, - { - name: 'n8n', - fancyName: 'n8n', - baseImage: 'n8nio/n8n', - versions: ['latest'], - ports: { - main: 5678 - } - }, - { - name: 'uptimekuma', - fancyName: 'Uptime Kuma', - baseImage: 'louislam/uptime-kuma', - versions: ['latest'], - ports: { - main: 3001 - } - }, - { - name: 'ghost', - fancyName: 'Ghost', - baseImage: 'bitnami/ghost', - images: ['bitnami/mariadb'], - versions: ['latest'], - ports: { - main: 2368 - } - }, - { - name: 'meilisearch', - fancyName: 'Meilisearch', - baseImage: 'getmeili/meilisearch', - images: [], - versions: ['latest'], - ports: { - main: 7700 - } - } -]; - export function getVersions(type) { const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type); if (found) { diff --git a/src/lib/haproxy/configuration.ts b/src/lib/haproxy/configuration.ts index a7f223e0e..831d9490b 100644 --- a/src/lib/haproxy/configuration.ts +++ b/src/lib/haproxy/configuration.ts @@ -6,6 +6,7 @@ import crypto from 'crypto'; import * as db from '$lib/database'; import { checkContainer, checkHAProxy } from '.'; import { asyncExecShell, getDomain, getEngine } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; @@ -223,7 +224,7 @@ export async function configureHAProxy() { const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service; if (destinationDockerId) { const { engine } = destinationDocker; - const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type); + const found = supportedServiceTypesAndVersions.find((a) => a.name === type); if (found) { const port = found.ports.main; const publicPort = service[type]?.publicPort; diff --git a/src/lib/letsencrypt/index.ts b/src/lib/letsencrypt/index.ts index eb74aa252..c9f9c037b 100644 --- a/src/lib/letsencrypt/index.ts +++ b/src/lib/letsencrypt/index.ts @@ -4,6 +4,7 @@ import * as db from '$lib/database'; import { dev } from '$app/env'; import cuid from 'cuid'; import getPort, { portNumbers } from 'get-port'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; export async function letsEncrypt(domain, id = null, isCoolify = false) { try { @@ -160,7 +161,7 @@ export async function generateSSLCerts() { type, destinationDocker: { engine } } = service; - const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type); + const found = supportedServiceTypesAndVersions.find((a) => a.name === type); if (found) { const domain = getDomain(fqdn); const isHttps = fqdn.startsWith('https://'); diff --git a/src/routes/databases/[id]/configuration/type.json.ts b/src/routes/databases/[id]/configuration/type.json.ts index 6f797e415..8ab007891 100644 --- a/src/routes/databases/[id]/configuration/type.json.ts +++ b/src/routes/databases/[id]/configuration/type.json.ts @@ -1,6 +1,7 @@ import { getUserDetails } from '$lib/common'; +import { supportedDatabaseTypesAndVersions } from '$lib/components/common'; import * as db from '$lib/database'; -import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; +import { ErrorHandler } from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { diff --git a/src/routes/databases/[id]/configuration/version.json.ts b/src/routes/databases/[id]/configuration/version.json.ts index 3477d832d..000717016 100644 --- a/src/routes/databases/[id]/configuration/version.json.ts +++ b/src/routes/databases/[id]/configuration/version.json.ts @@ -1,6 +1,7 @@ import { getUserDetails } from '$lib/common'; +import { supportedDatabaseTypesAndVersions } from '$lib/components/common'; import * as db from '$lib/database'; -import { ErrorHandler, supportedDatabaseTypesAndVersions } from '$lib/database'; +import { ErrorHandler } from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index a36feb047..637944b65 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -92,7 +92,22 @@ />
- +
+ + + +
diff --git a/src/routes/services/[id]/configuration/type.json.ts b/src/routes/services/[id]/configuration/type.json.ts index daf1cc71a..42c5344e6 100644 --- a/src/routes/services/[id]/configuration/type.json.ts +++ b/src/routes/services/[id]/configuration/type.json.ts @@ -1,6 +1,7 @@ import { getUserDetails } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; import * as db from '$lib/database'; -import { ErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; +import { ErrorHandler } from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { diff --git a/src/routes/services/[id]/configuration/version.json.ts b/src/routes/services/[id]/configuration/version.json.ts index f1a1a9522..bf93e167b 100644 --- a/src/routes/services/[id]/configuration/version.json.ts +++ b/src/routes/services/[id]/configuration/version.json.ts @@ -1,6 +1,7 @@ import { getUserDetails } from '$lib/common'; +import { supportedServiceTypesAndVersions } from '$lib/components/common'; import * as db from '$lib/database'; -import { ErrorHandler, supportedServiceTypesAndVersions } from '$lib/database'; +import { ErrorHandler } from '$lib/database'; import type { RequestHandler } from '@sveltejs/kit'; export const get: RequestHandler = async (event) => { @@ -14,6 +15,7 @@ export const get: RequestHandler = async (event) => { return { status: 200, body: { + type, versions: supportedServiceTypesAndVersions.find((name) => name.name === type).versions } }; diff --git a/src/routes/services/[id]/configuration/version.svelte b/src/routes/services/[id]/configuration/version.svelte index 5f2930487..bda5bb46c 100644 --- a/src/routes/services/[id]/configuration/version.svelte +++ b/src/routes/services/[id]/configuration/version.svelte @@ -31,11 +31,16 @@ import { errorNotification } from '$lib/form'; import { goto } from '$app/navigation'; import { post } from '$lib/api'; + import { supportedServiceTypesAndVersions } from '$lib/components/common'; const { id } = $page.params; const from = $page.url.searchParams.get('from'); export let versions; + export let type; + let recommendedVersion = supportedServiceTypesAndVersions.find( + ({ name }) => name === type + )?.recommendedVersion; async function handleSubmit(version) { try { await post(`/services/${id}/configuration/version.json`, { version }); @@ -49,13 +54,26 @@
Select a Service version
- +{#if from} +
+ Warning: you are about to change the version of this service.
This could cause problem + after you restart the service, + like losing your data, incompatibility issues, etc.
Only do if you know what you are doing. +
+{/if}
{#each versions as version}
handleSubmit(version)}> - {version} + {#if recommendedVersion === version} + recommended + {/if}
From 9f2f5b40c341e2a48a3342eaebf1ddb3006d348e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 20:44:24 +0200 Subject: [PATCH 17/24] Only show teams for root team --- src/routes/applications/_Application.svelte | 5 ++++- src/routes/databases/index.svelte | 5 ++++- src/routes/destinations/index.svelte | 4 +++- src/routes/services/index.svelte | 5 ++++- src/routes/sources/index.svelte | 4 +++- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/routes/applications/_Application.svelte b/src/routes/applications/_Application.svelte index fb6a157c7..8f183eb13 100644 --- a/src/routes/applications/_Application.svelte +++ b/src/routes/applications/_Application.svelte @@ -15,6 +15,7 @@ import Docker from '$lib/components/svg/applications/Docker.svelte'; import Astro from '$lib/components/svg/applications/Astro.svelte'; import Eleventy from '$lib/components/svg/applications/Eleventy.svelte'; + import { session } from '$app/stores'; const buildPack = application?.buildPack?.toLowerCase(); @@ -54,7 +55,9 @@ {/if}
{application.name}
-
Team {application.teams[0].name}
+ {#if $session.teamId === '0'} +
Team {application.teams[0].name}
+ {/if} {#if application.fqdn}
{application.fqdn}
{/if} diff --git a/src/routes/databases/index.svelte b/src/routes/databases/index.svelte index 6caa4d90e..32156ef4b 100644 --- a/src/routes/databases/index.svelte +++ b/src/routes/databases/index.svelte @@ -8,6 +8,7 @@ import Redis from '$lib/components/svg/databases/Redis.svelte'; import { post } from '$lib/api'; import { goto } from '$app/navigation'; + import { session } from '$app/stores'; async function newDatabase() { const { id } = await post('/databases/new', {}); @@ -59,7 +60,9 @@
{database.name}
-
Team {database.teams[0].name}
+ {#if $session.teamId === '0'} +
Team {database.teams[0].name}
+ {/if} {#if !database.type}
Configuration missing diff --git a/src/routes/destinations/index.svelte b/src/routes/destinations/index.svelte index a7b6b9a7e..df186e35f 100644 --- a/src/routes/destinations/index.svelte +++ b/src/routes/destinations/index.svelte @@ -57,7 +57,9 @@
{destination.name}
-
Team {destination.teams[0].name}
+ {#if $session.teamId === '0'} +
Team {destination.teams[0].name}
+ {/if}
{destination.network}
diff --git a/src/routes/services/index.svelte b/src/routes/services/index.svelte index f2a6e5fb0..d590761a2 100644 --- a/src/routes/services/index.svelte +++ b/src/routes/services/index.svelte @@ -12,6 +12,7 @@ import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte'; import Ghost from '$lib/components/svg/services/Ghost.svelte'; import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte'; + import { session } from '$app/stores'; export let services; async function newService() { @@ -74,7 +75,9 @@
{service.name}
-
Team {service.teams[0].name}
+ {#if $session.teamId === '0'} +
Team {service.teams[0].name}
+ {/if} {#if !service.type || !service.fqdn}
Configuration missing diff --git a/src/routes/sources/index.svelte b/src/routes/sources/index.svelte index d80579272..c57efdaa6 100644 --- a/src/routes/sources/index.svelte +++ b/src/routes/sources/index.svelte @@ -60,7 +60,9 @@ class:border-l-4={source.gitlabApp && !source.gitlabAppId} >
{source.name}
-
Team {source.teams[0].name}
+ {#if $session.teamId === '0'} +
Team {source.teams[0].name}
+ {/if} {#if (source.type === 'gitlab' && !source.gitlabAppId) || (source.type === 'github' && !source.githubAppId && !source.githubApp?.installationId)}
Configuration missing From f3cdda29bc099ae018b58742e7eacc97c5fdd33e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 21:37:42 +0200 Subject: [PATCH 18/24] code improvement --- .env.template | 3 ++- prisma/schema.prisma | 2 +- src/lib/database/services.ts | 12 ------------ src/lib/types/composeFile.ts | 6 +++++- src/routes/services/[id]/languagetool/index.json.ts | 2 +- src/routes/services/[id]/meilisearch/index.json.ts | 2 +- src/routes/services/[id]/vaultwarden/index.json.ts | 2 +- src/routes/services/[id]/vscodeserver/index.json.ts | 2 +- 8 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.env.template b/.env.template index ce02dc5bd..0fa7427f3 100644 --- a/.env.template +++ b/.env.template @@ -2,4 +2,5 @@ COOLIFY_APP_ID= COOLIFY_SECRET_KEY=12341234123412341234123412341234 COOLIFY_DATABASE_URL=file:../db/dev.db COOLIFY_SENTRY_DSN= -COOLIFY_IS_ON="docker" \ No newline at end of file +COOLIFY_IS_ON="docker" +COOLIFY_WHITE_LABELED="false" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d5e322337..7f37f9a2b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -336,7 +336,7 @@ model Wordpress { ftpUser String? ftpPassword String? ftpPublicPort Int? - ftpHostKey String? + ftpHostKey String? ftpHostKeyPrivate String? serviceId String @unique service Service @relation(fields: [serviceId], references: [id]) diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index d578a500c..b4d355946 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -220,18 +220,6 @@ export async function updatePlausibleAnalyticsService({ id, fqdn, email, usernam export async function updateService({ id, fqdn, name }) { return await prisma.service.update({ where: { id }, data: { fqdn, name } }); } -export async function updateLanguageToolService({ id, fqdn, name }) { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} -export async function updateMeiliSearchService({ id, fqdn, name }) { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} -export async function updateVaultWardenService({ id, fqdn, name }) { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} -export async function updateVsCodeServer({ id, fqdn, name }) { - return await prisma.service.update({ where: { id }, data: { fqdn, name } }); -} export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) { return await prisma.service.update({ where: { id }, diff --git a/src/lib/types/composeFile.ts b/src/lib/types/composeFile.ts index 43b8d2e7b..1e2a0723c 100644 --- a/src/lib/types/composeFile.ts +++ b/src/lib/types/composeFile.ts @@ -18,7 +18,11 @@ export type ComposeFileService = { restart: ComposeFileRestartOption; depends_on?: string[]; command?: string; - build?: string; + build?: { + context: string; + dockerfile: string; + args?: Record; + }; }; export type ComposerFileVersion = diff --git a/src/routes/services/[id]/languagetool/index.json.ts b/src/routes/services/[id]/languagetool/index.json.ts index c253112b9..d717502c5 100644 --- a/src/routes/services/[id]/languagetool/index.json.ts +++ b/src/routes/services/[id]/languagetool/index.json.ts @@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => { if (fqdn) fqdn = fqdn.toLowerCase(); try { - await db.updateMeiliSearchService({ id, fqdn, name }); + await db.updateService({ id, fqdn, name }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/services/[id]/meilisearch/index.json.ts b/src/routes/services/[id]/meilisearch/index.json.ts index e33112fe8..d717502c5 100644 --- a/src/routes/services/[id]/meilisearch/index.json.ts +++ b/src/routes/services/[id]/meilisearch/index.json.ts @@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => { if (fqdn) fqdn = fqdn.toLowerCase(); try { - await db.updateLanguageToolService({ id, fqdn, name }); + await db.updateService({ id, fqdn, name }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/services/[id]/vaultwarden/index.json.ts b/src/routes/services/[id]/vaultwarden/index.json.ts index 08d80a547..5ec3fa69a 100644 --- a/src/routes/services/[id]/vaultwarden/index.json.ts +++ b/src/routes/services/[id]/vaultwarden/index.json.ts @@ -12,7 +12,7 @@ export const post: RequestHandler = async (event) => { if (fqdn) fqdn = fqdn.toLowerCase(); try { - await db.updateVaultWardenService({ id, fqdn, name }); + await db.updateService({ id, fqdn, name }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/services/[id]/vscodeserver/index.json.ts b/src/routes/services/[id]/vscodeserver/index.json.ts index d1ec862bc..d717502c5 100644 --- a/src/routes/services/[id]/vscodeserver/index.json.ts +++ b/src/routes/services/[id]/vscodeserver/index.json.ts @@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => { if (fqdn) fqdn = fqdn.toLowerCase(); try { - await db.updateVsCodeServer({ id, fqdn, name }); + await db.updateService({ id, fqdn, name }); return { status: 201 }; } catch (error) { return ErrorHandler(error); From 3e8a8364dcb19418b0eafaa3a07f3b379f973e83 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 6 Apr 2022 22:01:41 +0200 Subject: [PATCH 19/24] feat: Basic white labeled version --- src/app.d.ts | 3 +-- src/app.html | 1 - src/hooks.ts | 3 +++ src/routes/__layout.svelte | 16 +++++++++++++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/app.d.ts b/src/app.d.ts index 0557299b9..a88bd7502 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -19,14 +19,13 @@ declare namespace App { } interface SessionData { + whiteLabeled: boolean; version?: string; userId?: string | null; teamId?: string | null; permission?: string; isAdmin?: boolean; expires?: string | null; - gitlabToken?: string | null; - ghToken?: string | null; } type DateTimeFormatOptions = { diff --git a/src/app.html b/src/app.html index 2dc97f37d..a0336e87d 100644 --- a/src/app.html +++ b/src/app.html @@ -2,7 +2,6 @@ - Coolify %svelte.head% diff --git a/src/hooks.ts b/src/hooks.ts index d6c6eca6d..c72c7fd6c 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -7,6 +7,8 @@ import { version } from '$lib/common'; import cookie from 'cookie'; import { dev } from '$app/env'; +const whiteLabeled = process.env['COOLIFY_WHITE_LABELED'] === 'true'; + export const handle = handleSession( { secret: process.env['COOLIFY_SECRET_KEY'], @@ -71,6 +73,7 @@ export const handle = handleSession( export const getSession: GetSession = function ({ locals }) { return { version, + whiteLabeled, ...locals.session.data }; }; diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index ce9cd7c0f..8d5922450 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -134,13 +134,18 @@ Coolify + {#if !$session.whiteLabeled} + + {/if} {#if $session.userId} + {#if $session.whiteLabeled} + Powered by Coolify + {/if} +