From 96b76aea6b530de089890b2ded9ecbc742997c28 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 19:00:39 +0000 Subject: [PATCH 01/14] debug: fider --- apps/api/src/lib/common.ts | 2 +- apps/api/src/lib/serviceFields.ts | 2 +- apps/api/src/routes/api/v1/services/handlers.ts | 6 +++--- package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index ce8b8051d..62064cbc4 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.3.2'; +export const version = '3.3.3'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 89d6a9c70..9ddf37ba3 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -326,7 +326,7 @@ export const fider = [{ isBoolean: false, isEncrypted: true }, { - name: 'postgreslUser', + name: 'postgresqlUser', isEditable: false, isLowerCase: false, isNumber: false, diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 8d07ac70b..71f1cc149 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -197,15 +197,15 @@ export async function getService(request: FastifyRequest) { const teamId = request.user.teamId; const { id } = request.params; const service = await getServiceFromDB({ id, teamId }); - const settings = await listSettings() + console.log(service) if (!service) { throw { status: 404, message: 'Service not found.' } } return { - service, - settings + service } } catch ({ status, message }) { + console.log({status, message}) return errorHandler({ status, message }) } } diff --git a/package.json b/package.json index 9a11294f8..f02c854bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.3.2", + "version": "3.3.3", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": { From eef6b95e242233ed76184f260c67d256f9a972ea Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 19:06:46 +0000 Subject: [PATCH 02/14] debug more --- apps/api/src/lib/common.ts | 4 +++- apps/api/src/routes/api/v1/services/handlers.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 62064cbc4..47149011a 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -1284,13 +1284,14 @@ export async function getServiceFromDB({ id, teamId }: { id: string; teamId: str }); let { type } = body type = fixType(type) - + console.log({body}) if (body?.serviceSecret.length > 0) { body.serviceSecret = body.serviceSecret.map((s) => { s.value = decrypt(s.value); return s; }); } + console.log({body2:body}) body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) } return { ...body, settings }; } @@ -1582,6 +1583,7 @@ export function getUpdateableFields(type: string, data: any) { update[k.name] = temp }); } + console.log({update}) return update } diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 71f1cc149..2ea954bad 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -196,6 +196,7 @@ export async function getService(request: FastifyRequest) { try { const teamId = request.user.teamId; const { id } = request.params; + console.log({id, teamId}) const service = await getServiceFromDB({ id, teamId }); console.log(service) if (!service) { From b63e516274ae8cad796da917a3f363aa6733ce95 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 19:21:45 +0000 Subject: [PATCH 03/14] debug --- apps/api/src/lib/serviceFields.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 9ddf37ba3..7085049b8 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -395,7 +395,7 @@ export const fider = [{ isLowerCase: false, isNumber: false, isBoolean: false, - isEncrypted: true + isEncrypted: false }, { name: 'emailMailgunDomain', From 29dc5a8bb4571f623c5c116cb2c7c1242396fabb Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 19:29:53 +0000 Subject: [PATCH 04/14] revert debug --- apps/api/src/lib/common.ts | 6 ++---- apps/api/src/lib/serviceFields.ts | 2 +- apps/api/src/routes/api/v1/services/handlers.ts | 3 --- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 47149011a..c9ce0e6df 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -1226,7 +1226,6 @@ export async function startTraefikTCPProxy( } traefikUrl = `${ip}/webhooks/traefik/other.json` } - console.log(traefikUrl) const tcpProxy = { version: '3.8', services: { @@ -1284,14 +1283,14 @@ export async function getServiceFromDB({ id, teamId }: { id: string; teamId: str }); let { type } = body type = fixType(type) - console.log({body}) + if (body?.serviceSecret.length > 0) { body.serviceSecret = body.serviceSecret.map((s) => { s.value = decrypt(s.value); return s; }); } - console.log({body2:body}) + body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) } return { ...body, settings }; } @@ -1583,7 +1582,6 @@ export function getUpdateableFields(type: string, data: any) { update[k.name] = temp }); } - console.log({update}) return update } diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 7085049b8..9ddf37ba3 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -395,7 +395,7 @@ export const fider = [{ isLowerCase: false, isNumber: false, isBoolean: false, - isEncrypted: false + isEncrypted: true }, { name: 'emailMailgunDomain', diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 2ea954bad..a2c87092e 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -196,9 +196,7 @@ export async function getService(request: FastifyRequest) { try { const teamId = request.user.teamId; const { id } = request.params; - console.log({id, teamId}) const service = await getServiceFromDB({ id, teamId }); - console.log(service) if (!service) { throw { status: 404, message: 'Service not found.' } } @@ -206,7 +204,6 @@ export async function getService(request: FastifyRequest) { service } } catch ({ status, message }) { - console.log({status, message}) return errorHandler({ status, message }) } } From 9b67a253f1b371dc7cf46e714f9fba0d4a3d2da3 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 19:39:03 +0000 Subject: [PATCH 05/14] fix: decryption errors --- apps/api/src/lib/common.ts | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index c9ce0e6df..a2b78bf50 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -96,17 +96,23 @@ export const base64Decode = (text: string): string => { }; export const decrypt = (hashString: string) => { if (hashString) { - const hash = JSON.parse(hashString); - const decipher = crypto.createDecipheriv( - algorithm, - process.env['COOLIFY_SECRET_KEY'], - Buffer.from(hash.iv, 'hex') - ); - const decrpyted = Buffer.concat([ - decipher.update(Buffer.from(hash.content, 'hex')), - decipher.final() - ]); - return decrpyted.toString(); + try { + const hash = JSON.parse(hashString); + const decipher = crypto.createDecipheriv( + algorithm, + process.env['COOLIFY_SECRET_KEY'], + Buffer.from(hash.iv, 'hex') + ); + const decrpyted = Buffer.concat([ + decipher.update(Buffer.from(hash.content, 'hex')), + decipher.final() + ]); + return decrpyted.toString(); + } catch (error) { + console.log({ decryptionError: error.message }) + return hashString + } + } }; export const encrypt = (text: string) => { From bc802b6f1909663ed182492447197b103380e7fc Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Sun, 14 Aug 2022 20:02:18 +0000 Subject: [PATCH 06/14] fix: postgresql on ARM --- apps/api/src/lib/common.ts | 7 ++++++- csb.nix | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index a2b78bf50..68afa8861 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -873,6 +873,11 @@ export function generateDatabaseConfiguration(database: any, arch: string): } if (isARM(arch)) { configuration.volume = `${id}-${type}-data:/var/lib/postgresql`; + configuration.environmentVariables = { + POSTGRES_PASSWORD: dbUserPassword, + POSTGRES_USER: dbUser, + POSTGRES_DB: defaultDatabase + } } return configuration } else if (type === 'redis') { @@ -909,7 +914,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): return configuration } } -export function isARM(arch) { +export function isARM(arch: string) { if (arch === 'arm' || arch === 'arm64') { return true } diff --git a/csb.nix b/csb.nix index 2005806f9..b2b07e4de 100644 --- a/csb.nix +++ b/csb.nix @@ -1,7 +1,7 @@ with import {}; stdenv.mkDerivation { - name = "git"; + name = "environment"; buildInputs = [ git git-lfs From 163eabb76c99593fb98a252fb4031c80da8de90a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 09:15:42 +0000 Subject: [PATCH 07/14] fix: make it public button --- apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte | 2 +- csb.nix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte index 8a59301f2..36ef4c836 100644 --- a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -59,7 +59,7 @@ async function changeSettings(name: any) { if (name !== 'appendOnly') { - if (publicLoading || !$status.database.isRunning || name !== 'appendOnly') return; + if (publicLoading || !$status.database.isRunning) return; } publicLoading = true; let data = { diff --git a/csb.nix b/csb.nix index b2b07e4de..bee4a5062 100644 --- a/csb.nix +++ b/csb.nix @@ -5,5 +5,6 @@ stdenv.mkDerivation { buildInputs = [ git git-lfs + docker-compose ]; } \ No newline at end of file From 03bf93eb12e3f756d862f70956755241bde84feb Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 09:17:31 +0000 Subject: [PATCH 08/14] fix: loading indicator --- apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte index 36ef4c836..73a971556 100644 --- a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -247,6 +247,7 @@ {#if database.type === 'redis'}
changeSettings('appendOnly')} title={$t('database.change_append_only_mode')} From 68d06dcd192827afdd6c259c56ff2357eca3c993 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 09:17:48 +0000 Subject: [PATCH 09/14] chore: version++ --- apps/api/src/lib/common.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 68afa8861..369b3f2b5 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.3.3'; +export const version = '3.3.4'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; diff --git a/package.json b/package.json index f02c854bb..af9c3f8fe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.3.3", + "version": "3.3.4", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": { From c2c9d992e77015343f56a99db10dbe816deaea17 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 09:27:54 +0000 Subject: [PATCH 10/14] fix: replace docker compose with docker-compose on CSB --- apps/api/src/lib/common.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 369b3f2b5..929aafc08 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -579,6 +579,11 @@ export async function executeDockerCmd({ dockerId, command }: { dockerId: string } else { engine = 'unix:///var/run/docker.sock' } + if (process.env.CODESANDBOX_HOST) { + if (command.startsWith('docker compose')) { + command = command.replace(/docker compose/gi, 'docker-compose') + } + } return await asyncExecShell( `DOCKER_BUILDKIT=1 DOCKER_HOST="${engine}" ${command}` ); @@ -873,11 +878,11 @@ export function generateDatabaseConfiguration(database: any, arch: string): } if (isARM(arch)) { configuration.volume = `${id}-${type}-data:/var/lib/postgresql`; - configuration.environmentVariables = { - POSTGRES_PASSWORD: dbUserPassword, - POSTGRES_USER: dbUser, - POSTGRES_DB: defaultDatabase - } + configuration.environmentVariables = { + POSTGRES_PASSWORD: dbUserPassword, + POSTGRES_USER: dbUser, + POSTGRES_DB: defaultDatabase + } } return configuration } else if (type === 'redis') { From 0586ec3f4288b6b6779738501cc050ffa3c0be1d Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 10:28:40 +0000 Subject: [PATCH 11/14] fix: dashboard ui --- apps/ui/src/routes/index.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/ui/src/routes/index.svelte b/apps/ui/src/routes/index.svelte index ec5132a4f..266fc6b53 100644 --- a/apps/ui/src/routes/index.svelte +++ b/apps/ui/src/routes/index.svelte @@ -97,6 +97,7 @@
+ {#if applications.length > 0}
Resources
@@ -308,6 +309,7 @@
+ {/if} {#if $appSession.teamId === '0'} {/if} From 2f724ffba275f17de1b78aaa2e44316b61bbc1df Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 10:28:57 +0000 Subject: [PATCH 12/14] fix: create coolify-infra, if it does not exists --- apps/api/src/lib/common.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 929aafc08..7617e53ab 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -595,6 +595,11 @@ export async function startTraefikProxy(id: string): Promise { const { id: settingsId, ipv4, ipv6 } = await listSettings(); if (!found) { + const { stdout: coolifyNetwork } = await executeDockerCmd({ dockerId: id, command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` }) + + if (!coolifyNetwork) { + await executeDockerCmd({ dockerId: id, command: `docker network create --attachable coolify-infra` }) + } const { stdout: Config } = await executeDockerCmd({ dockerId: id, command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` }) const ip = JSON.parse(Config)[0].Gateway; let traefikUrl = mainTraefikEndpoint From 3dd2a059bf8b182b65645d1b66a8098473234721 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 11:23:21 +0000 Subject: [PATCH 13/14] fix: gitpod conf and heroku buildpacks --- .gitpod.Dockerfile | 2 ++ .gitpod.yml | 3 ++- apps/api/src/lib/buildPacks/heroku.ts | 14 ++++++++++++-- apps/ui/src/lib/templates.ts | 3 +-- 4 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 .gitpod.Dockerfile diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 000000000..62de37209 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,2 @@ +FROM gitpod/workspace-node:2022-06-20-19-54-55 +RUN (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack) \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml index d46244e67..2cd6c8113 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,7 +1,8 @@ # This configuration file was automatically generated by Gitpod. # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) # and commit this file to your remote git repository to share the goodness with others. -image: gitpod/workspace-node:2022-06-20-19-54-55 +image: + file: .gitpod.Dockerfile tasks: - init: pnpm install && pnpm db:push && pnpm db:seed command: pnpm dev diff --git a/apps/api/src/lib/buildPacks/heroku.ts b/apps/api/src/lib/buildPacks/heroku.ts index 1b382547c..3efdeaf6a 100644 --- a/apps/api/src/lib/buildPacks/heroku.ts +++ b/apps/api/src/lib/buildPacks/heroku.ts @@ -2,14 +2,14 @@ import { executeDockerCmd, prisma } from "../common" import { saveBuildLog } from "./common"; export default async function (data: any): Promise { + const { buildId, applicationId, tag, dockerId, debug, workdir } = data try { - const { buildId, applicationId, tag, dockerId, debug, workdir } = data + await saveBuildLog({ line: `Building image started.`, buildId, applicationId }); const { stdout } = await executeDockerCmd({ dockerId, command: `pack build -p ${workdir} ${applicationId}:${tag} --builder heroku/buildpacks:20` }) - if (debug) { const array = stdout.split('\n') for (const line of array) { @@ -24,6 +24,16 @@ export default async function (data: any): Promise { } await saveBuildLog({ line: `Building image successful.`, buildId, applicationId }); } catch (error) { + const array = error.stdout.split('\n') + for (const line of array) { + if (line !== '\n') { + await saveBuildLog({ + line: `${line.replace('\n', '')}`, + buildId, + applicationId + }); + } + } throw error; } } diff --git a/apps/ui/src/lib/templates.ts b/apps/ui/src/lib/templates.ts index e2c023032..ecd669e8e 100644 --- a/apps/ui/src/lib/templates.ts +++ b/apps/ui/src/lib/templates.ts @@ -292,8 +292,7 @@ export const buildPacks = [ fancyName: 'Deno', hoverColor: 'hover:bg-green-700', color: 'bg-green-700' - } - // }, + }, // { // name: 'heroku', // fancyName: 'Heroku Buildpack', From 8b5c7c94cd547fcd2efe081a6591410b2aba7540 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 15 Aug 2022 14:58:10 +0000 Subject: [PATCH 14/14] feat: Appwrite service --- .../20220815133844_appwrite/migration.sql | 22 + apps/api/prisma/schema.prisma | 58 +- apps/api/src/lib/common.ts | 55 +- apps/api/src/lib/docker.ts | 3 +- apps/api/src/lib/serviceFields.ts | 81 +++ apps/api/src/lib/services.ts | 35 ++ .../src/routes/api/v1/services/handlers.ts | 511 ++++++++++++++++++ apps/ui/src/lib/common.ts | 11 + .../src/lib/components/svg/services/index.ts | 1 + .../routes/services/[id]/_ServiceLinks.svelte | 5 +- .../services/[id]/_Services/_Appwrite.svelte | 126 +++++ .../services/[id]/_Services/_Services.svelte | 5 +- .../[id]/configuration/version.svelte | 1 - package.json | 2 +- 14 files changed, 887 insertions(+), 29 deletions(-) create mode 100644 apps/api/prisma/migrations/20220815133844_appwrite/migration.sql create mode 100644 apps/api/src/lib/services.ts create mode 100644 apps/ui/src/routes/services/[id]/_Services/_Appwrite.svelte diff --git a/apps/api/prisma/migrations/20220815133844_appwrite/migration.sql b/apps/api/prisma/migrations/20220815133844_appwrite/migration.sql new file mode 100644 index 000000000..ec61dc434 --- /dev/null +++ b/apps/api/prisma/migrations/20220815133844_appwrite/migration.sql @@ -0,0 +1,22 @@ +-- CreateTable +CREATE TABLE "Appwrite" ( + "id" TEXT NOT NULL PRIMARY KEY, + "serviceId" TEXT NOT NULL, + "opensslKeyV1" TEXT NOT NULL, + "executorSecret" TEXT NOT NULL, + "redisPassword" TEXT NOT NULL, + "mariadbHost" TEXT, + "mariadbPort" INTEGER NOT NULL DEFAULT 3306, + "mariadbUser" TEXT NOT NULL, + "mariadbPassword" TEXT NOT NULL, + "mariadbRootUser" TEXT NOT NULL, + "mariadbRootUserPassword" TEXT NOT NULL, + "mariadbDatabase" TEXT NOT NULL, + "mariadbPublicPort" INTEGER, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Appwrite_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Appwrite_serviceId_key" ON "Appwrite"("serviceId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 7d50b63b8..4877c6ffb 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -312,30 +312,33 @@ model DatabaseSettings { } model Service { - id String @id @default(cuid()) + id String @id @default(cuid()) name String fqdn String? exposePort Int? - dualCerts Boolean @default(false) + dualCerts Boolean @default(false) type String? version String? destinationDockerId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) - fider Fider? - ghost Ghost? - hasura Hasura? - meiliSearch MeiliSearch? - minio Minio? - moodle Moodle? - plausibleAnalytics PlausibleAnalytics? - persistentStorage ServicePersistentStorage[] - serviceSecret ServiceSecret[] - umami Umami? - vscodeserver Vscodeserver? - wordpress Wordpress? - teams Team[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) + + fider Fider? + ghost Ghost? + hasura Hasura? + meiliSearch MeiliSearch? + minio Minio? + moodle Moodle? + plausibleAnalytics PlausibleAnalytics? + persistentStorage ServicePersistentStorage[] + serviceSecret ServiceSecret[] + umami Umami? + vscodeserver Vscodeserver? + wordpress Wordpress? + appwrite Appwrite? + + teams Team[] } model PlausibleAnalytics { @@ -491,3 +494,22 @@ model Moodle { updatedAt DateTime @updatedAt service Service @relation(fields: [serviceId], references: [id]) } + +model Appwrite { + id String @id @default(cuid()) + serviceId String @unique + opensslKeyV1 String + executorSecret String + redisPassword String + mariadbHost String? + mariadbPort Int @default(3306) + mariadbUser String + mariadbPassword String + mariadbRootUser String + mariadbRootUserPassword String + mariadbDatabase String + mariadbPublicPort Int? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + service Service @relation(fields: [serviceId], references: [id]) +} diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 7617e53ab..6170d380c 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.3.4'; +export const version = '3.4.0'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -78,6 +78,8 @@ export const include: any = { umami: true, hasura: true, fider: true, + moodle: true, + appwrite: true }; export const uniqueName = (): string => uniqueNamesGenerator(customConfig); @@ -275,6 +277,17 @@ export const supportedServiceTypesAndVersions = [ main: 3000 } }, + { + name: 'appwrite', + fancyName: 'Appwrite', + baseImage: 'appwrite/appwrite', + images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'], + versions: ['latest', '0.15.3'], + recommendedVersion: '0.15.3', + ports: { + main: 80 + } + } // { // name: 'moodle', // fancyName: 'Moodle', @@ -596,7 +609,7 @@ export async function startTraefikProxy(id: string): Promise { if (!found) { const { stdout: coolifyNetwork } = await executeDockerCmd({ dockerId: id, command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` }) - + if (!coolifyNetwork) { await executeDockerCmd({ dockerId: id, command: `docker network create --attachable coolify-infra` }) } @@ -1538,6 +1551,35 @@ export async function configureServiceType({ } } }); + } else if (type === 'appwrite') { + const opensslKeyV1 = encrypt(generatePassword()); + const executorSecret = encrypt(generatePassword()); + const redisPassword = encrypt(generatePassword()); + const mariadbHost = `${id}-mariadb` + const mariadbUser = cuid(); + const mariadbPassword = encrypt(generatePassword()); + const mariadbDatabase = 'appwrite'; + const mariadbRootUser = cuid(); + const mariadbRootUserPassword = encrypt(generatePassword()); + await prisma.service.update({ + where: { id }, + data: { + type, + appwrite: { + create: { + opensslKeyV1, + executorSecret, + redisPassword, + mariadbHost, + mariadbUser, + mariadbPassword, + mariadbDatabase, + mariadbRootUser, + mariadbRootUserPassword + } + } + } + }); } else { await prisma.service.update({ where: { id }, @@ -1549,6 +1591,7 @@ export async function configureServiceType({ } export async function removeService({ id }: { id: string }): Promise { + await prisma.serviceSecret.deleteMany({ where: { serviceId: id } }); await prisma.servicePersistentStorage.deleteMany({ where: { serviceId: id } }); await prisma.meiliSearch.deleteMany({ where: { serviceId: id } }); await prisma.fider.deleteMany({ where: { serviceId: id } }); @@ -1559,8 +1602,8 @@ export async function removeService({ id }: { id: string }): Promise { await prisma.minio.deleteMany({ where: { serviceId: id } }); await prisma.vscodeserver.deleteMany({ where: { serviceId: id } }); await prisma.wordpress.deleteMany({ where: { serviceId: id } }); - await prisma.serviceSecret.deleteMany({ where: { serviceId: id } }); - + await prisma.moodle.deleteMany({ where: { serviceId: id } }); + await prisma.appwrite.deleteMany({ where: { serviceId: id } }); await prisma.service.delete({ where: { id } }); } @@ -1625,9 +1668,9 @@ export const getServiceMainPort = (service: string) => { export function makeLabelForServices(type) { return [ 'coolify.managed=true', - `coolify.version = ${version} `, + `coolify.version = ${version}`, `coolify.type = service`, - `coolify.service.type = ${type} ` + `coolify.service.type = ${type}` ]; } export function errorHandler({ status = 500, message = 'Unknown error.' }: { status: number, message: string | any }) { diff --git a/apps/api/src/lib/docker.ts b/apps/api/src/lib/docker.ts index db2dbc3e7..3244c2475 100644 --- a/apps/api/src/lib/docker.ts +++ b/apps/api/src/lib/docker.ts @@ -16,6 +16,7 @@ export function formatLabelsOnDocker(data) { export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise { let containerFound = false; try { + console.log('checking ', container) const { stdout } = await executeDockerCmd({ dockerId, command: @@ -71,7 +72,7 @@ export async function removeContainer({ }): Promise { try { const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` }) - + console.log(id) if (JSON.parse(stdout).Running) { await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` }) await executeDockerCmd({ dockerId, command: `docker rm ${id}` }) diff --git a/apps/api/src/lib/serviceFields.ts b/apps/api/src/lib/serviceFields.ts index 9ddf37ba3..be8c34c4f 100644 --- a/apps/api/src/lib/serviceFields.ts +++ b/apps/api/src/lib/serviceFields.ts @@ -469,6 +469,87 @@ export const moodle = [{ isBoolean: false, isEncrypted: true }, +{ + name: 'mariadbDatabase', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}] + +export const appwrite = [{ + name: 'opensslKeyV1', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'executorSecret', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'redisPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'mariadbHost', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'mariadbPort', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'mariadbUser', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'mariadbPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'mariadbRootUser', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'mariadbRootUserPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, { name: 'mariadbDatabase', isEditable: true, diff --git a/apps/api/src/lib/services.ts b/apps/api/src/lib/services.ts new file mode 100644 index 000000000..22c8081a5 --- /dev/null +++ b/apps/api/src/lib/services.ts @@ -0,0 +1,35 @@ +import { createDirectories, getServiceFromDB, getServiceImage, getServiceMainPort, makeLabelForServices } from "./common"; + +export async function defaultServiceConfigurations({ id, teamId }) { + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, destinationDocker, type, serviceSecret } = service; + + const network = destinationDockerId && destinationDocker.network; + const port = getServiceMainPort(type); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + + const image = getServiceImage(type); + let secrets = []; + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + secrets.push([secret.name]=secret.value); + }); + } + return { ...service, network, port, workdir, image, secrets } +} + +export function defaultServiceComposeConfiguration(network: string) { + return { + networks: [network], + restart: 'always', + deploy: { + restart_policy: { + condition: 'on-failure', + delay: '10s', + max_attempts: 10, + window: '120s' + } + } + } +} \ No newline at end of file diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index a2c87092e..ceef05420 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -9,6 +9,7 @@ import cuid from 'cuid'; import type { OnlyId } from '../../../../types'; import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types'; +import { defaultServiceComposeConfiguration, defaultServiceConfigurations } from '../../../../lib/services'; // async function startServiceNew(request: FastifyRequest) { // try { @@ -588,6 +589,9 @@ export async function startService(request: FastifyRequest) { if (type === 'moodle') { return await startMoodleService(request) } + if (type === 'appwrite') { + return await startAppWriteService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -638,6 +642,9 @@ export async function stopService(request: FastifyRequest) { if (type === 'fider') { return await stopFiderService(request) } + if (type === 'appwrite') { + return await stopAppWriteService(request) + } if (type === 'moodle') { return await stopMoodleService(request) } @@ -2472,7 +2479,511 @@ async function stopFiderService(request: FastifyRequest) { return errorHandler({ status, message }) } } +async function startAppWriteService(request: FastifyRequest) { + try { + const { id } = request.params; + const teamId = request.user.teamId; + const { version, fqdn, destinationDocker, secrets, exposePort, network, port, workdir, image, appwrite } = await defaultServiceConfigurations({ id, teamId }) + let isStatsEnabled = false + if (secrets._APP_USAGE_STATS) { + isStatsEnabled = true + } + const { + opensslKeyV1, + executorSecret, + mariadbHost, + mariadbPort, + mariadbUser, + mariadbPassword, + mariadbRootUser, + mariadbRootUserPassword, + mariadbDatabase + } = appwrite; + + const dockerCompose = { + [id]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: id, + labels: makeLabelForServices('appwrite'), + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + "volumes": [ + `${id}-uploads:/storage/uploads:rw`, + `${id}-cache:/storage/cache:rw`, + `${id}-config:/storage/config:rw`, + `${id}-certificates:/storage/certificates:rw`, + `${id}-functions:/storage/functions:rw` + ], + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + "_APP_LOCALE=en", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_DOMAIN=${fqdn}`, + `_APP_DOMAIN_TARGET=${fqdn}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + `_APP_INFLUXDB_HOST=${id}-influxdb`, + "_APP_INFLUXDB_PORT=8806", + `_APP_EXECUTOR_SECRET=${executorSecret}`, + `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, + `_APP_STATSD_HOST=${id}-telegraf`, + "_APP_STATSD_PORT=8125", + ...secrets + ] + }, + [`${id}-realtime`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-realtime`, + entrypoint: "realtime", + labels: makeLabelForServices('appwrite'), + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-worker-audits`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-audits`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-audits", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-worker-webhooks`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-webhooks`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-webhooks", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + ...secrets + ] + }, + [`${id}-worker-deletes`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-deletes`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-deletes", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "volumes": [ + `${id}-uploads:/storage/uploads:rw`, + `${id}-cache:/storage/cache:rw`, + `${id}-config:/storage/config:rw`, + `${id}-certificates:/storage/certificates:rw`, + `${id}-functions:/storage/functions:rw`, + `${id}-builds:/storage/builds:rw`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + `_APP_EXECUTOR_SECRET=${executorSecret}`, + `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, + ...secrets + ] + }, + [`${id}-worker-databases`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-databases`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-databases", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-worker-builds`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-builds`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-builds", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_EXECUTOR_SECRET=${executorSecret}`, + `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-worker-certificates`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-certificates`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-certificates", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + ], + "volumes": [ + `${id}-config:/storage/config:rw`, + `${id}-certificates:/storage/certificates:rw`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_DOMAIN=${fqdn}`, + `_APP_DOMAIN_TARGET=${fqdn}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-worker-functions`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-functions`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-functions", + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + `${id}-executor` + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + `_APP_EXECUTOR_SECRET=${executorSecret}`, + `_APP_EXECUTOR_HOST=http://${id}-executor/v1`, + ...secrets + ] + }, + [`${id}-executor`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-executor`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "executor", + "stop_signal": "SIGINT", + "volumes": [ + `${id}-functions:/storage/functions:rw`, + `${id}-builds:/storage/builds:rw`, + "/var/run/docker.sock:/var/run/docker.sock", + "/tmp:/tmp:rw" + ], + "depends_on": [ + `${id}-mariadb`, + `${id}-redis`, + `${id}` + ], + "environment": [ + "_APP_ENV=production", + `_APP_EXECUTOR_SECRET=${executorSecret}`, + ...secrets + ] + }, + [`${id}-worker-mails`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-mails`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-mails", + "depends_on": [ + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + ...secrets + ] + }, + [`${id}-worker-messaging`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-worker-messaging`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "worker-messaging", + "depends_on": [ + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + ...secrets + ] + }, + [`${id}-maintenance`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-maintenance`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "maintenance", + "depends_on": [ + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_DOMAIN=${fqdn}`, + `_APP_DOMAIN_TARGET=${fqdn}`, + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + ...secrets + ] + }, + [`${id}-schedule`]: { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-schedule`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "schedule", + "depends_on": [ + `${id}-redis`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + ...secrets + ] + }, + [`${id}-mariadb`]: { + ...defaultServiceComposeConfiguration(network), + "image": "mariadb:10.7", + container_name: `${id}-mariadb`, + labels: makeLabelForServices('appwrite'), + "volumes": [ + `${id}-mariadb:/var/lib/mysql:rw` + ], + "environment": [ + `MYSQL_ROOT_USER=${mariadbRootUser}`, + `MYSQL_ROOT_PASSWORD=${mariadbRootUserPassword}`, + `MYSQL_USER=${mariadbUser}`, + `MYSQL_PASSWORD=${mariadbPassword}`, + `MYSQL_DATABASE=${mariadbDatabase}` + ], + "command": "mysqld --innodb-flush-method=fsync" + }, + [`${id}-redis`]: { + ...defaultServiceComposeConfiguration(network), + "image": "redis:6.2-alpine", + container_name: `${id}-redis`, + "command": `redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5\n`, + "volumes": [ + `${id}-redis:/data:rw` + ] + }, + + }; + if (isStatsEnabled) { + dockerCompose.id.depends_on.push(`${id}-influxdb`); + dockerCompose[`${id}-usage`] = { + ...defaultServiceComposeConfiguration(network), + image: `${image}:${version}`, + container_name: `${id}-usage`, + labels: makeLabelForServices('appwrite'), + "entrypoint": "usage", + "depends_on": [ + `${id}-mariadb`, + `${id}-influxdb`, + ], + "environment": [ + "_APP_ENV=production", + `_APP_OPENSSL_KEY_V1=${opensslKeyV1}`, + `_APP_DB_HOST=${mariadbHost}`, + `_APP_DB_PORT=${mariadbPort}`, + `_APP_DB_SCHEMA=${mariadbDatabase}`, + `_APP_DB_USER=${mariadbUser}`, + `_APP_DB_PASS=${mariadbPassword}`, + `_APP_INFLUXDB_HOST=${id}-influxdb`, + "_APP_INFLUXDB_PORT=8806", + `_APP_REDIS_HOST=${id}-redis`, + "_APP_REDIS_PORT=6379", + ...secrets + ] + } + dockerCompose[`${id}-influxdb`] = { + ...defaultServiceComposeConfiguration(network), + "image": "appwrite/influxdb:1.5.0", + container_name: `${id}-influxdb`, + "volumes": [ + `${id}-influxdb:/var/lib/influxdb:rw` + ] + } + dockerCompose[`${id}-telegraf`] = { + ...defaultServiceComposeConfiguration(network), + "image": "appwrite/telegraf:1.4.0", + container_name: `${id}-telegraf`, + "environment": [ + `_APP_INFLUXDB_HOST=${id}-influxdb`, + "_APP_INFLUXDB_PORT=8806", + ] + } + } + + const composeFile: any = { + version: '3.8', + services: dockerCompose, + networks: { + [network]: { + external: true + } + }, + volumes: { + [`${id}-uploads`]: { + name: `${id}-uploads` + }, + [`${id}-cache`]: { + name: `${id}-cache` + }, + [`${id}-config`]: { + name: `${id}-config` + }, + [`${id}-certificates`]: { + name: `${id}-certificates` + }, + [`${id}-functions`]: { + name: `${id}-functions` + }, + [`${id}-builds`]: { + name: `${id}-builds` + }, + [`${id}-mariadb`]: { + name: `${id}-mariadb` + }, + [`${id}-redis`]: { + name: `${id}-redis` + }, + [`${id}-influxdb`]: { + name: `${id}-influxdb` + } + } + + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} +async function stopAppWriteService(request: FastifyRequest) { + try { + // TODO: Fix async for of + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, destinationDocker } = service; + const containers = [`${id}-mariadb`, `${id}-redis`, `${id}-influxdb`, `${id}-telegraf`, id, `${id}-realtime`, `${id}-worker-audits`, `${id}worker-webhooks`, `${id}-worker-deletes`, `${id}-worker-databases`, `${id}-worker-builds`, `${id}-worker-certificates`, `${id}-worker-functions`, `${id}-worker-mails`, `${id}-worker-messaging`, `${id}-maintenance`, `${id}-schedule`, `${id}-executor`, `${id}-usage`] + if (destinationDockerId) { + for (const container of containers) { + const found = await checkContainer({ dockerId: destinationDocker.id, container }); + if (found) { + await removeContainer({ id, dockerId: destinationDocker.id }); + } + + } + } + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} async function startMoodleService(request: FastifyRequest) { try { const { id } = request.params; diff --git a/apps/ui/src/lib/common.ts b/apps/ui/src/lib/common.ts index 79c1598b5..32485a522 100644 --- a/apps/ui/src/lib/common.ts +++ b/apps/ui/src/lib/common.ts @@ -148,6 +148,17 @@ export const supportedServiceTypesAndVersions = [ main: 3000 } }, + { + name: 'appwrite', + fancyName: 'Appwrite', + baseImage: 'appwrite/appwrite', + images: ['mariadb:10.7', 'redis:6.2-alpine', 'appwrite/telegraf:1.4.0'], + versions: ['latest', '0.15.3'], + recommendedVersion: '0.15.3', + ports: { + main: 80 + } + } // { // name: 'moodle', // fancyName: 'Moodle', diff --git a/apps/ui/src/lib/components/svg/services/index.ts b/apps/ui/src/lib/components/svg/services/index.ts index 007ded1ce..e4f5bc749 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -13,5 +13,6 @@ export { default as MeiliSearch } from './MeiliSearch.svelte'; export { default as Umami } from './Umami.svelte'; export { default as Hasura } from './Hasura.svelte'; export { default as Fider } from './Fider.svelte'; +export { default as Appwrite } from './Moodle.svelte'; export { default as Moodle } from './Moodle.svelte'; diff --git a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte index e25f3d2d0..cad074ee5 100644 --- a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte +++ b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte @@ -55,9 +55,12 @@ +{:else if service.type === 'appwrote'} + + + {:else if service.type === 'moodle'} {/if} - diff --git a/apps/ui/src/routes/services/[id]/_Services/_Appwrite.svelte b/apps/ui/src/routes/services/[id]/_Services/_Appwrite.svelte new file mode 100644 index 000000000..f27e49fd4 --- /dev/null +++ b/apps/ui/src/routes/services/[id]/_Services/_Appwrite.svelte @@ -0,0 +1,126 @@ + + +
+
Appwrite
+
+ +
+ + +
+
+ + +
+ + + +
+
MariaDB
+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte index 3873604d1..326ae4c1a 100644 --- a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte +++ b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte @@ -26,6 +26,7 @@ import Umami from './_Umami.svelte'; import VsCodeServer from './_VSCodeServer.svelte'; import Wordpress from './_Wordpress.svelte'; + import Appwrite from './_Appwrite.svelte'; import Moodle from './_Moodle.svelte'; const { id } = $page.params; @@ -37,7 +38,7 @@ save: false, verification: false, cleanup: false - } + }; let dualCerts = service.dualCerts; let nonWWWDomain = service.fqdn && getDomain(service.fqdn).replace(/^www\./, ''); @@ -394,6 +395,8 @@ {:else if service.type === 'fider'} + {:else if service.type === 'appwrite'} + {:else if service.type === 'moodle'} {/if} diff --git a/apps/ui/src/routes/services/[id]/configuration/version.svelte b/apps/ui/src/routes/services/[id]/configuration/version.svelte index 1bbaad1ac..203929e66 100644 --- a/apps/ui/src/routes/services/[id]/configuration/version.svelte +++ b/apps/ui/src/routes/services/[id]/configuration/version.svelte @@ -35,7 +35,6 @@ const { id } = $page.params; const from = $page.url.searchParams.get('from'); - let recommendedVersion = supportedServiceTypesAndVersions.find( ({ name }) => name === type )?.recommendedVersion; diff --git a/package.json b/package.json index af9c3f8fe..f9ea8e763 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.3.4", + "version": "3.4.0", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": {