feat: init moodle and separate stuffs to shared package

This commit is contained in:
Andras Bacsai
2022-07-15 12:31:54 +00:00
parent 49ecb15773
commit 866070b9ea
34 changed files with 1118 additions and 3116 deletions

View File

@@ -69,7 +69,8 @@ export const include: any = {
meiliSearch: true,
umami: true,
hasura: true,
fider: true
fider: true,
moodle: true
};
export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
@@ -112,6 +113,7 @@ export const encrypt = (text: string) => {
});
}
};
import { supportedServiceTypesAndVersions } from 'shared';
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
const applications = await prisma.application.findMany({ where: { branch, projectId } });
return applications.length > 1;
@@ -308,167 +310,6 @@ export const supportedDatabaseTypesAndVersions = [
},
{ 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
}
},
{
name: 'umami',
fancyName: 'Umami',
baseImage: 'ghcr.io/mikecao/umami',
images: ['postgres:12-alpine'],
versions: ['postgresql-latest'],
recommendedVersion: 'postgresql-latest',
ports: {
main: 3000
}
},
{
name: 'hasura',
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.8.3'],
recommendedVersion: 'v2.8.3',
ports: {
main: 8080
}
},
{
name: 'fider',
fancyName: 'Fider',
baseImage: 'getfider/fider',
images: ['postgres:12-alpine'],
versions: ['stable'],
recommendedVersion: 'stable',
ports: {
main: 3000
}
// },
// {
// name: 'appwrite',
// fancyName: 'AppWrite',
// baseImage: 'appwrite/appwrite',
// images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
// versions: ['latest', '0.13.0'],
// recommendedVersion: '0.13.0',
// ports: {
// main: 3000
// }
// }
}
];
export async function startTraefikProxy(engine: string): Promise<void> {
const host = getEngine(engine);
@@ -1347,6 +1188,40 @@ export async function configureServiceType({
}
}
});
} else if (type === 'moodle') {
const defaultUsername = cuid();
const defaultPassword = encrypt(generatePassword());
const defaultEmail = `${cuid()}@example.com`;
const mariadbUser = cuid();
const mariadbPassword = encrypt(generatePassword());
const mariadbDatabase = 'moodle_db';
const mariadbRootUser = cuid();
const mariadbRootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
moodle: {
create: {
defaultUsername,
defaultPassword,
defaultEmail,
mariadbUser,
mariadbPassword,
mariadbDatabase,
mariadbRootUser,
mariadbRootUserPassword
}
}
}
});
} else {
await prisma.service.update({
where: { id },
data: {
type
}
});
}
}

View File

@@ -15,7 +15,7 @@ export const plausibleAnalytics = [{
isNumber: false,
isBoolean: false,
isEncrypted: false
},{
}, {
name: 'username',
isEditable: true,
isLowerCase: false,
@@ -86,7 +86,7 @@ export const minio = [{
isNumber: false,
isBoolean: false,
isEncrypted: false
},{
}, {
name: 'rootUser',
isEditable: false,
isLowerCase: false,
@@ -325,7 +325,7 @@ export const fider = [{
isNumber: false,
isBoolean: false,
isEncrypted: true
},{
}, {
name: 'postgreslUser',
isEditable: false,
isLowerCase: false,
@@ -412,4 +412,68 @@ export const fider = [{
isNumber: false,
isBoolean: false,
isEncrypted: false
}]
export const moodle = [{
name: 'defaultEmail',
isEditable: true,
isLowerCase: true,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultUsername',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
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,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
}]

View File

@@ -2,13 +2,14 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions, generatePassword, isDev, stopTcpHttpProxy } from '../../../../lib/common';
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs';
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid';
import type { OnlyId } from '../../../../types';
import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
import { supportedServiceTypesAndVersions } from 'shared';
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
// try {
@@ -227,7 +228,6 @@ export async function getServiceType(request: FastifyRequest) {
}
export async function saveServiceType(request: FastifyRequest<SaveServiceType>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
const { type } = request.body;
await configureServiceType({ id, type });
@@ -589,6 +589,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
if (type === 'fider') {
return await startFiderService(request)
}
if (type === 'moodle') {
return await startMoodleService(request)
}
throw `Service type ${type} not supported.`
} catch (error) {
throw { status: 500, message: error?.message || error }
@@ -639,6 +642,9 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
if (type === 'fider') {
return await stopFiderService(request)
}
if (type === 'moodle') {
return await stopMoodleService(request)
}
throw `Service type ${type} not supported.`
} catch (error) {
throw { status: 500, message: error?.message || error }
@@ -2524,6 +2530,170 @@ async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
}
}
async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
exposePort,
moodle: {
defaultUsername,
defaultPassword,
defaultEmail,
mariadbRootUser,
mariadbRootUserPassword,
mariadbDatabase,
mariadbPassword,
mariadbUser
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('moodle');
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const domain = getDomain(fqdn);
const config = {
moodle: {
image: `${image}:${version}`,
volume: `${id}-data:/bitnami/moodle`,
environmentVariables: {
MOODLE_USERNAME: defaultUsername,
MOODLE_PASSWORD: defaultPassword,
MOODLE_EMAIL: defaultEmail,
MOODLE_DATABASE_HOST: `${id}-mariadb`,
MOODLE_DATABASE_USER: mariadbUser,
MOODLE_DATABASE_PASSWORD: mariadbPassword,
MOODLE_DATABASE_NAME: mariadbDatabase,
MOODLE_REVERSEPROXY: 'yes'
}
},
mariadb: {
image: 'bitnami/mariadb:latest',
volume: `${id}-mariadb-data:/bitnami/mariadb`,
environmentVariables: {
MARIADB_USER: mariadbUser,
MARIADB_PASSWORD: mariadbPassword,
MARIADB_DATABASE: mariadbDatabase,
MARIADB_ROOT_USER: mariadbRootUser,
MARIADB_ROOT_PASSWORD: mariadbRootUserPassword
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.moodle.environmentVariables[secret.name] = secret.value;
});
}
const composeFile: ComposeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.moodle.image,
environment: config.moodle.environmentVariables,
networks: [network],
volumes: [],
restart: 'always',
labels: makeLabelForServices('moodle'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
},
depends_on: [`${id}-mariadb`]
},
[`${id}-mariadb`]: {
container_name: `${id}-mariadb`,
image: config.mariadb.image,
environment: config.mariadb.environmentVariables,
networks: [network],
volumes: [],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
},
depends_on: []
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[config.moodle.volume.split(':')[0]]: {
name: config.moodle.volume.split(':')[0]
},
[config.mariadb.volume.split(':')[0]]: {
name: config.mariadb.volume.split(':')[0]
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
async function stopMoodleService(request: FastifyRequest<ServiceStartStop>) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeContainer({ id, engine });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-mariadb`);
if (found) {
await removeContainer({ id: `${id}-mariadb`, engine });
}
} catch (error) {
console.error(error);
}
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {
const { id } = request.params

View File

@@ -1,5 +1,6 @@
import { FastifyRequest } from "fastify";
import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions } from "../../../lib/common";
import { supportedServiceTypesAndVersions } from "shared";
import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma } from "../../../lib/common";
import { getEngine } from "../../../lib/docker";
import { TraefikOtherConfiguration } from "./types";