From 40e8dd4a8d7787b5f1a27a385e5975ff7ccf74a8 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 31 Aug 2022 15:02:05 +0200 Subject: [PATCH] feat: new service - weblate --- .../migration.sql | 18 +++ apps/api/prisma/schema.prisma | 18 ++- apps/api/src/lib/services/common.ts | 44 ++++---- apps/api/src/lib/services/handlers.ts | 106 ++++++++++++++++++ apps/api/src/lib/services/serviceFields.ts | 99 +++++++++++++++- .../api/src/lib/services/supportedVersions.ts | 11 ++ .../svg/services/ServiceIcons.svelte | 2 + .../components/svg/services/Weblate.svelte | 61 ++++++++++ .../src/lib/components/svg/services/index.ts | 3 +- .../routes/services/[id]/_ServiceLinks.svelte | 4 + .../services/[id]/_Services/_Services.svelte | 3 + .../services/[id]/_Services/_Weblate.svelte | 66 +++++++++++ 12 files changed, 413 insertions(+), 22 deletions(-) create mode 100644 apps/api/prisma/migrations/20220831095714_service_weblate/migration.sql create mode 100644 apps/ui/src/lib/components/svg/services/Weblate.svelte create mode 100644 apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte diff --git a/apps/api/prisma/migrations/20220831095714_service_weblate/migration.sql b/apps/api/prisma/migrations/20220831095714_service_weblate/migration.sql new file mode 100644 index 000000000..c985b4ae2 --- /dev/null +++ b/apps/api/prisma/migrations/20220831095714_service_weblate/migration.sql @@ -0,0 +1,18 @@ +-- CreateTable +CREATE TABLE "Weblate" ( + "id" TEXT NOT NULL PRIMARY KEY, + "adminPassword" TEXT NOT NULL, + "postgresqlHost" TEXT NOT NULL, + "postgresqlPort" INTEGER NOT NULL, + "postgresqlUser" TEXT NOT NULL, + "postgresqlPassword" TEXT NOT NULL, + "postgresqlDatabase" TEXT NOT NULL, + "postgresqlPublicPort" INTEGER, + "serviceId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Weblate_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Weblate_serviceId_key" ON "Weblate"("serviceId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index dfa7ae26b..dc254b6df 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -200,7 +200,7 @@ model Build { commit String? pullmergeRequestId String? forceRebuild Boolean @default(false) - sourceBranch String? + sourceBranch String? branch String? status String? @default("queued") createdAt DateTime @default(now()) @@ -348,6 +348,7 @@ model Service { wordpress Wordpress? appwrite Appwrite? searxng Searxng? + weblate Weblate? } model PlausibleAnalytics { @@ -559,3 +560,18 @@ model Searxng { updatedAt DateTime @updatedAt service Service @relation(fields: [serviceId], references: [id]) } + +model Weblate { + id String @id @default(cuid()) + adminPassword String + postgresqlHost String + postgresqlPort Int + postgresqlUser String + postgresqlPassword String + postgresqlDatabase String + postgresqlPublicPort Int? + serviceId String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + service Service @relation(fields: [serviceId], references: [id]) +} diff --git a/apps/api/src/lib/services/common.ts b/apps/api/src/lib/services/common.ts index 44caeabe9..ff63bc9e3 100644 --- a/apps/api/src/lib/services/common.ts +++ b/apps/api/src/lib/services/common.ts @@ -1,23 +1,7 @@ -import { exec } from 'node:child_process' -import util from 'util'; -import fs from 'fs/promises'; -import yaml from 'js-yaml'; -import forge from 'node-forge'; -import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; -import type { Config } from 'unique-names-generator'; -import generator from 'generate-password'; -import crypto from 'crypto'; -import { promises as dns } from 'dns'; -import { PrismaClient } from '@prisma/client'; + import cuid from 'cuid'; -import os from 'os'; -import sshConfig from 'ssh-config' import { encrypt, generatePassword, prisma } from '../common'; - -export const version = '3.8.2'; -export const isDev = process.env.NODE_ENV === 'development'; - export const includeServices: any = { destinationDocker: true, persistentStorage: true, @@ -34,7 +18,8 @@ export const includeServices: any = { moodle: true, appwrite: true, glitchTip: true, - searxng: true + searxng: true, + weblate: true }; export async function configureServiceType({ id, @@ -312,6 +297,27 @@ export async function configureServiceType({ } } }); + }else if (type === 'weblate') { + const adminPassword = encrypt(generatePassword({})) + const postgresqlUser = cuid(); + const postgresqlPassword = encrypt(generatePassword({})); + const postgresqlDatabase = 'weblate'; + await prisma.service.update({ + where: { id }, + data: { + type, + weblate: { + create: { + adminPassword, + postgresqlHost: `${id}-postgresql`, + postgresqlPort: 5432, + postgresqlUser, + postgresqlPassword, + postgresqlDatabase, + } + } + } + }); } else { await prisma.service.update({ where: { id }, @@ -338,7 +344,7 @@ export async function removeService({ id }: { id: string }): Promise { await prisma.moodle.deleteMany({ where: { serviceId: id } }); await prisma.appwrite.deleteMany({ where: { serviceId: id } }); await prisma.searxng.deleteMany({ where: { serviceId: id } }); - + await prisma.weblate.deleteMany({ where: { serviceId: id } }); await prisma.service.delete({ where: { id } }); } \ No newline at end of file diff --git a/apps/api/src/lib/services/handlers.ts b/apps/api/src/lib/services/handlers.ts index cbefd7a94..f175f77d0 100644 --- a/apps/api/src/lib/services/handlers.ts +++ b/apps/api/src/lib/services/handlers.ts @@ -63,6 +63,9 @@ export async function startService(request: FastifyRequest) { if (type === 'searxng') { return await startSearXNGService(request) } + if (type === 'weblate') { + return await startWeblateService(request) + } throw `Service type ${type} not supported.` } catch (error) { throw { status: 500, message: error?.message || error } @@ -2224,3 +2227,106 @@ async function startSearXNGService(request: FastifyRequest) { return errorHandler({ status, message }) } } + + +async function startWeblateService(request: FastifyRequest) { + try { + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + const { + weblate: { adminPassword, postgresqlHost, postgresqlPort, postgresqlUser, postgresqlPassword, postgresqlDatabase } + } = service; + const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort, persistentStorage, fqdn } = + service; + const network = destinationDockerId && destinationDocker.network; + const port = getServiceMainPort('weblate'); + + const { workdir } = await createDirectories({ repository: type, buildId: id }); + const image = getServiceImage(type); + + const config = { + weblate: { + image: `${image}:${version}`, + volume: `${id}-data:/app/data`, + environmentVariables: { + WEBLATE_SITE_DOMAIN: getDomain(fqdn), + WEBLATE_ADMIN_PASSWORD: adminPassword, + POSTGRES_PASSWORD: postgresqlPassword, + POSTGRES_USER: postgresqlUser, + POSTGRES_DATABASE: postgresqlDatabase, + POSTGRES_HOST: postgresqlHost, + POSTGRES_PORT: postgresqlPort, + REDIS_HOST: `${id}-redis`, + } + }, + postgresql: { + image: `postgres:14-alpine`, + volume: `${id}-postgresql-data:/var/lib/postgresql/data`, + environmentVariables: { + POSTGRES_PASSWORD: postgresqlPassword, + POSTGRES_USER: postgresqlUser, + POSTGRES_DB: postgresqlDatabase, + POSTGRES_HOST: postgresqlHost, + POSTGRES_PORT: postgresqlPort, + } + }, + redis: { + image: `redis:6-alpine`, + volume: `${id}-redis-data:/data`, + } + + }; + + if (serviceSecret.length > 0) { + serviceSecret.forEach((secret) => { + config.weblate.environmentVariables[secret.name] = secret.value; + }); + } + const { volumes, volumeMounts } = persistentVolumes(id, persistentStorage, config) + const composeFile: ComposeFile = { + version: '3.8', + services: { + [id]: { + container_name: id, + image: config.weblate.image, + environment: config.weblate.environmentVariables, + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + volumes, + labels: makeLabelForServices('weblate'), + ...defaultComposeConfiguration(network), + }, + [`${id}-postgresql`]: { + container_name: `${id}-postgresql`, + image: config.postgresql.image, + environment: config.postgresql.environmentVariables, + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + volumes, + labels: makeLabelForServices('weblate'), + ...defaultComposeConfiguration(network), + }, + [`${id}-redis`]: { + container_name: `${id}-redis`, + image: config.redis.image, + ...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}), + volumes, + labels: makeLabelForServices('weblate'), + ...defaultComposeConfiguration(network), + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: volumeMounts + }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; + await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); + await startServiceContainers(destinationDocker.id, composeFileDestination) + return {} + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} + diff --git a/apps/api/src/lib/services/serviceFields.ts b/apps/api/src/lib/services/serviceFields.ts index a9d5b379a..7994643cf 100644 --- a/apps/api/src/lib/services/serviceFields.ts +++ b/apps/api/src/lib/services/serviceFields.ts @@ -599,6 +599,54 @@ export const glitchTip = [{ isBoolean: false, isEncrypted: true }, +{ + name: 'emailSmtpHost', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'emailSmtpPassword', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'emailSmtpUseSsl', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: true, + isEncrypted: false +}, +{ + name: 'emailSmtpUseSsl', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: true, + isEncrypted: false +}, +{ + name: 'emailSmtpPort', + isEditable: true, + isLowerCase: false, + isNumber: true, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'emailSmtpUser', + isEditable: true, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, { name: 'defaultEmail', isEditable: false, @@ -624,7 +672,7 @@ export const glitchTip = [{ isEncrypted: true }, { - name: 'defaultFromEmail', + name: 'defaultEmailFrom', isEditable: true, isLowerCase: false, isNumber: false, @@ -687,4 +735,53 @@ export const searxng = [{ isNumber: false, isBoolean: false, isEncrypted: true +}] + +export const weblate = [{ + name: 'adminPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'postgresqlHost', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'postgresqlPort', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'postgresqlUser', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false +}, +{ + name: 'postgresqlPassword', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: true +}, +{ + name: 'postgresqlDatabase', + isEditable: false, + isLowerCase: false, + isNumber: false, + isBoolean: false, + isEncrypted: false }] \ No newline at end of file diff --git a/apps/api/src/lib/services/supportedVersions.ts b/apps/api/src/lib/services/supportedVersions.ts index b6f039695..c31ef1993 100644 --- a/apps/api/src/lib/services/supportedVersions.ts +++ b/apps/api/src/lib/services/supportedVersions.ts @@ -190,4 +190,15 @@ export const supportedServiceTypesAndVersions = [ main: 8080 } }, + { + name: 'weblate', + fancyName: 'Weblate', + baseImage: 'weblate/weblate', + images: ['postgres:14-alpine','redis:6-alpine'], + versions: ['latest'], + recommendedVersion: 'latest', + ports: { + main: 8080 + } + }, ]; \ No newline at end of file diff --git a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte index 754068e2d..4cc5426b2 100644 --- a/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte +++ b/apps/ui/src/lib/components/svg/services/ServiceIcons.svelte @@ -40,4 +40,6 @@ {:else if type === 'searxng'} +{:else if type === 'weblate'} + {/if} diff --git a/apps/ui/src/lib/components/svg/services/Weblate.svelte b/apps/ui/src/lib/components/svg/services/Weblate.svelte new file mode 100644 index 000000000..25b5a837a --- /dev/null +++ b/apps/ui/src/lib/components/svg/services/Weblate.svelte @@ -0,0 +1,61 @@ + + + diff --git a/apps/ui/src/lib/components/svg/services/index.ts b/apps/ui/src/lib/components/svg/services/index.ts index 15fa32f90..22f7702c9 100644 --- a/apps/ui/src/lib/components/svg/services/index.ts +++ b/apps/ui/src/lib/components/svg/services/index.ts @@ -16,4 +16,5 @@ export { default as Fider } from './Fider.svelte'; export { default as Appwrite } from './Appwrite.svelte'; export { default as Moodle } from './Moodle.svelte'; export { default as GlitchTip } from './GlitchTip.svelte'; -export { default as Searxng } from './Searxng.svelte'; \ No newline at end of file +export { default as Searxng } from './Searxng.svelte'; +export { default as Weblate } from './Weblate.svelte'; \ No newline at end of file diff --git a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte index 22044229c..d68a3eae0 100644 --- a/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte +++ b/apps/ui/src/routes/services/[id]/_ServiceLinks.svelte @@ -71,4 +71,8 @@ +{:else if service.type === 'weblate'} + + + {/if} diff --git a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte index b7d365680..929a24170 100644 --- a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte +++ b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte @@ -30,6 +30,7 @@ import Appwrite from './_Appwrite.svelte'; import Moodle from './_Moodle.svelte'; import Searxng from './_Searxng.svelte'; + import Weblate from './_Weblate.svelte'; const { id } = $page.params; $: isDisabled = @@ -405,6 +406,8 @@ {:else if service.type === 'searxng'} + {:else if service.type === 'weblate'} + {/if} diff --git a/apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte b/apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte new file mode 100644 index 000000000..fdbd055c1 --- /dev/null +++ b/apps/ui/src/routes/services/[id]/_Services/_Weblate.svelte @@ -0,0 +1,66 @@ + + +
+
Weblate
+
+ +
+ + +
+ +
+
PostgreSQL
+
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +